使用Java反射模拟实现Spring的IoC容器的操作
目录
- 实现的功能:
- 项目结构
- 下面是程序的项目结构图:
- 自定义注解
- 容器实现
- 测试
- 实体类User的定义:
实现的功能:
- 默认情况下将扫描整个项目的文件
- 可以使用@ComponentScan注解配置扫描路径
- 只将被@Component注解修饰的类装载到容器中
- 可以使用@AutoWired注解实现自动装配
- 读取配置文件中的声明的类并注册到容器中
项目结构
下面是程序的项目结构图:
文章图片
自定义注解 下面是自定义的三个注解: @AutoWired,@Component,@ComponentScan。
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface AutoWired {}@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Component {}@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface ComponentScan {String[] value(); }
容器实现 其中AnnotationConfigApplicationContext和ClassPathXMLApplicationContext为核心的类,其中
AnnotationConfigApplicationContext类实现扫描文件和解析注解等功能。
package learn.reflection.reflect; import learn.reflection.Bootstrap; import learn.reflection.annotation.AutoWired; import learn.reflection.annotation.Component; import learn.reflection.annotation.ComponentScan; import java.io.File; import java.io.InputStream; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; public class AnnotationConfigApplicationContext{//使用HaspMap存储Beanprivate HashMap beanFactory=new HashMap<>(); //获取Bean的方法public T getBean(Class clazz){return (T) beanFactory.get(clazz); }String path; //编译后的字节码存储路径/*** 初始化ApplicationContext,加载注解修饰的Bean到beanFactory*/public void initContextByAnnotation(){//编译后的项目根目录:D:/idea_workplace/javaAppliTechnology/target/classes/path = AnnotationConfigApplicationContext.class.getClassLoader().getResource("").getFile(); //查看启动类Bootstrap是否有定义扫描包ComponentScan annotation = Bootstrap.class.getAnnotation(ComponentScan.class); if (annotation!=null){//有定义就只扫描自定义的String[] definedPaths = annotation.value(); if (definedPaths!=null&&definedPaths.length>0){loadClassInDefinedDir(path,definedPaths); }}else{//默认扫描整个项目的目录System.out.println(path); findClassFile(new File(path)); }assembleObject(); }/*** 给@AutoWired修饰的属性赋值*/private void assembleObject(){Set > entries = beanFactory.entrySet(); //扫描所有容器中的Beanfor (Map.Entry entry : entries) {Object value = https://www.it610.com/article/entry.getValue(); //获取所有属性Field[] fields = value.getClass().getDeclaredFields(); for (Field field : fields) {//如果被@AutoWired注解修饰则进行赋值AutoWired annotation = field.getAnnotation(AutoWired.class); if (annotation!=null){try {field.setAccessible(true); field.set(value,beanFactory.get(field.getType())); } catch (IllegalAccessException e) {e.printStackTrace(); }}}}}/*** 扫描用户自定义的包* @param path* @param definedPaths*/private void loadClassInDefinedDir(String path, String[] definedPaths){for (String definedPath : definedPaths) {//转换成绝对路径String s = definedPath.replaceAll("\\.", "/"); String fullName=path+s; System.out.println(s); findClassFile(new File(fullName)); }}/*** 扫描项目中的每一个文件夹找到所有的class文件*/private void findClassFile(File pathParent) {//路径是否是目录,子目录是否为空if (pathParent.isDirectory()) {File[] childrenFiles = pathParent.listFiles(); if (childrenFiles == null || childrenFiles.length == 0) {return; }for (File childrenFile : childrenFiles) {if (childrenFile.isDirectory()) {//递归调用直到找到所有的文件findClassFile(childrenFile); } else {//找到文件loadClassWithAnnotation(childrenFile); }}}}/***装配找到的所有带有@Component注解的类到容器*/private void loadClassWithAnnotation(File file) {//1.去掉前面的项目绝对路径String pathWithClass=file.getAbsolutePath().substring(path.length()-1); //2.将路径的“/”转化为“.”和去掉后面的.classif (pathWithClass.contains(".class")){String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", ""); /***根据获取到的类的全限定名使用反射将实例添加到beanFactory中*/try {Class> clazz = Class.forName(fullName); //3.判断是不是接口,不是接口才创建实例if (!clazz.isInterface()){//4.是否具有@Bean注解Component annotation = clazz.getAnnotation(Component.class); if (annotation!=null){//5.创建实例对象Object instance = clazz.newInstance(); //6.判断是否有实现的接口Class>[] interfaces = clazz.getInterfaces(); if (interfaces!=null&&interfaces.length>0){//如果是有接口就将其接口的class作为key,实例对象作为valueSystem.out.println("正在加载【"+interfaces[0].getName()+"】 实例对象:"+instance.getClass().getName()); beanFactory.put(interfaces[0],instance); }else{System.out.println("正在加载【"+clazz.getName()+"】 实例对象:"+instance.getClass().getName()); beanFactory.put(clazz,instance); }//如果没有接口就将自己的class作为key,实例对象作为value}}} catch (Exception e) {e.printStackTrace(); }}}}
ClassPathXMLApplicationContext类实现解析xml配置文件,并装载组件到容器中。
package learn.reflection.reflect; import java.net.URL; import java.util.HashMap; import java.util.Map; import org.jdom2.Document; import org.jdom2.JDOMException; import org.jdom2.Element; import org.jdom2.xpath.XPath; import org.jdom2.input.SAXBuilder; import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URISyntaxException; import java.util.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; /** * @author Hai * @date 2020/5/17 - 18:47 */public class ClassPathXMLApplicationContext{private File file; private Map map = new HashMap(); public ClassPathXMLApplicationContext(String config_file) {URL url = this.getClass().getClassLoader().getResource(config_file); try {file = new File(url.toURI()); XMLParsing(); } catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace(); }}private void XMLParsing() throws Exception {SAXBuilder builder = new SAXBuilder(); Document document = builder.build(file); Element root = document.getRootElement(); List elementList = root.getChildren("bean"); Iterator i = elementList.iterator(); //读取bean节点的所有信息while (i.hasNext()) {Element bean = (Element) i.next(); String id = bean.getAttributeValue("id"); //根据class创建实例String cls = bean.getAttributeValue("class"); Object obj = Class.forName(cls).newInstance(); Method[] method = obj.getClass().getDeclaredMethods(); Listlist = bean.getChildren("property"); for (Element el : list) {for (int n = 0; n < method.length; n++) {String name = method[n].getName(); String temp = null; //找到属性对应的setter方法进行赋值if (name.startsWith("set")) {temp = name.substring(3, name.length()).toLowerCase(); if (el.getAttribute("name") != null) {if (temp.equals(el.getAttribute("name").getValue())) {method[n].invoke(obj, el.getAttribute("value").getValue()); }}}}}map.put(id, obj); }}public Object getBean(String name) {return map.get(name); }}
测试
实体类User的定义:
@Componentpublic class User {private String username; private String password; public User(String username, String password) {this.username = username; this.password = password; }public User() {}//省略getter,setter方法}
在UserServiceImpl类中添加@Component注解,并使用@AutoWired注解注入容器中的IUerDao接口的实现类UserDaoImpl。
@Componentpublic class UserServiceImpl implements IUserService {@AutoWiredprivate IUserDao userDao; @Overridepublic void login(User user) {System.out.println("调用UserDaoImpl的login方法"); userDao.loginByUsername(user); }}
UserDaoImpl类同样添加@Component注解
@Componentpublic class UserDaoImpl implements IUserDao {@Overridepublic void loginByUsername(User user) {System.out.println("验证用户【"+user.getUsername()+"】登录"); }}
在beans.xml中配置注册User类,文件beans.xml的内容如下:
下面同时使用 AnnotationConfigApplicationContext类和 ClassPathXMLApplicationContext类。
Bootstrap类作为启动类添加注解@ComponentScan,指定扫描learn.reflection.dao和learn.reflection.service这两个包。
@ComponentScan(value = https://www.it610.com/article/{"learn.reflection.dao","learn.reflection.service"})public class Bootstrap {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.initContextByAnnotation(); UserServiceImpl userService = (UserServiceImpl) applicationContext.getBean(IUserService.class); ClassPathXMLApplicationContext xmlApplicationContext = new ClassPathXMLApplicationContext("beans.xml"); User user = (User) xmlApplicationContext.getBean("user"); System.out.println(user); userService.login(user); }}
运行Bootstrap类,程序运行结果如下:
learn/reflection/dao以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
正在加载【learn.reflection.dao.IUserDao】 实例对象:learn.reflection.dao.impl.UserDaoImpl
learn/reflection/service
正在加载【learn.reflection.service.IUserService】 实例对象:learn.reflection.service.impl.UserServiceImpl
User{username='张三', password='123'}
调用UserDaoImpl的login方法
验证用户【张三】登录
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 由浅入深理解AOP
- 【译】20个更有效地使用谷歌搜索的技巧
- mybatisplus如何在xml的连表查询中使用queryWrapper
- MybatisPlus|MybatisPlus LambdaQueryWrapper使用int默认值的坑及解决
- MybatisPlus使用queryWrapper如何实现复杂查询
- 事件代理
- Java|Java OpenCV图像处理之SIFT角点检测详解
- java中如何实现重建二叉树
- iOS中的Block