从山寨Spring中学习Spring IOC原理-自动装配注解

前言

上一篇博客【从山寨Spring中学习Spring IOC原理-byType自动装配】完成了在xml中的自动注解的过程,本篇将会挑战一个更加有挑战性的功能,也就是Spring中自动注解功能。更多Spring内容进入【Spring解读系列目录】

思路

不知道大家有没有注意过,在使用Spring的时候,Spring是如何进行初始化的,换句话说Spring是怎么把交给它管理的类进行 实例化的。这里说下,大致可以分为两种情况:

  1. 基于XML的:这里注册一个<bean>,然后使用ClassPathXmlApplicationContext把整个xml加载进来,根据配置的<bean>完成扫描。
  2. 基于JavaConfig:构建一个配置类,在上面加上扫描范围@ComponentScan,然后使用AnnotationConfigApplicationContext把这个类加载进来,根据包内的类上的注解完成初始化。

但是这里还是有细微的区别的,比如单个类注册在基于Xml的配置中只要<bean>配置了,就一定会被注册和初始化,也就是说Xml中只要声明就一定会被解析和实例化。而在JavaConfig中,不仅要加了注解,还必须被扫描到才可以被注册和初始化,如果没有扫描到,Spring是不认这个注解的。当然也可以基于注解,但是注解的又必须依赖Xml或者JavaConfig去完成类的扫描才行,所以就不讨论这个了。

为什么要引入这个小知识呢?因为既然是基于注解的,那就必须要引入扫描。也就是说我们要手动完成扫描,然后针对扫描出来的包进行实例化。这个小点就给我们提供了一个山寨的思路,第一步就是想办法去扫描到包,并且拿到里面的类名。但是在编译的过程中我们已经无法找到源码的路径,所以我们此时拿到的包都会是classes包下的,因此我们此时能拿到的都是*.class文件,基于此我们把这些文件名找到对应的类(用Calss.forName方法拿出来)并构造成对象,然后再使用。

构建

既然要山寨自动装配注解,那就山寨的完全一些,首先创造一个注解出来,加上运行时有效。更多自定义注解的内容请参考【Spring 自定义注解的使用】

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
    
    
    public String value();
}

经过上面的分析,我们就完成了这样的一个类MyAnnoConfigContext,里面有一个scan方法并且在这个里面对发现的类进行实例化,和一个getBean方法用来模拟Spring拿到对应对象的方法。

public class MyAnnoConfigContext {
    
    

    //用来存储创建的实例
    Map<String, Object> newInstanceMap = new HashMap<>();

    public void scan(String packagePath) {
    
    
        //拿到编译后的跟路径
        String rootPath = this.getClass().getResource("/").getPath();
        //转化包名为路径
        String basePackagePath = packagePath.replaceAll("\\.", "\\\\");
        //获取包下所有文件名
        File file = new File(rootPath + "//" + basePackagePath);
        //拿出所有*.class文件的名字
        String[] fileNames = file.list();
        for (String fileName : fileNames) {
    
    
            //替换文件名后缀,拿出类名
            fileName = fileName.replaceAll(".class", "");
            try {
    
    
                //获取对应名字的类对象,注意这个packagePath和basePackagePath是不一样的
                Class clazz = Class.forName(packagePath + "." + fileName);
                //判断这个类是不是bean类,也就是是不是加了自定义注解
                if (clazz.isAnnotationPresent(MyAnno.class)) {
    
    
                    MyAnno anno = (MyAnno) clazz.getAnnotation(MyAnno.class);
                    //把注解的名字和实例对应,存到map里
                    newInstanceMap.put(anno.value(), clazz.newInstance());
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }
    public Object getBean(String beanName) {
    
    
        //从map里拿出对应的对象。
        for (String key : newInstanceMap.keySet()) {
    
    
            if (beanName.equals(key)) {
    
    
                return newInstanceMap.get(key);
            }
        }
        return null;
    }
}

声明一个Service类作为测试,因为我们要传入的是com.demo.service,务必要把Service类建在这个包下,并且加上我们的自定义注解。

@MyAnno("MyAnnoService")
public class UserServiceImpl{
    
    
}

创建Test类测试,运行看看我们自己的注解有没有被初始化。

public class Test {
    
    
    public static void main(String[] args) {
    
    
    	//山寨AnnotationConfigApplicationContext类功能 
        MyAnnoConfigContext myanno=new MyAnnoConfigContext();
        myanno.scan("com.demo.service");
        System.out.println(myanno.getBean("MyAnnoService"));
    }
}
运行结果,正确打印了UserServiceImpl实例对象
com.demo.service.UserServiceImpl@4b1210ee

那么也就完成了对Spring这套代码的模拟。

AnnotationConfigApplicationContext annotation=new AnnotationConfigApplicationContext();
annotation.scan("xx.xx.xx");
annotation.getBean("xxx");

在上面的例子中,如果注销掉@MyAnno("MyAnnoService")则什么都不会取到,说明我们山寨的这一个注解扫描类,确实简单的完成了一个自动注解扫描并注册实例化的过程。

扩展

其实到这里再进一步就是模仿@Autowired,其实原理都是一样的,区别就在于:我们在拿到类名使用clazz.newInstance()构建实例的时候,需要使用Field数组把类中所有的属性都拿出来,然后再次遍历所有的字段找到被@Autowired注解的那些属性,然后找到对应的类型,getType出来,最后再次clazz.newInstance()出来完成实例化。这里已经在【从山寨Spring中学习Spring IOC原理-XML-Constructor】有类似的代码,就不占篇幅了,大家可以自己研究下。

猜你喜欢

转载自blog.csdn.net/Smallc0de/article/details/108341740