简单实现Spring容器(五) 实现bean后置处理器BeanPostProcessor机制

阶段5:

// 1.编写自己的Spring容器,实现扫描包,得到bean的class对象.
// 2.扫描将 bean 信息封装到 BeanDefinition对象,并放入到Map.
// 3.初始化单例池并完成getBean() createBean()方法
// 4.完成依赖注入(如果创建某个Bean对象,存在依赖注入,需要进行bean组装操作)
5.bean后置处理器实现,如果存在BeanPostProcessor扩展机制,就进行处理.先完成原生Spring使用Bean后置处理器案例再实现自己的bean的后置处理器

思路:

Spring容器中可能有多个后置处理器,所以需要考虑如果别人配置了多个Bean后置处理器,怎么处理,怎么切入到整个Spring容器里.

代码实现:

1.定义一个InitializingBean接口

package com.hspedu.spring.processor;/*** @author 45* @version 1.0* 1. 我们根据原生Spring 定义了一个InitializingBean* 2. 该InitializingBean接口有一个方法void afterPropertiesSet() throws Exception;* 3. afterPropertiesSet() 在Bean的 setter后执行,即就是我们原来的初始化方法* 4. 当一个Bean实现这个接口后,就实现afterPropertiesSet() , 这个方法就是初始化方法*/
public interface InitializingBean {void afterPropertiesSet() throws Exception;
}

2.MonsterService去实现接口

package com.elf.spring.component;
import com.elf.spring.annotation.Autowired;
import com.elf.spring.annotation.Component;
import com.elf.spring.annotation.Scope;
import com.elf.spring.processor.InitializingBean;/*** @author 45~* @version 1.0* 说明 MonsterService 是一个Servic*/
@Component //把MonsterService注入到我们自己的spring容器中
@Scope(value = "prototype")
public class MonsterService implements InitializingBean {//这里@Autowired匹配的形式很多,不用每个都去实现,这里先按名字来装配//这里使用的是自己定义的@Autowired来修饰属性,表示该属性,是通过容器完成依赖注入的//说明:我们实现按照名字来进行组装@Autowiredprivate MonsterDao monsterDao;public void m1(){monsterDao.hi();}/*** 1. afterPropertiesSet就是在bean的setter方法执行完毕后被spring容器调用* 2 即就是初始化方法* @throws Exception*/@Overridepublic void afterPropertiesSet() throws Exception {//这个方法是在Spring容器里边被调用的,你不会主动地调用它,//因为现在是用容器来搞定这个事System.out.println("MonsterService 初始化方法被调用 程序员在这里加入初始化的业务..");}
}

3.在创建好Bean实例后,判断是否需要初始化,容器文件

在容器中常用的一个方法是:根据该类是否实现了某个接口来判断是否要执行某个业务逻辑,这里就是基础的接口编程的实际应用
有一种接口叫标记接口,里面一个方法都没有,它实现这个接口的价值就是在它底层去判断你这个类是否要去走一个业务,Serilaizable接口就是,实现了这个接口但并没有实现任何方法,它其实就是个标记接口.(标记接口主要是给底层使用的=>看到你有这个接口,我就要干什么事情.)

运行看实现了接口的Bean在创建好后,有没有触发初始化方法. 在这里插入图片描述
因为MonsterService里实现了接口,就自动重写了afterPropertiesSet()初始化方法.所以创建好了MonsterService实例后,就调用初始化方法成功.已然是通过容器底层的接口编程的方式,将我们指定的初始化方法调用起来了.

  1. processor包下定义接口BeanPostProcessor
package com.elf.spring.processor;/*** @author 45* @version 1.0* 1. 参考原生Spring容器定义一个接口BeanPostProcessor* 2. 该接口有两个方法postProcessBeforeInitialization 和 postProcessAfterInitialization* 3. 这两个方法,会对Spring容器的所有Bean生效, 已经是切面编程的概念.*/
public interface BeanPostProcessor {/*** 说明* 1. postProcessBeforeInitialization在Bean的初始化方法前调用* @param bean* @param beanName* @return*/default Object postProcessBeforeInitialization(Object bean, String beanName) {return bean;}/*** 1. postProcessAfterInitialization在Bean的初始化方法后调用* @param bean* @param beanName* @return*/default Object postProcessAfterInitialization(Object bean, String beanName) {return bean;}
}

5.component包下写类

