当前位置: 首页 > news >正文

十堰论坛网站东莞专业网站推广多少钱

十堰论坛网站,东莞专业网站推广多少钱,常德小程序开发公司,网站开发需要团队目录 一、前言 二、Spring底层整体架构 1.准备工作 : 2.架构分析 : #xff08;重要#xff09; 3.环境搭建 #xff1a; 三、手动实现Spring容器结构 1.自定义注解 : 1.1 Component注解 1.2 Scope注解 2.自定义组件 : 3.自定义用于封装Bean信息的BeanDefinition类重要 3.环境搭建 三、手动实现Spring容器结构 1.自定义注解 :  1.1 Component注解  1.2 Scope注解 2.自定义组件 :  3.自定义用于封装Bean信息的BeanDefinition类 4.自定义IOC容器 :  5.运行测试 :  四、手动实现Spring依赖注入 1.准备工作 :  2.代码实现 :  3.运行测试 :  五、手动实现Spring后置处理器 1.实现思路 :  2.代码实现 :  3.运行测试 :  六、手动实现SpringAOP机制 1.准备工作 :  2.代码实现 :  3.运行测试 :  七、总结 一、前言 第六节内容我们一起来手动实现一下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配置文件中引入如下依赖 :  dependenciesdependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.11/versionscopetest/scope/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.3.8/version/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactIdversion5.3.8/version/dependencydependencygroupIdorg.dom4j/groupIdartifactIddom4j/artifactIdversion2.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;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;Component public class OrderService {Autowiredprivate OrderDAO orderDAO;public void saveOrder() {orderDAO.saveOrder();} }OrderServlet类代码如下 :  package com.cyan.spring.component;import org.springframework.stereotype.Component;Component public class OrderServlet {}然后up在resources目录下建立一个beans.xml配置文件代码如下 :  ?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://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-packagecom.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注解该自定义注解也是用于标注组件。*/ Target(ElementType.TYPE) Retention(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为单例还是多例。*/ Target(ElementType.TYPE) Retention(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。*/ Component(value orderService) Scope(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。*/ Component(value orderDAO) Scope(value prototype) public class OrderDAO { }假设com.cyan.spring.component包就是我们要扫描的包beans.xml配置文件代码如下 :  ?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://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-packagecom.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;}Overridepublic 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 ConcurrentHashMapString, BeanDefinition beanDefinitionMap new ConcurrentHashMap();//singletonObjects属性用于存放创建的Bean对象/实例单例池private final ConcurrentHashMapString, Object singletonObjects new ConcurrentHashMap();public CyanApplicationContext() {//初始化BeanDefinitionMapthis.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的实际工作路径pathString 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配置文件的根元素beansElement rootElement document.getRootElement();//1.4 获取根元素下的所有子元素(context:component-scan元素)ListElement 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 首先获取到该包对应的FileFile 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.classSystem.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值默认以类名首字母小写作为idif (.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 isnt a bean!);}} catch (Exception e) {throw new RuntimeException(e);}}}}}//初始化单例池的方法(注意形参列表中泛型的使用)private void initSingletonObjects(ConcurrentHashMapString, BeanDefinition beanDefinitionMap) {//遍历枚举类型//PS : asIterator()方法只有在Java9之后才能使用。//keys是所有的beanNameEnumerationString keys beanDefinitionMap.keys();IteratorString 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();//若反射过程中出现异常默认返回nullObject 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中是否存在该keyif (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注解用于实现自动装配。*/ Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) Retention(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。*/ Component(value orderDAO) Scope(value prototype) public class OrderDAO {public void save() {System.out.println(OrderDAOs 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。*/ Component(value orderService) Scope(value singleton) public class OrderService {//使用自定义的Autowired注解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。*/ Component(value orderService) Scope(value singleton) public class OrderService implements InitializingBean {//使用自定义的Autowired注解Autowired(required true)private OrderDAO orderDAO;public void save() {orderDAO.save();}Overridepublic void afterPropertiesSet() throws Exception {System.out.println(OrderServices 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*/ Component public class CyanBeanPostProcessor implements BeanPostProcessor {Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {System.out.println(postProcessBeforeInitialization方法被调用~ , bean bean.getClass() , beanName beanName);return null;}Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println(postProcessorAfterInitialization方法被调用~ , bean bean.getClass() , beanName beanName);return null;} }继续我们在IOC容器类中定义一个方法用于取出单例池中所有的后置处理器对象并返回代码如下 :  //定义一个方法用于从单例池中取出Bean后置处理器对象private ListBeanPostProcessor getBeanPostProcessors() {ListBeanPostProcessor 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();//若反射过程中出现异常默认返回nullObject 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注解用于标识切面类。*/ Retention(RetentionPolicy.RUNTIME) Target({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注解用于实现前置通知。*/ Retention(RetentionPolicy.RUNTIME) Target({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注解用于实现返回通知。*/ Retention(RetentionPolicy.RUNTIME) Target({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;Component public class Calculator_Demo1 implements Calculator {Overridepublic double add(double n1, double n2) {double result n1 n2;System.out.println(result result);return result;}Overridepublic double subtract(double n1, double n2) {double result n1 - n2;System.out.println(result result);return result;}Overridepublic double multiply(double n1, double n2) {double result n1 * n2;System.out.println(result result);return result;}Overridepublic double divide(double n1, double n2) {double result -1;if (n2 ! 0) { //分母不允许为0result 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切入表达式*/ Aspect Component public class CalculatorAspect {//1.前置通知Before(value execution(public double com.cyan.spring.component.Calculator_Demo1.add(double, double)))public void beforeAdvice() {System.out.println(--------------------前置通知~~);}//2.返回通知AfterReturning(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*/ Component public class CyanBeanPostProcessor implements BeanPostProcessor {//定义一个Map属性用于保存切入点表达式//key——String : 接口实现类需要被切入的方法的name 空格 对应的通知类型//value——Method : 切面类定义的切入方法private ConcurrentHashMapString, Method pointcutMap new ConcurrentHashMap();//定义一个CalculatorAspect类对象用于invoke方法执行时作为参数传入。private CalculatorAspect calculatorAspect new CalculatorAspect();Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {System.out.println(postProcessBeforeInitialization方法被调用~ , bean bean.getClass() , beanName beanName);return null;}Overridepublic 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对象ClassCalculatorAspect 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() {Overridepublic 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机制, 则直接返回原生Beanreturn 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(calculators 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-----------------------------------------------------);
http://www.w-s-a.com/news/807079/

