前言
上一篇博客【从山寨Spring中学习Spring IOC原理-byType自动装配】完成了在xml中的自动注解的过程,本篇将会挑战一个更加有挑战性的功能,也就是Spring中自动注解功能。更多Spring内容进入【Spring解读系列目录】。
思路
不知道大家有没有注意过,在使用Spring的时候,Spring是如何进行初始化的,换句话说Spring是怎么把交给它管理的类进行 实例化的。这里说下,大致可以分为两种情况:
- 基于XML的:这里注册一个
<bean>
,然后使用ClassPathXmlApplicationContext
把整个xml
加载进来,根据配置的<bean>
完成扫描。 - 基于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】有类似的代码,就不占篇幅了,大家可以自己研究下。