package com.elf.spring.component;import com.elf.spring.annotation.Component;
import com.elf.spring.processor.BeanPostProcessor;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @author 45* @version 1.0* 说明* 1. 这是我们自己的一个后置处理器* 2. 实现了BeanPostProcessor* 3. 我们可以重写before和after方法* 4. 在Spring容器中,仍然把ElfBeanPostProcessor当做一个Bean对象, 要在注入到容器* 5. @Component 标识* 6. 我们要让ElfBeanPostProcessor成为真正的后置处理器, 需要在容器中加入业务代码* 7. 还要考虑多个后置处理器对象注入到容器问题*/
@Component
//这个后置处理器如果不在容器里边写代码支撑,它就只是一个普通类
public class ElfBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {//要体会到,后置处理器是会容器的创建的bean生效//,相当于是可以对多个对象编程, 切面编程//日志,权限,身份, 事务.......if (bean instanceof Car) {System.out.println("这是一个Car对象, 我可以处理");//((Car)bean)}System.out.println("后置处理器ElfBeanPostProcessor Before调用 bean类型="+ bean.getClass() + " bean的名字=" + beanName);return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println("后置处理器ElfBeanPostProcessor After调用 bean类型="+ bean.getClass() + " bean的名字=" + beanName);
//
//        //实现AOP, 返回代理对象, 即对Bean进行包装
//        //1. 先死后活-> 后面我们可以通过注解就可以更加灵活
//        if ("smartDog".equals(beanName)) {
//            //使用Jdk的动态代理,返回返回bean的代理对象
//            //如果没有印象的小伙伴,回去看老韩讲过的动态代理
//            Object proxyInstance = Proxy.newProxyInstance(ElfBeanPostProcessor.class.getClassLoader(),
//                    bean.getClass().getInterfaces(), new InvocationHandler() {
//                        @Override
//                        public Object invoke(Object proxy, Method method, Object[] args)
//                                throws Throwable {
//                            System.out.println("method=" + method.getName());
//                            Object result = null;
//                            //假如我们进行前置通知+返回通知 处理的方法是getSum
//                            //后面可以通过注解来做的更加灵活
//                            if ("getSum".equals(method.getName())) {
//                                SmartAnimalAspect.showBeginLog();
//                                result = method.invoke(bean, args);//执行目标方法
//                                //进行返回通知的处理
//                                SmartAnimalAspect.showSuccessLog();
//                            } else {
//                                result = method.invoke(bean, args);//执行目标方法
//                            }
//                            return result;
//                        }
//                    });
//            //如果bean是需要返回代理对象的, 这里就直接return proxyInstance
//            return proxyInstance;
//        }
//        //如果不需要AOP, 返回 beanreturn bean;}
}

6.容器文件