相关文章:

  • 平面设计在线网站工业设计公司有哪些
  • 福州网站设计外包公司网站做的比较好
  • 如何设计网站首页网站开发综合技能实训心得体会
  • 用织梦做的网站好用吗w网站链接如何做脚注
  • 东莞做网站公司在哪哪里有网站培训的
  • 做宣传 为什么要做网站那重庆网站建设公司在线联系
  • 网站设计制作售价多少钱制作图片的软件是
  • 网站验证码目录简单带数据库的网站模版
  • 制作网站用c#做前台网站建设专题的意义
  • 广西建设职业技术学院教育网站牡丹区建设局网站
  • 网站后台怎么用ftp打开上海外贸进出口有限公司
  • 淘宝建设网站的意义大学生做那个视频网站
  • 如何提高你的网站的粘性建设银行流水网站
  • 微信h5在哪个网站做泰州专业网站制作公司
  • 现在.net做网站的多吗建设工程造价网
  • pc访问手机网站跳转违法网站开发人员
  • 网站前端做报名框wordpress 启动慢
  • 沈阳做网站客户多吗前端可以做网站吗
  • 网站设计规划书新媒体营销策略分析
  • dw个人网站主页怎么做天津工程信息建设网
  • 顺义做网站的公司网站页面设计基础教程
  • 安阳哪个公司做网站好企业没有做网站有的坏处
  • 网站开发有必要用php框架wordpress分页导航代码
  • wordpress建站seo鞍山制作网站哪家好
  • 网站空间流量查询上海门户网站制作
  • 网站开发技术是什么专业会的加强普法网站和普法网络集群建设
  • 上海建筑网站seo 推广
  • 乌兰察布做网站公司爱站网关键词挖掘工具站长工具
  • 白银网站建设白银申请网站空间怎么做
  • 免费炫酷网站模板网站建设需要用到什么软件有哪些