本文共 3479 字,大约阅读时间需要 11 分钟。
上一篇博客完成了在xml中的自动注解的过程,本篇将会挑战一个更加有挑战性的功能,也就是Spring中自动注解功能。更多Spring内容进入。
不知道大家有没有注意过,在使用Spring的时候,Spring是如何进行初始化的,换句话说Spring是怎么把交给它管理的类进行 实例化的。这里说下,大致可以分为两种情况:
<bean>
,然后使用ClassPathXmlApplicationContext
把整个xml
加载进来,根据配置的<bean>
完成扫描。@ComponentScan
,然后使用AnnotationConfigApplicationContext
把这个类加载进来,根据包内的类上的注解完成初始化。但是这里还是有细微的区别的,比如单个类注册在基于Xml的配置中只要<bean>
配置了,就一定会被注册和初始化,也就是说Xml中只要声明就一定会被解析和实例化。而在JavaConfig中,不仅要加了注解,还必须被扫描到才可以被注册和初始化,如果没有扫描到,Spring是不认这个注解的。当然也可以基于注解,但是注解的又必须依赖Xml或者JavaConfig去完成类的扫描才行,所以就不讨论这个了。
为什么要引入这个小知识呢?因为既然是基于注解的,那就必须要引入扫描。也就是说我们要手动完成扫描,然后针对扫描出来的包进行实例化。这个小点就给我们提供了一个山寨的思路,第一步就是想办法去扫描到包,并且拿到里面的类名。但是在编译的过程中我们已经无法找到源码的路径,所以我们此时拿到的包都会是classes
包下的,因此我们此时能拿到的都是*.class
文件,基于此我们把这些文件名找到对应的类(用Calss.forName方法拿出来
)并构造成对象,然后再使用。
既然要山寨自动装配注解,那就山寨的完全一些,首先创造一个注解出来,加上运行时有效。更多自定义注解的内容请参考。
@Retention(RetentionPolicy.RUNTIME)public @interface MyAnno { public String value();}
经过上面的分析,我们就完成了这样的一个类MyAnnoConfigContext
,里面有一个scan
方法并且在这个里面对发现的类进行实例化,和一个getBean
方法用来模拟Spring拿到对应对象的方法。
public class MyAnnoConfigContext { //用来存储创建的实例 MapnewInstanceMap = 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()
出来完成实例化。这里已经在有类似的代码,就不占篇幅了,大家可以自己研究下。
转载地址:http://stmsi.baihongyu.com/