package com.elf.spring.ioc;import com.elf.spring.annotation.*;
import com.elf.spring.processor.BeanPostProcessor;
import com.elf.spring.processor.InitializingBean;
import org.apache.commons.lang.StringUtils;import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;/*** @author 45~* @version 1.0*/
public class ElfSpringApplicationContext {//第一步,扫描包,得到bean的class对象,排除包下不是bean的,因此还没有放到容器中//因为现在写的spring容器比原先的基于注解的,要更加完善,所以不会直接把它放在ConcurrentHashMapprivate Class configClass;//定义属性BeanDefinitionMap -> 存放BeanDefinition对象private  ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =new ConcurrentHashMap<>();//定义属性SingletonObjects -> 存放单例对象 (跟原生容器的名字保持一致)//因为将来存放单例池的时候肯定要指定单例对象是对应哪个Bean的,所以k用String来充当//存放单例对象的类型是不确定的,可能是Dog,Cat,或者其他的对象,所以用Objectprivate  ConcurrentHashMap<String, Object> singletonObjects =new ConcurrentHashMap<>();//定义一个属性ArrayList,存放后置处理器[]private List<BeanPostProcessor> beanPostProcessorList =new ArrayList<>();//构造器public ElfSpringApplicationContext(Class configClass) {//完成扫描指定包beanDefinitionsByScan(configClass);//通过beanDefinitionMap,初始化singletonObjects单例池//封装成方法//遍历所有的beanDefinition,用到集合和枚举的知识Enumeration<String> keys = beanDefinitionMap.keys();//把所有bean的名字拿到while (keys.hasMoreElements()) {//得到beanNameString beanName = keys.nextElement();//通过beanName得到对应的beanDefinition对象BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);//判断该bean是singleton还是prototypeif ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {//将该bean实例放入到singletonObjects集合Object bean = createBean(beanName,beanDefinition);singletonObjects.put(beanName, bean);}}
//        System.out.println("singletonObjects 单例池=" + singletonObjects);
//        System.out.println("beanDefinitionMap=" + beanDefinitionMap);}//构造器结束//该方法完成对指定包的扫描,并将Bean信息封装到BeanDefinition对象,在放入到Mappublic void beanDefinitionsByScan(Class configClass) {this.configClass = configClass;/**获取要扫描的包:1.先得到ElfSpringConfig配置的 @ComponentScan(value= "com.elf.spring.component")2.通过 @ComponentScan的value => 即要扫描的包 **/ComponentScan componentScan =(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);String path = componentScan.value();
//        System.out.println("要扫描的包path=" + path);/*** 得到要扫描包下的所有资源(类.class)* 1.得到类的加载器 -> APP类加载器是可以拿到 target目录下的classes所有文件的* 2.通过类的加载器获取到要扫描的包的资源url => 类似一个路径* 3.将要加载的资源(.class)路径下的文件进行遍历 => io*/ClassLoader classLoader = ElfSpringApplicationContext.class.getClassLoader();path = path.replace(".", "/"); // 把.替换成 /URL resource = classLoader.getResource(path);
//        System.out.println("resource=" + resource);File file = new File(resource.getFile());if (file.isDirectory()) {File[] files = file.listFiles();for (File f : files) { //把所有的文件都取出来
//                System.out.println("============================");
//                System.out.println("f.getAbsolutePath()=" + f.getAbsolutePath());String fileAbsolutePath = f.getAbsolutePath();//到target的classes目录下了//这里只处理.class文件if (fileAbsolutePath.endsWith(".class")) {//1.获取类名String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1,fileAbsolutePath.indexOf(".class"));//2.获取类的完整路径(全类名)String classFullName = path.replace("/", ".") + "." + className;//System.out.println("classFullName=" + classFullName);//3.判断该类是否需要注入,就看是不是有注解@Component @Service @Contoller @Re....try {Class<?> cla = classLoader.loadClass(classFullName);if (cla.isAnnotationPresent(Component.class) ||cla.isAnnotationPresent(Controller.class) ||cla.isAnnotationPresent(Service.class) ||cla.isAnnotationPresent(Repository.class)) {//演示机制//如果该类使用了@Component注解,说明是一个Spring beanSystem.out.println("这是一个Spring bean=" + cla + " 类名=" + className);//1.真实的还是走下面是代码,为了方便,这里将后置处理器放到一个ArrayList//2.如果发现是一个后置处理器,就将其放入到beanPostProcessorList//3. 在原生的Spring容器中, 对后置处理器还是走的getBean, createBean//   , 但是需要我们在singletonObjects 加入相应的业务逻辑//4. 因为这里我们是为了讲解后置处理去的机制,我就简化//5. 如果小伙伴们,仍然走以前的逻辑,也可以,就是要麻烦一点//判断当前的这个cla有没有实现BeanPostProcessor//说明, 这里我们不能使用 instanceof 来判断cla是否实现了BeanPostProcessor//原因: cla不是一个实例对象,而是一个类对象/cla, 使用isAssignableFrom//小伙伴将其当做一个语法理解if (BeanPostProcessor.class.isAssignableFrom(cla)) {BeanPostProcessor beanPostProcessor =(BeanPostProcessor) cla.newInstance();//放入到beanPostProcessorListbeanPostProcessorList.add(beanPostProcessor);continue;//把后置处理器放好以后就不在往下走,让它去调用create方法}//先得到beanName(有可能通过经典4注解,例如Component注解的value值来指定)//1.得到类上的Component注解,此时的clazz已经是当前bean的class对象,通过类加载器得到的 反射知识Component componentAnnotation = cla.getDeclaredAnnotation(Component.class);//2.得到配置的valueString beanName = componentAnnotation.value();if ("".equals(beanName)) {//如果没有写value,空串//将该类的类名首字母小写作为beanName//StringUtils其实是在springframework的框架下面的类,而这里本身我就是要自己写所以不用beanName = StringUtils.uncapitalize(className);}//3.将Bean的信息封装到BeanDefinition对象中,然后将其放入到BeanDefinitionMap集合中BeanDefinition beanDefinition = new BeanDefinition();//!!!多看看这里多理解beanDefinition.setClazz(cla);//由类加载器通过反射得到对象,Class<?> cla = classLoader.loadClass(classFullName);//4.获取Scope值if (cla.isAnnotationPresent(Scope.class)) {//如果配置了Scope,就获取它配置的值Scope scopeAnnotation = cla.getDeclaredAnnotation(Scope.class);beanDefinition.setScope(scopeAnnotation.value());} else {//如果没有配置Scope,就以默认的值singletonbeanDefinition.setScope("singleton");}//将beanDefinitionMap对象放入MapbeanDefinitionMap.put(beanName, beanDefinition);} else {//如果该类没有使用了@Component注解,说明是一个Spring beanSystem.out.println("这不是一个Spring bean" + cla + " 类名=" + className);}} catch (Exception e) {e.printStackTrace();}}
//                System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");}//遍历文件结束}}//完成createBean(BeanDefinition beanDefinition)方法private  Object createBean(String beanName,BeanDefinition beanDefinition) {//得到Bean的Clazz对象Class clazz = beanDefinition.getClazz();try {//使用反射得到实例Object instance = clazz.getDeclaredConstructor().newInstance();//这里加入依赖注入的业务逻辑!!!//1.遍历当前要创建的对象的所有字段( 比如:要创建MonsterService,需要把所有的字段都扫描一遍,看看字段上有没有@Autowired的注解)for(Field declaredField : clazz.getDeclaredFields()){//2.判断这个字段是否有@Autwired的修饰if(declaredField.isAnnotationPresent(Autowired.class)){//提示:这里处理@Autowired的required
//                    Autowired annotation = declaredField.getAnnotation(Autowired.class);
//                    annotation.required()=>进行下一步其他处理//3.得到这个字段的名字String name = declaredField.getName();//4.通过getBean()方法来获取要组装的对象Object bean = getBean(name);//5.进行组装declaredField.setAccessible(true);//因为反射里属性是私有的,须进行暴破.declaredField.set(instance,bean);}}System.out.println("========创建好实例=========" + instance);//我们在bean的初始化方法前,调用后置处理器的before方法for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {//在后置处理器的before方法,可以对容器生成的bean实例进行处理//然后返回处理后的bean实例,相当于做了一个前置处理Object current = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);if(current != null){//判断后置处理器的before方法,返回来的对象是不是一个空,//如果不是空,再去改变//如果是一个空,就不用去该说明还是以前的instance//原生SPringl容器比这个复杂instance = current;}}//这里判断是否要执行Bean的初始化方法//1.判断当前创建的Bean对象是否实现了InitializingBean接口//2.instanceof是比较操作符,基础 PPT363,// 表示判断某个对象的运行类型,是不是某个类型或者某个类型的子类型//如果某个对象instance 实现了这个接口InitializingBean,那它可以认为是这个接口的子类型,那就是trueif(instance instanceof InitializingBean){//3.将instance转成InitializingBean接口类型//接口编程的具体体现try {((InitializingBean)instance).afterPropertiesSet();} catch (Exception e) {e.printStackTrace();}}//我们在bean的初始化方法后,调用后置处理器的after方法for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {//在后置处理器的after方法,可以对容器生成的bean实例进行处理//然后返回处理后的bean实例,相当于做了一个后置处理Object current = beanPostProcessor.postProcessAfterInitialization(instance, beanName);if(current != null){instance = current;}}System.out.println("-------------------------------------------");return instance;} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}//如果反射创建对象失败return null;}//编写getBean(String name)方法,返回容器中的对象.// 既然是getBean()那么返回类型是Object,因为不管是单例池还是创建的对象也好,类型是不确定的public  Object getBean(String name) {//加一个判断,严谨. 传入的beanName是否在beanDefinitonMap中存在..if(beanDefinitionMap.containsKey(name)) {//如果存在BeanDefinition beanDefinition = beanDefinitionMap.get(name);//得到beanDefinition的scope,分别进行处理if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {//说明是单例配置,就直接从单例池获取return singletonObjects.get(name);} else {//如果不是单例的,我们就调用createBean, 反射一个对象return createBean(name,beanDefinition);}} else { //如果不存在,就抛出一个空指针异常,也可自定义throw new NullPointerException("没有该bean");}}}

运行结果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/265167.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

城市基础设施智慧路灯改造的特点

智慧城市建设稳步有序推进。作为智慧城市的基础设施&#xff0c;智能照明是智慧城市的重要组成部分&#xff0c;而叁仟智慧路灯是智慧城市理念下的新产品。随着物联网和智能控制技术的飞速发展&#xff0c;路灯被赋予了新的任务和角色。除了使道路照明智能化和节能化外&#xf…

机器学习 类别特征编码:Category Encoders 库的使用

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

使用WebyogSQLyog使用数据库

数据库 实现数据持久化到本地&#xff1a; 使用完整的管理系统统一管理&#xff0c; 数据库&#xff08;DateBase&#xff09;&#xff1a; 为了方便数据存储和管理&#xff08;增删改查&#xff09;&#xff0c;将数据按照特定的规则存储起来 安装WebyogSQLyog -- 创建数…

STP(生成树协议)

STP&#xff08;生成树协议&#xff09; 生成树协议原理&#xff1a; ​在二层交换网络中&#xff0c;逻辑的阻塞部分接口&#xff0c;实现从根节点到所有节点唯一的路径的生成&#xff0c;成为一个没有环路的拓扑。当最佳路径数显故障时&#xff0c;个别被阻塞的接口将被打开…

Slate基础使用说明

目录 Slate基础使用说明 1. 简单教程 2. 要点说明 2.1 TCommands以及TCommands基类 2.2 FUICommandInfo 2.3 FUICommandList 2.4 FUIAction 2.5 UICommand 3. 代码源码 4. 工具使用 4.1 Display Ul Extension Points 4. 参考文章 Slate基础使用说明 1.…

linux逻辑卷LVM

创建LVMVG管理LV扩容 6.2.6 逻辑卷LVM LVM是Logical Volume Manager 的简称&#xff0c;译为逻辑卷管理&#xff0c;它是Linux下对硬盘分区的一种管理机制。LVM适合于管理大存储设备&#xff0c;并允许用户动态调整文件系统的大小。此外&#xff0c;LVM的快照功能可以帮助我们快…

算法leetcode|92. 反转链表 II(rust重拳出击)

文章目录 92. 反转链表 II&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a;进阶&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 92. 反转链表 II&#xff1a; 给你单链表的…

期待已久:K8S终于迎来交换内存Beta支持!

关注【云原生百宝箱】公众号&#xff0c;获取更多云原生消息 Kubernetes 1.22 版本开始支持在 Linux 节点上使用交换内存的 Alpha 特性&#xff0c;而在 1.28 版本中升级为 Beta 版本并进行了许多改进。之前版本的 Kubernetes 不支持 Linux 系统上的交换内存&#xff0c;但随着…

微信小程序、mpvue性能测试与体验

最近一直在折腾mpvue写的微信小程序的性能优化&#xff0c;分享下实战的过程。 先上个优化前后的图&#xff1a; 可以看到打包后的代码量从 813KB减少到 387KB,Audits体验评分从 B到 A&#xff0c;效果还是比较明显的。其实这个指标说明不了什么&#xff0c;而且轻易就可以做…

Pytorch深度强化学习1-6:详解时序差分强化学习(SARSA、Q-Learning算法)

目录 0 专栏介绍1 时序差分强化学习2 策略评估原理3 策略改进原理3.1 SARSA算法3.2 Q-Learning算法 0 专栏介绍 本专栏重点介绍强化学习技术的数学原理&#xff0c;并且采用Pytorch框架对常见的强化学习算法、案例进行实现&#xff0c;帮助读者理解并快速上手开发。同时&#…

网络层重点协议——IP协议详解

✏️✏️✏️今天给大家分享的是网络层的重点协议——IP协议。 清风的CSDN博客 &#x1f6e9;️&#x1f6e9;️&#x1f6e9;️希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&#xff01; ✈️✈️✈️动动你们发财的…

UIKit-AVCapture(创建一个简单的摄像头demo)

AVCaptureVideoPreviewLayer 用于显示摄像头采集到的实时视频预览&#xff0c;可以直接添加到视图层次结构中。 AVCaptureVideoPreviewLayer可以嵌入 AVCaptureSession 处理摄像头捕获的数据封装后给屏幕 AVCaptureConnection 用于管理捕获会话中的数据流连接&#xff0c;…