目录
3.自定义用于封装Bean信息的BeanDefinition类:
一、前言
- 第六节内容,我们一起来手动实现一下Spring的底层机制,这有助于加深我们对Spring的理解;包括手动实现Spring容器结构,手动实现IOC依赖注入,手动实现Bean的后置处理器机制,以及手动实现AOP机制等(PS : AOP 底层是基于 BeanPostProcessor 机制的)。
- 注意事项——①代码中的注释也很重要;②不要眼高手低,自己跟着敲一遍才真正有收获;③点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
- PS : up会博文中会展示出所有的源代码,不过考虑到有朋友们想要完整源代码包的需求,up会将代码上传到CSDN 和 Github,没会员可以去Github下载。
- 良工不示人以朴,up所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。感谢阅读!
二、Spring底层整体架构
1.准备工作 :
在IDEA中搭建Maven项目,原型Archetype选择“org.apache.maven.archetypes:maven-archetype-webapp”,建立Maven项目后,在pom.xml配置文件中引入如下依赖 :
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.8</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.8</version> </dependency> <dependency> <groupId>org.dom4j</groupId> <artifactId>dom4j</artifactId> <version>2.1.4</version> </dependency> </dependencies>
建立java, resources, test包,并右键通过 “Mark Directory As” 进行标记,如下图所示:
编辑
接着,up在java包下,建了OrderDAO, OrderService, OrderServlet这几个类,如下图所示 :
编辑
OrderDAO类代码如下 :
package com.cyan.spring.component; import org.springframework.stereotype.Component; public class OrderDAO { public void saveOrder() { System.out.println("保存订单~"); } }
OrderService类代码如下 :
package com.cyan.spring.component; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; public class OrderService { private OrderDAO orderDAO; public void saveOrder() { orderDAO.saveOrder(); } }
OrderServlet类代码如下 :
package com.cyan.spring.component; import org.springframework.stereotype.Component; public class OrderServlet { }
然后,up在resources目录下建立一个beans.xml配置文件,代码如下 :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置自动扫描 --> <context:component-scan base-package="com.cyan.spring.component"/> </beans>
PS : 在IDEA中,如果是Java项目,beans.xml文件要放在src目录下;而在Maven项目中,需要放在resources目录下。
接着,我们新建一个spring.test包,并在该包下新建一个类AppMain,如下图所示 :
编辑
AppMain类代码如下 :
package com.cyan.spring.test; import com.cyan.spring.component.OrderDAO; import com.cyan.spring.component.OrderService; import com.cyan.spring.component.OrderServlet; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author : Cyan_RA9 * @version : 21.0 */ public class AppMain { public static void main(String[] args) { ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml"); OrderServlet orderServlet = ioc.getBean("orderServlet", OrderServlet.class); OrderServlet orderServlet2 = ioc.getBean("orderServlet", OrderServlet.class); OrderService orderService = ioc.getBean("orderService", OrderService.class); OrderDAO orderDAO = ioc.getBean("orderDAO", OrderDAO.class); System.out.println("orderServlet = " + orderServlet); System.out.println("orderServlet2 = " + orderServlet2); System.out.println("orderService = " + orderService); System.out.println("orderDAO = " + orderDAO); } }
运行结果 :
编辑
可以看到,在默认情况下,我们配置的@Component, @Controller, @Service, @Repository组件都是单例的。
成功运行后,我们可以看到实际的工作目录target/classes下,可以找到beans.xml文件,如下图所示 :
编辑
2.架构分析 : (重要)
Spring整体底层架构如下图所示 : (从上至下,从左到右)
编辑
3.环境搭建 :
在上文“准备工作”创建的Maven项目的基础上,新建一个并行的模块Module(也可以不这么做, whatever),选择同样的Maven原型,只不过Parent为None,然后引入同样的maven依赖,并创建beans.xml配置文件。如下图所示 :
编辑
创建这个新的模块的目的是为了有一个干净的环境,不会被之前创建的包干扰(当然,你就在原来那个模块下写也是可以的)。
接着,我们右键选择“Open Module Settings”,把两个模块的Language level都修改为17(注意:修改pom.xml配置文件中的依赖,会引起此处Language level的变化,需要再手动修改回来),如下图所示 :
编辑
另外,在"Settings-->Build,Execution,Deployment-->Compiler-->Java Compiler"中,将右侧的version也改成17,如下图所示 :
编辑
三、手动实现Spring容器结构
1.自定义注解 :
1.1 @Component注解
在annotation包下自定义一个Component注解,用于模拟Spring原生的"@Component"注解,该注解可以标注一个类为Bean组件。代码如下 :
package com.cyan.spring.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** 自定义的Component注解,用于模拟Spring原生的"@Component"注解, 该自定义注解也是用于标注组件。 */ ElementType.TYPE) (RetentionPolicy.RUNTIME) (public @interface Component { String value() default ""; }
1.2 @Scope注解
在annotation包下自定义一个Scope注解,用于模拟Spring原生的"@Scope"注解,该注解可以标注一个Bean为单例还是多例(singleton OR prototype)。代码如下 :
package com.cyan.spring.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** 自定义的Scope注解,用于模拟Spring原生的"@Scope"注解, 该自定义注解也是用于标注Bean为单例还是多例。 */ ElementType.TYPE) (RetentionPolicy.RUNTIME) (public @interface Scope { String value() default ""; }
2.自定义组件 :
在component包下创建OrderService和OrderDAO两个类,表示待注入的组件,并用我们自定义的@Component注解和自定义的@Scope注解进行标注。(假设OrderService为单例singleton,而OrderDAO为多例prototype)
OrderService类代码如下 :
package com.cyan.spring.component; import com.cyan.spring.annotation.Component; import com.cyan.spring.annotation.Scope; /** OrderService用了自定义的@Component标注,表示这是一个组件,会被注入到IOC容器中。 OrderService用了自定义的@Scope标注,表明了OrderService为单例singleton。 */ value = "orderService") (value = "singleton") (public class OrderService { }
OrderDAO类代码如下 :
package com.cyan.spring.component; import com.cyan.spring.annotation.Component; import com.cyan.spring.annotation.Scope; /** OrderDAO也用了自定义的@Component标注,表示这是一个组件,会被注入到IOC容器中。 OrderDAO用了自定义的@Scope标注,表明了OrderDAO为多例prototype。 */ value = "orderDAO") (value = "prototype") (public class OrderDAO { }
假设com.cyan.spring.component包就是我们要扫描的包,beans.xml配置文件代码如下 :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置自动扫描 --> <context:component-scan base-package="com.cyan.spring.component"/> </beans>
3.自定义用于封装Bean信息的BeanDefinition类:
通过上文“Spring底层架构分析图”我们可以得知——通过Dom4J解析beans.xml后,我们不能直接将获取到的Bean信息封装到Map容器中,而是应该先将需要的Bean的信息封装在一个BeanDefinition对象中,再将该BeanDefinition对象保存到IOC容器维护的Map容器中。BeanDefinition类代码如下 :
package com.cyan.spring.ioc; /** 该类对象用于封装Bean的相关信息,且对象最终会存放在自定义的IOC容器中。 */ public class BeanDefinition { private String scope; //Bean的单例多例情况 private Class<?> clazz; //Bean的字节码文件对象 public BeanDefinition() { } public BeanDefinition(String scope, Class<?> clazz) { this.scope = scope; this.clazz = clazz; } public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } public Class<?> getClazz() { return clazz; } public void setClazz(Class<?> clazz) { this.clazz = clazz; } public String toString() { return "BeanDefinition{" + "scope='" + scope + '\'' + ", clazz=" + clazz + '}'; } }
4.自定义IOC容器 :
在自定义的IOC容器中,首先我们肯定得获取到被扫描的包的信息,这里up使用Dom4j来解析target/classes目录下的beans.xml配置文件,通过DOM操作来获取到被扫描的包,这一点和我们在“Spring IOC—基于注解配置和管理Bean”一文中手动实现Spring注解配置机制有所不同,但我们在“手写Tomcat底层机制”中已经有过“使用DOM4j解析XML配置文件”的经验了,不熟悉的朋友可以先去快速看一下up之前写的源码。
得到被扫描的包后,后续步骤同我们之前写的手动实现Springt注解配置机制类似,即遍历该包下的.class文件,然后得到每个资源的Class对象。我们需要对每个Class对象进行判断,如果它是一个Bean类型,就要将Bean的配置信息封装到BeanDefinition对象中,并将BeanDefinition对象保存在IOC容器维护的Map容器中。这是初始化IOC容器的第一步,beanDefinitionMap初始化完毕后,还需要初始化单例池singletonObjects。
记住,beanDefinitionMap的初始化是单例池初始化的前提,因为单例池需要根据beanDefinitionMap中的信息来创建Bean实例。当然,基于OOP的思想,“初始化beanDefinitionMap”,“初始化singletonObjects”,以及“反射创建Bean实例”的代码我们都可以分别封装在方法中,然后在自定义的IOC容器的构造器中调用需要的方法即可。
当初始化IOC容器的任务完成后,我们就需要定义自己的getBean(String id)方法,正如我们在上文“Spring整体底层架构分析图”中描绘的那样,我们在getBean(..)方法中需要先判断传入的beanName是否是beanDefinitionMap中的一个key。若不存在,可以考虑直接throw一个NullPointerException异常对象;若存在,继续判断它是单例还是多例,如果是singleton单例,就直接从单例池中获取实例,并返回,如果是prototype多例,就调用已经封装好的createBean(BeanDefinition beanDefinition)方法来反射创建新的对象,并返回。
CyanApplicationContext类代码如下 :
package com.cyan.spring.ioc; import com.cyan.spring.annotation.Autowired; import com.cyan.spring.annotation.Component; import com.cyan.spring.annotation.Scope; import com.cyan.spring.component.OrderDAO; import com.cyan.spring.component.OrderService; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.springframework.util.StringUtils; import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; /** * @author : Cyan_RA9 * @version : 21.0 */ public class CyanApplicationContext { //scanPath属性用于暂时保存要扫描的包的路径(由Dom4j解析得到) private String scanPath; //beanDefinitionMap属性用于存放Bean的配置信息(以对象的形式存放) private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); //singletonObjects属性用于存放创建的Bean对象/实例(单例池) private final ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); public CyanApplicationContext() { //初始化BeanDefinitionMap this.initBeanDefinitionMap(); //初始化单例池 this.initSingletonObjects(this.beanDefinitionMap); System.out.println("beanDefinitionMap = " + beanDefinitionMap); System.out.println("singletonObjects = " + singletonObjects); } //初始化BeanDefinitionMap的方法 private void initBeanDefinitionMap() { //1.通过DOM4j来解析target/classes目录下的beans.xml配置文件 //1.1 获取beans.xml的实际工作路径path String path = CyanApplicationContext.class.getResource("/").getPath(); //path = /D:/JAVA/IDEA/IntelliJ_IDEA/javaProject/SSM/cyanSpring_selfDef/target/classes/ SAXReader saxReader = new SAXReader(); try { //1.2 获取Document文档对象(注意文件名) Document document = saxReader.read(new File(path + "beans.xml")); System.out.println("document = " + document); //1.3 获取beans.xml配置文件的根元素beans Element rootElement = document.getRootElement(); //1.4 获取根元素下的所有子元素(context:component-scan元素) List<Element> elements = rootElement.elements(); //2.遍历并判断获得的元素,得到扫描包的包名 for (Element element : elements) { if ("component-scan".equals(element.getName())) { scanPath = element.attributeValue("base-package"); //scanPath = com.cyan.spring.component } } } catch (DocumentException e) { throw new RuntimeException(e); } //3.将包名转换为路径格式 scanPath = scanPath.replace(".", "/"); System.out.println("scanPath = " + scanPath); //4.获取App类加载器,以得到真实的工作路径(资源目录)————target/classes下的.class文件 ClassLoader classLoader = CyanApplicationContext.class.getClassLoader(); URL resource = classLoader.getResource(scanPath); System.out.println("resource = " + resource); //5.遍历目标包下的所有资源(.class文件) //5.1 首先获取到该包对应的File File scanPackage = new File(resource.getFile()); /* PS : 注意路径中不要有特殊字符,比如空格,否则会判断不是目录, 报错NullPointerException : Cannot read the array length because "<local8>" is null. */ //5.2 判断该File是不是一个目录 if (scanPackage.isDirectory()) { //5.3 获取被扫描的包下的所有文件/资源(.class) File[] files = scanPackage.listFiles(); //5.4 使用增强for对所有.class文件进行遍历 for (File file : files) { System.out.println("==========================================="); //D:\JAVA\IDEA\IntelliJ_IDEA\javaProject\SSM\cyanSpring_selfDef\target\classes\com\cyan\spring\component\OrderDAO.class System.out.println(file.getAbsolutePath()); //5.5 对资源的绝对路径做接收 String fileAbsolutePath = file.getAbsolutePath(); //5.6 判断是否为.class文件 if (fileAbsolutePath.endsWith(".class")) { //5.7 得到用于反射的全类名 //先获取到包名(反转) scanPath = scanPath.replace("/", "."); //再获取到类名(此处两个右斜杠表示转义) String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class")); //最后得到反射所需的全类名(全类名 = 包名 + 类名; 用于反射) String fullClassName = scanPath + "." + className; //5.8 反射获取Class对象 try { Class<?> clazz = classLoader.loadClass(fullClassName); //判断是否该类是否需要被实例化(是否被特定注解标识) if (clazz.isAnnotationPresent(Component.class)) { //若被自定义的@Component注解修饰,说明它是一个组件,是Bean类型,需要被注入 //6.将Bean信息封装到BeanDefinition对象中 //6.1 定义一个BeanDefinition对象 BeanDefinition beanDefinition = new BeanDefinition(); //6.2 获得Bean的beanName /* "Declared"表示不包括从父类继承来的注解, 该方法只有在Java8中才可以使用。 */ Component component = clazz.getDeclaredAnnotation(Component.class); String beanName = component.value(); //若没有指定bean的id值,默认以类名首字母小写作为id if ("".equals(beanName)) { beanName = StringUtils.uncapitalize(className); /* 若不想使用SpringFramework的工具类,可以如下写: className.toLowerCase().substring(0,1) + className.substring(1); */ } //6.3 获得Bean的Scope值 if (clazz.isAnnotationPresent(Scope.class)) { Scope scope = clazz.getAnnotation(Scope.class); String scopeValue = scope.value(); beanDefinition.setScope(scopeValue); } else { //若没有使用@Scope注解标识,默认为单例 beanDefinition.setScope("singleton"); } //6.4 将Bean的beanName, 以及Scope值和Class对象进行封装 beanDefinition.setClazz(clazz); //7. 将封装好的BeanDefinition对象保存到Map容器中 beanDefinitionMap.put(beanName, beanDefinition); } else { System.out.println("Sorry, this isn't a bean!"); } } catch (Exception e) { throw new RuntimeException(e); } } } } } //初始化单例池的方法(注意形参列表中泛型的使用) private void initSingletonObjects(ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap) { //遍历枚举类型 //PS : asIterator()方法只有在Java9之后才能使用。 //keys是所有的beanName Enumeration<String> keys = beanDefinitionMap.keys(); Iterator<String> iterator = keys.asIterator(); String beanName = null; BeanDefinition beanDefinition = null; //通过迭代器进行遍历 while (iterator.hasNext()) { if ((beanName = iterator.next()) != null) { //若BeanDefinitionMap中存在该beanName,就获取到对应的BeanDefinition对象。 beanDefinition = beanDefinitionMap.get(beanName); /* 此处实际不需要Objects的静态方法, 因为ConcurrentHashMap是线程安全的,不支持null键或null值, 此处使用仅为了回顾。 */ if (Objects.requireNonNull(beanDefinition) != null) { String scope = beanDefinition.getScope(); if ("singleton".equalsIgnoreCase(scope)) { //调用已经定义好的实例化Bean的方法 Object instance = this.createBean(beanDefinition); singletonObjects.put(beanName, instance); } } } } } /* 自定义createBean(...)方法,用于根据传入的BeanDefinition对象,创建Bean对象. 不管是实例化单例池,还是多例Bean的懒加载,底层都是通过反射来创建Bean对象。 */ private Object createBean(BeanDefinition beanDefinition) { Class<?> clazz = beanDefinition.getClazz(); //若反射过程中出现异常,默认返回null Object instance = null; try { Constructor<?> constructor = clazz.getConstructor(); instance = constructor.newInstance(); /**依赖注入*/ //1.首先通过Class对象获取到所有Field字段。 Field[] fields = clazz.getDeclaredFields(); //2.遍历所有Filed字段 for (Field field : fields) { //3.判断哪些字段用了自定义的@Autowired注解修饰 if (field.isAnnotationPresent(Autowired.class)) { //required属性默认为true. if (field.getAnnotation(Autowired.class).required() == true) { String fieldName = field.getName(); System.out.println("fieldName = " + fieldName); //4.根据字段名,获取Bean对象 Object bean = this.getBean(fieldName); //5.通过set(Object obj, Object value)方法为instance实例注入依赖 //开启暴力反射 field.setAccessible(true); field.set(instance, bean); } } } } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } return instance; } //自定义getBean(...)方法,通过id返回Bean对象 public Object getBean(String id) { BeanDefinition beanDefinition = beanDefinitionMap.get(id); //判断beanDefinitionMap中是否存在该key if (beanDefinitionMap.containsKey(id)) { String scope = beanDefinition.getScope(); if ("singleton".equalsIgnoreCase(scope)) { return singletonObjects.get(id); } else { //每次反射创建对象,都是一个新的对象,符合“多例”的特点。 Object bean = createBean(beanDefinition); return bean; } } else { throw new NullPointerException("No such bean exists in the beanDefinitionMap!"); } } }
5.运行测试 :
在test包下新建一个AppMain类用于测试,如下图所示 :
编辑
在AppMain类中我们通过getBean方法分别获取3次OrderService实例和3次OrderDAO实例,由于我们配置的@Scope中,OrderService为单例,OrderDAO为多例,所以预期结果应该是——3次获取到的OrderService实例是同一个Bean对象,而3次获取到的OrderDAO实例是三个不同的Bean对象。
AppMain类代码如下 :
import com.cyan.spring.component.OrderDAO; import com.cyan.spring.component.OrderService; import com.cyan.spring.ioc.CyanApplicationContext; /** * @author : Cyan_RA9 * @version : 21.0 */ public class AppMain { public static void main(String[] args) { CyanApplicationContext cyanApplicationContext = new CyanApplicationContext(); OrderService orderService1 = (OrderService) cyanApplicationContext.getBean("orderService"); OrderService orderService2 = (OrderService) cyanApplicationContext.getBean("orderService"); OrderService orderService3 = (OrderService) cyanApplicationContext.getBean("orderService"); OrderDAO orderDAO1 = (OrderDAO) cyanApplicationContext.getBean("orderDAO"); OrderDAO orderDAO2 = (OrderDAO) cyanApplicationContext.getBean("orderDAO"); OrderDAO orderDAO3 = (OrderDAO) cyanApplicationContext.getBean("orderDAO"); System.out.println("orderService1 = " + orderService1); System.out.println("orderService2 = " + orderService2); System.out.println("orderService3 = " + orderService3); System.out.println("orderDAO1 = " + orderDAO1); System.out.println("orderDAO2 = " + orderDAO2); System.out.println("orderDAO3 = " + orderDAO3); } }
运行结果 :
编辑
四、手动实现Spring依赖注入
1.准备工作 :
首先,回顾一下,我们曾在“Spring IOC—基于注解配置和管理Bean”中简单了解了“泛型依赖注入”,其实就是某一个组件的属性是另一个组件的类型,从而产生了依赖关系。同样地,我们仍要通过@Autowired注解来实现,只不过这里的@Autowired注解不再是Spring原生的了,而是我们自定义的“@Autowired”注解。
自定义的@Autowired注解类代码如下 :
package com.cyan.spring.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** 自定义的@Autowired注解,用于实现自动装配。 */ ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) ({RetentionPolicy.RUNTIME) (public @interface Autowired { boolean required() default true; }
为了验证我们自定义的@Autowired注解成功实现自动装配,我们在OrderDAO类中定义一个方法,OrderDAO类代码如下 :
package com.cyan.spring.component; import com.cyan.spring.annotation.Component; import com.cyan.spring.annotation.Scope; /** OrderDAO也用了自定义的@Component标注,表示这是一个组件,会被注入到IOC容器中。 OrderDAO用了自定义的@Scope标注,表明了OrderDAO为多例prototype。 */ value = "orderDAO") (value = "prototype") (public class OrderDAO { public void save() { System.out.println("OrderDAO's save() is invoked~"); } }
同样地,OrderService类中要维护有一个OrderDAO类型的属性,该属性使用自定义的@Autowired注解修饰;在OrderService类中也定义一个save()方法,通过orderDAO属性调用。代码如下 :
package com.cyan.spring.component; import com.cyan.spring.annotation.Autowired; import com.cyan.spring.annotation.Component; import com.cyan.spring.annotation.Scope; /** OrderService用了自定义的@Component标注,表示这是一个组件,会被注入到IOC容器中。 OrderService用了自定义的@Scope标注,表明了OrderService为单例singleton。 */ value = "orderService") (value = "singleton") (public class OrderService { //使用自定义的Autowired注解 required = true) ( private OrderDAO orderDAO; public void save() { orderDAO.save(); } }
2.代码实现 :
其实我们在上文“自定义IOC容器”中,已经实现了依赖注入,就在我们封装好的createBean(BeanDefinition beanDefinition)方法中,需要注意一点,由于OrderService类维护的OrderDAO属性是被private关键字修饰的,即是私有的,所以我们在装配前,一定要先调用setAccessible(true)方法实现暴力反射。如下图所示 :
编辑
3.运行测试 :
在测试类AppMain中,我们获取OrderService实例,并调用save()方法,检测OrderDAO实例的save()方法是否被调用,若成功调用,则说明“通过自动装配实现依赖注入”实现成功。AppMain类代码如下 :
package com.cyan.spring.test; import com.cyan.spring.component.OrderService; import com.cyan.spring.ioc.CyanApplicationContext; /** * @author : Cyan_RA9 * @version : 21.0 */ public class AppMain { public static void main(String[] args) { CyanApplicationContext cyanApplicationContext = new CyanApplicationContext(); OrderService orderService1 = (OrderService) cyanApplicationContext.getBean("orderService"); System.out.println("orderService1 = " + orderService1); orderService1.save(); } }
运行结果 :
编辑
五、手动实现Spring后置处理器
1.实现思路 :
我们先来简单回顾一下原生Spring的后置处理器,那么要回顾后置处理器,就必须先回顾一下Bean的生命周期,如下图所示 :
编辑
我们知道,在原生Spring框架中,后置处理器的本质是一个实现了BeanPostProcessor接口的Java类,而这个实现类中重写了BeanPostProcessor接口中的两个方法,分别为postProcessBeforeInitialization(...) 和 postProcessAfterInitialization(...)。这两个方法允许该后置处理器在Bean的初始化方法(即配置Bean时init-method属性所指定的初始化方法)调用前 和 调用后进行相应的业务处理。
那么,既然我们想自己模拟实现Spring的后置处理器机制,首先就要解决一个问题——初始化Bean的方法从哪儿来?
因为在原生Spring框架中,我们是可以直接在beans.xml配置文件中为Bean指定初始化方法的,但是现在我们的beans.xml配置文件仅仅起到“指明扫描包”的作用,因为我们是手动模拟呀,当然要自己想办法弄一个Bean的初始化方法。诶,我们发现在原生Spring框架中,有这么一个接口,叫InitializingBean,如下图所示 :
编辑
可以看到,接口中有个“afterPropertiesSet()”方法,嗯?这个方法不就是“在属性注入之后”的意思吗?噢~,想想我们的Bean的生命周期,属性注入之后,那不就是Bean的初始化方法嘛。所以,可以想到,如果我们能自己模拟一个InitializingBean接口,岂不是可以利用afterPropertiesSet()方法来充当Bean的初始化方法?
噢,这么一来,我们只需要让JavaBean去实现模拟的InitializingBean接口,并重写afterPropertiesSet()方法,Bean实例便有了自己的初始化方法。
那儿,有了初始化方法后,我们是不是得考虑,上哪儿去调用这个初始化方法呢?嗯?那当然是我们之前定义的IOC容器类CyanApplicationContext了。在我们自定义的容器类中,我们不是封装了一个createBean(BeanDefinition beanDefinition)方法吗,在这个方法中我们反射创建了Bean实例,并且实现了依赖注入。那我们可以在创建好Bean实例后,判断是否需要调用初始化Bean的方法。(PS : 在容器中,我们常常根据一个类是否实现了某个接口,来判断是否要执行相应的某个业务逻辑,这其实就是Java接口编程的实际运用。实际开发中,我们有时会让类去实现一个什么方法都没有定义的接口,称为“标记接口”,其实就是利用接口编程实现业务。)
这还没完,要知道,原生的Spring的后置处理器是要在Bean的初始化方法调用前和调用后,分别调用postProcessBeforeInitialization(...) 和 postProcessAfterInitialization(...)方法。那么到现在为止,我们仅仅是解决了Bean的初始化方法的问题,剩下的才是重头戏。我们还需要自定义一个BeanPostProcessor接口,并在其中定义postProcessBeforeInitialization(...) 和 postProcessAfterInitialization(...)方法,从而让接口的实现类(自定义的后置处理器)去重写这两个方法。显然,接口的实现类需要用@Component注解修饰,表明这是一个组件,即Bean的后置处理器;它会和其他单例Bean一样,在容器启动时被实例化并放入到单例池中保存。So,我们只需要设法从单例池中取出所有的后置处理器对象,并在createBean方法的合适位置调用接口中的方法即可。
2.代码实现 :
首先,up新建一个processor包,并在改包下定义一个自己的InitializingBean接口,如下图所示 :
编辑
InitializingBean接口代码如下 :
package com.cyan.spring.processor; /** 自定义的InitializingBean接口, afterPropertiesSet()方法用于充当Bean的初始化方法。 */ public interface InitializingBean { void afterPropertiesSet() throws Exception; }
接着,我们让OrderService 去实现这个自定义接口,并重写afterPropertiesSet()方法。(此处仅让OrderService去实现接口,是为了在IOC容器中进行判断测试,即判断Bean是否定义了初始化方法。)
OrderService类代码如下 :
package com.cyan.spring.component; import com.cyan.spring.annotation.Autowired; import com.cyan.spring.annotation.Component; import com.cyan.spring.annotation.Scope; import com.cyan.spring.processor.InitializingBean; /** OrderService用了自定义的@Component标注,表示这是一个组件,会被注入到IOC容器中。 OrderService用了自定义的@Scope标注,表明了OrderService为单例singleton。 */ value = "orderService") (value = "singleton") (public class OrderService implements InitializingBean { //使用自定义的Autowired注解 required = true) ( private OrderDAO orderDAO; public void save() { orderDAO.save(); } public void afterPropertiesSet() throws Exception { System.out.println("OrderService's init-method is invoked~"); } }
在IOC容器类中,我们要在之前封装好的createBean(..)方法中进行判断,若当前Bean定义了初始化方法,就进行调用,如下图所示 :
编辑
继续,仍是在processor包下,自定义BeanPostProcessor接口,代码如下 :
package com.cyan.spring.processor; /** 自定义的BeanPostProcessor接口 */ public interface BeanPostProcessor { default Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; } default Object postProcessAfterInitialization(Object bean, String beanName) { return bean; } }
接着,在component包下,定义自己的后置处理器实现类,CyanBeanPostProcessor类代码如下 : (要注意后置处理器也是一个组件)
package com.cyan.spring.component; import com.cyan.spring.annotation.Component; import com.cyan.spring.processor.BeanPostProcessor; /** * @author : Cyan_RA9 * @version : 21.0 */ public class CyanBeanPostProcessor implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("postProcessBeforeInitialization方法被调用~ " + ", bean=" + bean.getClass() + ", beanName=" + beanName); return null; } public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("postProcessorAfterInitialization方法被调用~ " + ", bean=" + bean.getClass() + ", beanName=" + beanName); return null; } }
继续,我们在IOC容器类中定义一个方法,用于取出单例池中所有的后置处理器对象,并返回,代码如下 :
//定义一个方法,用于从单例池中取出Bean后置处理器对象 private List<BeanPostProcessor> getBeanPostProcessors() { List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>(); for (Object singletonKey : this.singletonObjects.keySet()) { Object o = this.singletonObjects.get(singletonKey); //instanceof关键字回顾————实际参与判断的是引用变量指向的——堆空间中真正的对象 if (o instanceof BeanPostProcessor) { beanPostProcessorList.add((BeanPostProcessor) o); } } return beanPostProcessorList; }
有了这个方法,我们便可以在createBean(..)方法中去调用后置处理器对象,createBean(..)方法代码如下 : (注意,相比之前,形参增加了String beanName)
/* 自定义createBean(...)方法,用于根据传入的BeanDefinition对象,创建Bean对象. 不管是实例化单例池,还是多例Bean的懒加载,底层都是通过反射来创建Bean对象。 */ private Object createBean(String beanName, BeanDefinition beanDefinition) { Class<?> clazz = beanDefinition.getClazz(); //若反射过程中出现异常,默认返回null Object instance = null; try { Constructor<?> constructor = clazz.getConstructor(); instance = constructor.newInstance(); /**依赖注入*/ //1.首先通过Class对象获取到所有Field字段。 Field[] fields = clazz.getDeclaredFields(); //2.遍历所有Filed字段 for (Field field : fields) { //3.判断哪些字段用了自定义的@Autowired注解修饰 if (field.isAnnotationPresent(Autowired.class)) { //required属性默认为true. if (field.getAnnotation(Autowired.class).required() == true) { String fieldName = field.getName(); //4.根据字段名,获取Bean对象 Object bean = this.getBean(fieldName); //5.通过set(Object obj, Object value)方法为instance实例注入依赖 //开启暴力反射 field.setAccessible(true); field.set(instance, bean); } } } } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } /** 调用Bean后置处理器的postProcessBeforeInitialization(..)方法 */ //回顾———此处的Map.Entry实际为Map.Node类型。 for (BeanPostProcessor beanPostProcessor : this.getBeanPostProcessors()) { Object current = beanPostProcessor.postProcessBeforeInitialization(instance, beanName); if (null != current) { instance = current; } } /** 判断是否调用Bean的初始化方法 * 实际参与判断的是引用变量指向的——堆空间中真正的对象 */ if (instance instanceof InitializingBean) { //多态 try { ((InitializingBean) instance).afterPropertiesSet(); } catch (Exception e) { throw new RuntimeException(e); } } /** 调用Bean后置处理器的postProcessAfterInitialization(..)方法 */ //回顾———此处的Map.Entry实际为Map.Node类型。 for (BeanPostProcessor beanPostProcessor : this.getBeanPostProcessors()) { Object current = beanPostProcessor.postProcessAfterInitialization(instance, beanName); if (null != current) { instance = current; } } return instance; }
3.运行测试 :
我们在测试类中AppMain中只保留启动容器的代码,如下图所示 :
编辑
运行结果 :
编辑
可以看到,在OrderService的初始化方法调用前和调用后,分别调用了Bean的后置处理器的postProcessBeforeInitialization方法和postProcessAfterInitialization方法。需要注意的是,我们之前配置的OrderDAO是"prototype"多例,按道理讲,它在容器初始化的时候,根本就不会进入单例池中,而且我们也没有在测试类中调用getBean方法,那为什么运行结果会显示后置处理器也作用于OrderDAO呢?
其实,这是因为我们上文已经实现的“依赖注入”,即下图所示 :
编辑
由于OrderService的属性使用了@Autowired注解修饰,所以此处会进行Bean的自动装配,从而间接调用了getBean方法,而又因为OrderDAO是多例prototype,所以在getBean方法中又调用了createBean方法,从而触发了Bean的后置处理器。
六、手动实现SpringAOP机制
1.准备工作 :
首先,在annotation包下,我们当然需要定义用于标记AOP切面类的@Aspect注解,代码如下 :
package com.cyan.spring.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义的@Aspect注解,用于标识切面类。 */ RetentionPolicy.RUNTIME) (ElementType.TYPE}) ({public @interface Aspect { String value() default ""; }
其次,还需要定义用于实现各种通知的注解,up这里只定义了@Before注解 和 @AfterReturning注解,用于实现前置通知 和 返回通知。@Before注解代码如下 :
package com.cyan.spring.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义的@Before注解,用于实现前置通知。 */ RetentionPolicy.RUNTIME) (ElementType.METHOD}) ({public @interface Before { String value(); String argNames() default ""; }
@AfterReturning注解代码如下 :
package com.cyan.spring.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** 自定义的@AfterReturning注解,用于实现返回通知。 */ RetentionPolicy.RUNTIME) (ElementType.METHOD}) ({public @interface AfterReturning { String value() default ""; String pointcut() default ""; String returning() default ""; String argNames() default ""; }
在component包下,新建Calculator接口,以及一个它的实现类Calculator_Demo1。
Calculator接口代码如下 :
package com.cyan.spring.component; public interface Calculator { public abstract double add(double n1, double n2); public abstract double subtract(double n1, double n2); public abstract double multiply(double n1, double n2); public abstract double divide(double n1, double n2); }
Calculator_Demo1类代码如下 : (注意,实现类是一个组件,因此需要用@Component注解修饰)
package com.cyan.spring.component; import com.cyan.spring.annotation.Component; public class Calculator_Demo1 implements Calculator { public double add(double n1, double n2) { double result = n1 + n2; System.out.println("result = " + result); return result; } public double subtract(double n1, double n2) { double result = n1 - n2; System.out.println("result = " + result); return result; } public double multiply(double n1, double n2) { double result = n1 * n2; System.out.println("result = " + result); return result; } public double divide(double n1, double n2) { double result = -1; if (n2 != 0) { //分母不允许为0 result = n1 / n2; } System.out.println("result = " + result); return result; } }
2.代码实现 :
定义切面类CalculatorAspect,用我们自定义的@Aspect注解和@Component注解修饰,并在其中定义用于前置通知和返回通知的方法,代码如下 :
package com.cyan.spring.component; import com.cyan.spring.annotation.AfterReturning; import com.cyan.spring.annotation.Aspect; import com.cyan.spring.annotation.Before; import com.cyan.spring.annotation.Component; /** 切面类CalculatorAspect,用于实现AOP切入表达式 */ public class CalculatorAspect { //1.前置通知 value = "execution(public double com.cyan.spring.component.Calculator_Demo1.add(double, double))") ( public void beforeAdvice() { System.out.println("--------------------前置通知~~"); } //2.返回通知 value = "execution(public double com.cyan.spring.component.Calculator_Demo1.add(double, double))") ( public void afterReturningAdvice() { System.out.println("--------------------返回通知~~"); } }
之前我们说,"Bean的后置处理器"机制是"AOP"机制的底层支撑,现在我们就将深刻体会这一点。我们需要在CyanBeanPostProcessor的postProcessAfterInitialization(..)方法中做点手脚。
首先,如果判断当前Bean是一个切面类,我们就必须获取到该切面类中定义的各种通知方法以及需要被切入的方法(即配置的切入点表达式),并且我们还得把这种映射关系保存到一个Map集合中,保存到Map集合中有什么用呢?诶,别急。
如果判断当前Bean是一个需要被代理的对象,就利用JDK的Proxy动态代理,返回一个代理对象,注意,在匿名内部重写的invoke方法中,我们便可以利用之前已经保存的Map集合,来反射调用前置通知的方法和返回通知的方法。
CyanBeanPostProcessor类代码如下 :
package com.cyan.spring.component; import com.cyan.spring.annotation.AfterReturning; import com.cyan.spring.annotation.Aspect; import com.cyan.spring.annotation.Before; import com.cyan.spring.annotation.Component; import com.cyan.spring.processor.BeanPostProcessor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.concurrent.ConcurrentHashMap; /** * @author : Cyan_RA9 * @version : 21.0 */ public class CyanBeanPostProcessor implements BeanPostProcessor { //定义一个Map属性用于保存切入点表达式 //key——String : 接口实现类需要被切入的方法的name + 空格 + 对应的通知类型 //value——Method : 切面类定义的切入方法 private ConcurrentHashMap<String, Method> pointcutMap = new ConcurrentHashMap<>(); //定义一个CalculatorAspect类对象,用于invoke方法执行时作为参数传入。 private CalculatorAspect calculatorAspect = new CalculatorAspect(); public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("postProcessBeforeInitialization方法被调用~ " + ", bean=" + bean.getClass() + ", beanName=" + beanName); return null; } public Object postProcessAfterInitialization(Object bean, String beanName) throws NoSuchMethodException { System.out.println("postProcessorAfterInitialization方法被调用~ " + ", bean=" + bean.getClass() + ", beanName=" + beanName); /**如果当前Bean是一个切面类,就设法获取到切入点表达式。*/ if (bean.getClass().isAnnotationPresent(Aspect.class)) { //1.获取到切面类的Class对象 Class<CalculatorAspect> calculatorAspectClass = CalculatorAspect.class; //2.遍历切面类的所有方法 Method[] declaredMethods = calculatorAspectClass.getDeclaredMethods(); for (Method method : declaredMethods) { //3.遍历过程中,拿到切入点表达式的value值 if (method.isAnnotationPresent(Before.class)) { Before before = method.getAnnotation(Before.class); String value = before.value(); String pointcutMethodName = value.substring(value.lastIndexOf(".") + 1, value.lastIndexOf("(")); //Δ 你要是不知道通知类型,怎么执行各种通知? //所以必须一并保存 String key = pointcutMethodName + " Before"; Method declaredMethod = calculatorAspectClass.getDeclaredMethod(method.getName()); //4.将被切入的方法名(以及对应的通知类型) 和 用于执行前置通知的方法的映射保存到Map中。 pointcutMap.put(key, declaredMethod); //CalculatorAspect calculatorAspect = calculatorAspectClass.getConstructor().newInstance(); //method.invoke(calculatorAspect); } else if (method.isAnnotationPresent(AfterReturning.class)) { AfterReturning afterReturning = method.getAnnotation(AfterReturning.class); String value = afterReturning.value(); String pointcutMethodName = value.substring(value.lastIndexOf(".") + 1, value.lastIndexOf("(")); String key = pointcutMethodName + " AfterReturning"; Method declaredMethod = calculatorAspectClass.getDeclaredMethod(method.getName()); pointcutMap.put(key, declaredMethod); //CalculatorAspect calculatorAspect = calculatorAspectClass.getConstructor().newInstance(); //method.invoke(calculatorAspect); } } } /** AOP——使用JDK的Proxy动态代理,返回一个代理对象(针对于接口的实现类) */ //判断当前bean是否需要被代理 if (bean instanceof Calculator) { //1.获取newProxyInstance方法的第一个参数———类加载器 ClassLoader classLoader = CyanBeanPostProcessor.class.getClassLoader(); //2.获取newProxyInstance方法的第二个参数———接口信息 Class<?>[] interfaces = bean.getClass().getInterfaces(); //3.获取newProxyInstance方法的第三个参数———处理器对象(通过匿名内部类来实现) InvocationHandler invocationHandler = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; if ("add".equalsIgnoreCase(method.getName())) { /** 前置通知*/ //PS : 匿名内部类中不能使用this关键字。 for (String key : CyanBeanPostProcessor.this.pointcutMap.keySet()) { String[] names = key.split(" "); if (names[1].equals("Before")) { Method before = CyanBeanPostProcessor.this.pointcutMap.get(key); before.invoke(calculatorAspect); } } //反射调用接口实现类中的方法 result = method.invoke(bean, args); /** 返回通知*/ for (String key : CyanBeanPostProcessor.this.pointcutMap.keySet()) { String[] names = key.split(" "); if (names[1].equals("AfterReturning")) { Method before = CyanBeanPostProcessor.this.pointcutMap.get(key); before.invoke(calculatorAspect); } } } else { //如果不是目标对象,直接执行方法 result = method.invoke(bean, args); } return result; } }; //若当前Bean符合AOP机制,就返回代理对象 Object instance = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); return instance; } //若不走AOP机制, 则直接返回原生Bean return null; } }
3.运行测试 :
在测试类AppMain中,我们获取Calculator_Demo1对象,打印出它的运行类型,查看是否成功获得代理对象;接着,通过获取的代理对象调用 add方法,查看切面类配置的前置通知和返回通知是否切入成功。AppMain类代码如下 :
package com.cyan.spring.test; import com.cyan.spring.component.Calculator; import com.cyan.spring.ioc.CyanApplicationContext; /** * @author : Cyan_RA9 * @version : 21.0 */ public class AppMain { public static void main(String[] args) { CyanApplicationContext cyanApplicationContext = new CyanApplicationContext(); Calculator calculator = (Calculator) cyanApplicationContext.getBean("calculator_Demo1"); System.out.println("calculator = " + calculator); System.out.println("calculator's Class = " + calculator.getClass()); double result = calculator.add(11.2, 5.55); System.out.println("我在测试类AppMain中,result = " + result); } }
运行结果 :
编辑
可以看到,动态代理 + AOP 成功实现。
七、总结
- 🆗,以上就是Spring系列博文第六小节的全部内容了。
- 总结一下,最重要的就是“Spring 底层架构”的那张图,理解了那张图之后,实现Spring底层容器结构便不是难事;实现依赖注入也就是在IOC容器类上做做手脚。当然,Bean后置处理器机制的实现也很重要,因为它直接作为AOP机制的底层支撑。总体来说,代码量较大,结合注释来看会好一点,但需要亲手去敲,亲手反复地去敲。
- 下一节内容——Spring jdbcTemplate,我们不见不散😆。感谢阅读!
System.out.println("END-----------------------------------------------------");