Spring系列五:手动实现Spring底层机制 第三部分
💗实现任务阶段5
🍚bean后置处理器实现
bean后置处理器实现
思路:先完成原生Spring 使用Bean后置处理器的案例, 然后实现自己的bean后置处理器
bean的生命周期~传送门
在idea中 shortcuts: shift+shift
, 输入 InitializingBean
, 搜索. 在zzw-spring
项目里
1.在com.zzw.spring.processor
包下定义接口 InitializingBean
. 实现该接口的Bean
, 需要实现Bean
初始化方法, 可以参考 原生Spring规范来定义这个接口
👆
//解读
//1.我们根据原生Spring 定义了一个InitializingBean
//2.这个接口有一个方法 void afterPropertiesSet() throws Exception;
//3.afterPropertiesSet() 在Bean的 setter后执行, 即就是我们原来的初始化方法
//4.当一个Bean实现这个接口后, 就实现这个afterPropertiesSet(), 这个方法就是初始化方法
public interface InitializingBean {void afterPropertiesSet() throws Exception;
}
2.MonsterService
实现这个接口
@Component(value = "monsterService")// 把MonsterService注入我们自己写的spring容器中
@Scope(value = "prototype")
public class MonsterService implements InitializingBean {@Autowiredprivate MonsterDao monsterDao;public void m1() {monsterDao.hi();}/*** 解读* 1.afterPropertiesSet就是在bean的setter方法执行完毕后被spring容器调用* 2.即就是初始化方法* @throws Exception*/@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("MonsterService 初始化方法被调用 程序员在这里加入初始化的业务...");}
}
说明
在创建好Bean
实例后, 判断是否需要进行初始化. 【编程心得: 容器中常用的一个方法是, 根据该类是否实现了某个接口, 来判断是否要执行某个业务逻辑, 这里其实就是java基础的接口编程实际应用】
1.在ZzwSpringApplicationContext
类的createBean
方法遍历字段后
加入如下代码
System.out.println("======创建好实例======" + instance);
//这里判断是否要执行Bean的初始化方法
//1.判断当前创建的Bean对象是否实现了InitializingBean接口
//2.instanceof 表示判断某个对象的运行类型是不是某个类型或其子类型
//3.这里使用到接口编程
if (instance instanceof InitializingBean) {//将instance转成接口InitializingBean((InitializingBean) instance).afterPropertiesSet();
}
return instance;
2.注销ZzwSpringApplicationContext
类的构造器中的下面两条代码
//System.out.println("singletonObjects 单例池=" + singletonObjects);
//System.out.println("beanDefinitionMap=" + beanDefinitionMap);
3.测试com.zzw.spring.AppMain
public class AppMain {public static void main(String[] args) {ZzwSpringApplicationContext ioc =new ZzwSpringApplicationContext(ZzwSpringConfig.class);MonsterService monsterService = (MonsterService)ioc.getBean("monsterService");}
}
4.运行效果
//上面输出结果忽略...
======创建好实例======com.zzw.spring.component.MonsterDao@5479e3f
======创建好实例======com.zzw.spring.component.MonsterService@33e5ccce
MonsterService 初始化方法被调用 程序员在这里加入初始化的业务...
5.MonsterDao
实现InitializingBean
的方法
@Component(value = "monsterDao")
//@Scope(value = "prototype")
public class MonsterDao implements InitializingBean {public void hi() {System.out.println("MonsterDao hi()...");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("MonsterDao 初始化方法被调用...");}
}
6.测试com.zzw.spring.AppMain
public class AppMain {public static void main(String[] args) {ZzwSpringApplicationContext ioc =new ZzwSpringApplicationContext(ZzwSpringConfig.class);MonsterService monsterService = (MonsterService)ioc.getBean("monsterService");}
}
7.运行效果
上面输出结果忽略...
======创建好实例======com.zzw.spring.component.MonsterDao@27082746
MonsterDao 初始化方法被调用...
======创建好实例======com.zzw.spring.component.MonsterService@33e5ccce
MonsterService 初始化方法被调用 程序员在这里加入初始化的业务...
8.参考原生Spring容器的BeanPostProcessor接口
, 定义com.zzw.spring.processor.BeanPostProcessor
接口
注意: 在jdk8及jdk8以后, 接口中可以有默认实现方法, 需要是由default关键字修饰
//解读
//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;}
}
9.在com.zzw.spring.component
包下新建ZzwBeanPostProcessor
/*** 说明* 1.这是我们自己的一个后置处理器* 2.实现了BeanPostProcessor* 3.我们可以重写before和after方法* 4.在Spring容器中, 仍然把ZzwBeanPostProcessor当做一个Bean对象, 要注入到容器* 5.@Component 标识* 6.我们要让ZzwBeanPostProcessor成为真正的后置处理器, 需要在容器中加入业务代码* 7.还要考虑多个后置处理器对象注入到容器的问题
*/
@Component
public class ZzwBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {System.out.println("后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型="+ bean.getClass() + " bean的名字=" + beanName);return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println("后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型="+ bean.getClass() + " bean的名字=" + beanName);return bean;}
}
这时后置处理器不会生效, 继续写代码支撑.
10.在ZzwSpringApplicationContext
类中定义一个属性, 在beanDefinitionsByScan
方法中添加代码
//代码省略...//定义一个属性beanPostProcessorList, => 存放后置处理器
private List<BeanPostProcessor> beanPostProcessorList= new ArrayList<>();//代码省略...public void beanDefinitionsByScan(Class configClass) {//代码省略...try {Class<?> clazz = classLoader.loadClass(classFullName);//这里就是演示了一下机制if (clazz.isAnnotationPresent(Component.class)) {//如果该类使用了@Component注解, 说明是Spring beanSystem.out.println("是一个Spring bean=" + clazz + " 类名=" + className);//说明👇//1.为了方便, 这里将后置处理器放入到一个ArrayList//2.如果发现是一个后置处理器, 就放入到beanPostProcessorList//3.在原生的Spring容器中, 对后置处理器还是走的getBean(),createBean()// ,但是需要我们在singletonObjects中加入相应的业务逻辑//4.因为这里我们只是为了学习后置处理器的机制, 就简化处理.//5.如果仍然走以前的逻辑, 也可以, 就是要麻烦一点.//判断当前这个clazz有没有实现我们定义的BeanPostProcessor接口//说明, 这里我们不能用 instanceof 来判断clazz是否实现了BeanPostProcessor接口//原因: clazz不是一个实例对象, 而是一个类对象/clazz, 使用isAssignableFrom// 将其当作一个语法理解if (BeanPostProcessor.class.isAssignableFrom(clazz)) {BeanPostProcessor beanPostProcessor= (BeanPostProcessor) clazz.newInstance();//将其放入到BeanPostProcessorListbeanPostProcessorList.add(beanPostProcessor);}👆//代码省略...} else {//如果该类没有使用@Component注解, 说明不是Spring beanSystem.out.println("不是一个Spring bean=" + clazz + " 类名=" + className);}} catch (Exception e) {throw new RuntimeException(e);}
}
11.在ZzwSpringApplicationContext
类的createBean
方法中添加如下代码
System.out.println("======创建好实例======" + instance);👇//我们在Bean的初始化方法前, 调用后置处理器的before方法for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {//在后置处理器的before方法, 可以对容器bean进行处理//然后返回处理后的bean实例, 相当于做了一个前置处理instance =beanPostProcessor.postProcessBeforeInitialization(instance, "...");
👆}//这里判断是否要执行Bean的初始化方法//1.判断当前创建的Bean对象是否实现了InitializingBean接口//2.instanceof 表示判断某个对象的运行类型是不是某个类型或者某个类型的子类型//3.这里使用到接口编程if (instance instanceof InitializingBean) {//3.将instance转成接口InitializingBean((InitializingBean) instance).afterPropertiesSet();}👇//我们在Bean的初始化方法后, 调用后置处理器的after方法for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {//在后置处理器的after方法, 可以对容器bean进行处理//然后返回处理后的bean实例, 相当于做了一个后置处理instance =beanPostProcessor.postProcessAfterInitialization(instance, "...");
👆}return instance;
12.测试com.zzw.spring.AppMain
public class AppMain {public static void main(String[] args) {ZzwSpringApplicationContext ioc =new ZzwSpringApplicationContext(ZzwSpringConfig.class);MonsterService monsterService = (MonsterService)ioc.getBean("monsterService");}
}
13.运行结果
//前面输出结果忽略...
======创建好实例======com.zzw.spring.component.ZzwBeanPostProcessor@4c75cab9
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.ZzwBeanPostProcessor bean的名字=...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.ZzwBeanPostProcessor bean的名字=...
======创建好实例======com.zzw.spring.component.MonsterDao@67117f44
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.MonsterDao bean的名字=...
MonsterDao 初始化方法被调用...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.MonsterDao bean的名字=...
======创建好实例======com.zzw.spring.component.MonsterService@3930015a
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.MonsterService bean的名字=...
MonsterService 初始化方法被调用 程序员在这里加入初始化的业务...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.MonsterService bean的名字=...
这里有两份ZzwBeanPostProcessor
, 去掉singletonObjects
中的后置处理器对象, 保留beanPostProcessorList
中的, 因为我们自定义的beanPostProcessorList
操作起来方便.
1.在ZzwSpringApplicationContext
的beanDefinitionsByScan
方法内添加continue
跳过循环
if (BeanPostProcessor.class.isAssignableFrom(clazz)) {BeanPostProcessor beanPostProcessor= (BeanPostProcessor) clazz.newInstance();//将其放入到BeanPostProcessorListbeanPostProcessorList.add(beanPostProcessor);👉continue;👈
}
2.测试com.zzw.spring.AppMain
public class AppMain {public static void main(String[] args) {ZzwSpringApplicationContext ioc =new ZzwSpringApplicationContext(ZzwSpringConfig.class);MonsterService monsterService = (MonsterService)ioc.getBean("monsterService");}
}
3.运行结果
//前面输出结果忽略...
======创建好实例======com.zzw.spring.component.MonsterDao@7bfcd12c
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.MonsterDao bean的名字=...
MonsterDao 初始化方法被调用...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.MonsterDao bean的名字=...
======创建好实例======com.zzw.spring.component.MonsterService@1ef7fe8e
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.MonsterService bean的名字=...
MonsterService 初始化方法被调用 程序员在这里加入初始化的业务...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.MonsterService bean的名字=...
1.新建com.zzw.spring.component.Car
.让Car
类实现InitializingBean
初始化方法
@Component
public class Car implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("Car的初始化方法...");}
}
2.测试com.zzw.spring.AppMain
public class AppMain {public static void main(String[] args) {ZzwSpringApplicationContext ioc =new ZzwSpringApplicationContext(ZzwSpringConfig.class);MonsterService monsterService = (MonsterService)ioc.getBean("monsterService");}
}
3.运行结果
//前面输出结果忽略...
======创建好实例======com.zzw.spring.component.Car@6f79caec
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.Car bean的名字=...
Car的初始化方法...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.Car bean的名字=...
======创建好实例======com.zzw.spring.component.MonsterDao@5d3411d
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.MonsterDao bean的名字=...
MonsterDao 初始化方法被调用...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.MonsterDao bean的名字=...
======创建好实例======com.zzw.spring.component.MonsterService@50675690
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.MonsterService bean的名字=...
MonsterService 初始化方法被调用 程序员在这里加入初始化的业务...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.MonsterService bean的名字=...
4.修改com.zzw.spring.processor.ZzwBeanPostProcessor
的before方法
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {//这里一定要体会到后置处理器是对容器创建的所有bean生效//, 相当于是可以对多个对象编程, 切面编程//日志, 权限, 身份, 事务...if (bean instanceof Car) {System.out.println("这是一个Car对象, 我可以处理");//((Car) bean)}System.out.println("后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型="+ bean.getClass() + " bean的名字=" + beanName);return bean;
}
5.测试com.zzw.spring.AppMain
public class AppMain {public static void main(String[] args) {ZzwSpringApplicationContext ioc =new ZzwSpringApplicationContext(ZzwSpringConfig.class);MonsterService monsterService = (MonsterService)ioc.getBean("monsterService");}
}
6.运行结果
//前面输出结果忽略...
======创建好实例======com.zzw.spring.component.Car@6f79caec
这是一个Car对象, 我可以处理
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.Car bean的名字=...
Car的初始化方法...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.Car bean的名字=...
======创建好实例======com.zzw.spring.component.MonsterDao@5d3411d
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.MonsterDao bean的名字=...
MonsterDao 初始化方法被调用...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.MonsterDao bean的名字=...
======创建好实例======com.zzw.spring.component.MonsterService@50675690
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.MonsterService bean的名字=...
MonsterService 初始化方法被调用 程序员在这里加入初始化的业务...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.MonsterService bean的名字=...
完善: 对下列代码的beanName
进行完善.
instance = beanPostProcessor.postProcessBeforeInitialization(instance, "...");
1.ZzwSpringApplicationContext
类的createBean
方法增加一个形参String beanName
private Object createBean(BeanDefinition beanDefinition, 👉String beanName👈) {//内容忽略...//我们在Bean的初始化方法前, 调用后置处理器的before方法for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {//在后置处理器的before方法, 可以对容器bean进行处理//然后返回处理后的bean实例, 相当于做了一个前置处理instance =beanPostProcessor.postProcessBeforeInitialization(instance, 👉beanName👈);}//我们在Bean的初始化方法后, 调用后置处理器的after方法for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {//在后置处理器的after方法, 可以对容器bean进行处理//然后返回处理后的bean实例, 相当于做了一个后置处理instance =beanPostProcessor.postProcessAfterInitialization(instance, 👉beanName👈);}
}
2.修改ZzwSpringApplicationContext
类的getBean
方法
//编写方法getBean(String name), 编写方法返回容器对象
public Object getBean(String name) {//加一个判断条件: 传入的beanName是否在beanDefinitionMap中存在if (beanDefinitionMap.containsKey(name)) {//如果存在BeanDefinition beanDefinition = beanDefinitionMap.get(name);//得到beanDefinition的scope, 分别进行处理if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {//说明是单例配置, 就直接从单例池获取return singletonObjects.get(name);} else {//如果不是单例的, 我们就调用createBean, 反射一个对象返回return createBean(beanDefinition, 👉name👈);}} else {//如果不存在//抛出一个空指针异常, 也可以自定义throw new NullPointerException("没有该bean");}
}
3.修改ZzwSpringApplicationContext
类的构造器
方法
//构造器
public ZzwSpringApplicationContext(Class configClass) {//完成扫描指定的包beanDefinitionsByScan(configClass);//通过beanDefinitionMap, 初始化singletonObjects 单例池//封装成方法//遍历所有的beanDefinition对象Enumeration<String> keys = beanDefinitionMap.keys();while (keys.hasMoreElements()) {//得到beanName, 得到对应的beanDefinitionString beanName = keys.nextElement();BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);//判断该bean是singleton, 还是prototypeif ("singleton".equals(beanDefinition.getScope())) {//将该bean实例放入到singletonObjects 集合Object bean = createBean(beanDefinition, 👉beanName👈);singletonObjects.put(beanName, bean);}}
}
4.测试com.zzw.spring.AppMain
public class AppMain {public static void main(String[] args) {ZzwSpringApplicationContext ioc =new ZzwSpringApplicationContext(ZzwSpringConfig.class);MonsterService monsterService = (MonsterService)ioc.getBean("monsterService");}
}
5.运行结果
//前面输出结果忽略...
======创建好实例======com.zzw.spring.component.Car@6f79caec
这是一个Car对象, 我可以处理
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.Car bean的名字=car
Car的初始化方法...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.Car bean的名字=car
======创建好实例======com.zzw.spring.component.MonsterDao@5d3411d
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.MonsterDao bean的名字=monsterDao
MonsterDao 初始化方法被调用...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.MonsterDao bean的名字=monsterDao
======创建好实例======com.zzw.spring.component.MonsterService@50675690
后置处理器ZzwBeanPostProcessor Before方法 被调用... bean类型=class com.zzw.spring.component.MonsterService bean的名字=monsterService
MonsterService 初始化方法被调用 程序员在这里加入初始化的业务...
后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型=class com.zzw.spring.component.MonsterService bean的名字=monsterService
1.对于原生后置处理器的before
和after
方法是可以返回空的
2.证明: 对前面我们写的spring项目, 进行Debug. 传送门
3.但是我们自己写的后置处理器到目前为止如果返回null的话, 会报错. Exception in thread "main" java.lang.NullPointerException
修改src/main/java/com/zzw/spring/component/ZzwBeanPostProcessor.java
@Component
public class ZzwBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {//这里一定要体会到后置处理器是对容器创建的所有bean生效//相当于可以对多个对象编程, 切面编程//日志, 权限, 身份, 事务.if (bean instanceof Car) {System.out.println("这是一个Car对象, 我可以处理");}System.out.println("后置处理器ZzwBeanPostProcessor Before方法被调用 beand对象=" + bean+ " bean名字=" + beanName);return null;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println("后置处理器ZzwBeanPostProcessor After方法被调用 beand对象=" + bean+ " bean名字=" + beanName);return null;}
}
4.改进 ZzwSpringApplicationContext类
的createBean
方法
这里其实也可以不用赋值, 因为instance是引用类型.
以上说法是错误的, 因为后置处理器方法有可能返回代理类型, 所以必须给instance赋值
//我们在Bean的初始化方法前, 调用后置处理器的before方法
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {//在后置处理器的before方法, 可以对容器bean进行处理//然后返回处理后的bean实例, 相当于做了一个前置处理👇Object current =beanPostProcessor.postProcessBeforeInitialization(instance, beanName);if (current != null) {instance = current;👆}
}//这里判断是否要执行Bean的初始化方法
//1.判断当前创建的Bean对象是否实现了InitializingBean接口
//2.instanceof 表示判断某个对象的运行类型是不是某个类型或者某个类型的子类型
//3.这里使用到接口编程
if (instance instanceof InitializingBean) {//3.将instance转成接口InitializingBean((InitializingBean) instance).afterPropertiesSet();
}//我们在Bean的初始化方法后, 调用后置处理器的after方法
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {//在后置处理器的after方法, 可以对容器bean进行处理//然后返回处理后的bean实例, 相当于做了一个后置处理// 原生Spring容器, 比我们这个还要复杂👇Object current =beanPostProcessor.postProcessAfterInitialization(instance, beanName);if (current != null) {instance = current;👆}
}
5.测试com.zzw.spring.AppMain
public class AppMain {public static void main(String[] args) {ZzwSpringApplicationContext ioc =new ZzwSpringApplicationContext(ZzwSpringConfig.class);MonsterService monsterService = (MonsterService)ioc.getBean("monsterService");}
}
💗实现任务阶段6
🍚AOP机制实现
AOP机制实现
在 Spring系列四:AOP切面编程 里, 我们有这么一段运行结果. 其中, 可以看到, smartDog
在postProcessBeforeInitialization
方法里是SmartDog
类型, 到了postProcessAfterInitialization
里变成了代理类型
, 中间又没有执行初始化方法, 所以返回一个代理类型只能是在后置处理器的postProcessAfterInitialization
方法内完成的.
因为只对SmartDog
做了切入, 所以只有SmartDog
返回了代理类型.
1.新建com.zzw.spring.component.SmartAnimalAble
接口
public interface SmartAnimalAble {float getSum(float i, float j);float getSub(float i, float j);
}
2.新建com.zzw.spring.component.SmartDog
@Component(value = "smartDog")
public class SmartDog implements SmartAnimalAble{public float getSum(float i, float j) {float res = i + j;System.out.println("SmartDog getSum() 结果=" + res);return res;}public float getSub(float i, float j) {float res = i - j;System.out.println("SmartDog getSub() 结果=" + res);return res;}
}
3.新建com.zzw.spring.component.SmartAnimalAspect
切面类
//说明: SmartAnimalAspect当做一个切面类来使用
//,后面再分析如何做得更加灵活
public class SmartAnimalAspect {public static void showBeginLog() {System.out.println("前置通知..");}public static void showSuccessEndLog() {System.out.println("返回通知..");}
}
4.在ZzwBeanPostProcessor
的after方法
内写入代码
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println("后置处理器ZzwBeanPostProcessor After方法 被调用... bean类型="+ bean.getClass() + " bean的名字=" + beanName);//实现AOP, 返回代理对象, 即对Bean进行包装//1.先死后活 -> 后面我们可以通过注解就可以更加灵活if ("smartDog".equals(beanName)) {//使用Jdk的动态代理, 返回该bean的代理对象Object proxyInstance = Proxy.newProxyInstance(ZzwBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("method=" + method.getName());Object result = null;//假如我们要进行前置通知+返回通知处理的方法是getSum// 后面可以通过注解来做的更加灵活@Before @AfterReturningif ("getSum".equals(method.getName())) {SmartAnimalAspect.showBeginLog();result = method.invoke(bean, args);//执行目标方法//进行返回通知的处理SmartAnimalAspect.showSuccessEndLog();} else {result = method.invoke(bean, args);//执行目标方法}return result;}});//如果bean是需要返回代理对象的, 这里就直接return proxyInstancereturn proxyInstance;}//如果不需要AOP, 返回 beanreturn bean;
}
5.测试com.zzw.spring.AppMain
public class AppMain {public static void main(String[] args) {//创建自己的容器ZzwSpringApplicationContext ioc =new ZzwSpringApplicationContext(ZzwSpringConfig.class);SmartAnimalAble smartDog = (SmartAnimalAble) ioc.getBean("smartDog");smartDog.getSum(10, 2);smartDog.getSub(10, 2);System.out.println("ok");}
}
6.运行结果
//前面输出结果省略...
method=getSum
前置通知..
SmartDog getSum() 结果=12.0
返回通知..
method=getSub
SmartDog getSub() 结果=8.0
//后面输出结果省略...
思考扩展: 如何做的更加灵活
1).前面我们使用的硬编码, 不灵活, 但是已经把AOP的核心代码说清楚了.
2).思考一下如何把AOP做的更加灵活, 核心知识点(注解 + 数据结构/map/少许算法 + 业务处理), 和AOP机制关系不大了
示意代码
1.新建com.zzw.spring.annotation.Aspect
注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Aspect {String value() default "";
}
2.新建com.zzw.spring.annotation.Before
注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Before {String value();String argNames() default "";
}
3.新建com.zzw.spring.annotation.AfterReturning
注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AfterReturning {String value() default "";String pointcut() default "";String returning() default "";String argNames() default "";
}
4.修改切面类com.zzw.spring.component.SmartAnimalAspect
, 用我们自己的注解修饰
@Aspect //我们自己的注解
@Component //我们自己的注解
public class SmartAnimalAspect {@Before(value = "execution com.zzw.spring.component.SmartDog getSum")public static void showBeginLog() {System.out.println("前置通知..");}@AfterReturning(value = "execution com.zzw.spring.component.SmartDog getSum")public static void showSuccessEndLog() {System.out.println("返回通知..");}
}
5.测试com.zzw.spring.ZzwTest
public class ZzwTest {public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {//1.获取SmartAnimalAspect的class对象Class<SmartAnimalAspect> clazz = SmartAnimalAspect.class;//2.遍历该类的所有方法for (Method declaredMethod : clazz.getDeclaredMethods()) {//如果切面类的方法有Before注解if (declaredMethod.isAnnotationPresent(Before.class)) {//得到切面类的切入方法System.out.println("method名字=" + declaredMethod.getName());//得到Before注解Before beforeAnnotation = declaredMethod.getDeclaredAnnotation(Before.class);//得到Before注解 的valueString value = beforeAnnotation.value();System.out.println("value=" + value);//得到要切入的方法[通过反射调用]Method declaredMethod1 = clazz.getDeclaredMethod(declaredMethod.getName());//调用切入方法declaredMethod1.invoke(clazz.newInstance(), null);} else if (declaredMethod.isAnnotationPresent(AfterReturning.class)) {//如果切面类的方法有AfterReturning注解, 同样可以进行类似处理//得到切面类的切入方法System.out.println("method名字=" + declaredMethod.getName());//得到AfterReturning注解AfterReturning afterReturningAnnotation = declaredMethod.getAnnotation(AfterReturning.class);//得到AfterReturning注解 的valueString value = afterReturningAnnotation.value();System.out.println("value=" + value);//得到要切入的方法Method declaredMethod1 = clazz.getDeclaredMethod(declaredMethod.getName());//调用切入方法[通过反射调用]declaredMethod1.invoke(clazz.newInstance(), null);}}}
}
6.运行结果
method名字=showBeginLog
value=execution com.zzw.spring.component.SmartDog getSum
前置通知..
method名字=showSuccessEndLog
value=execution com.zzw.spring.component.SmartDog getSum
返回通知..
完结散花…
相关文章:
Spring系列五:手动实现Spring底层机制 第三部分
💗实现任务阶段5 🍚bean后置处理器实现 bean后置处理器实现 思路:先完成原生Spring 使用Bean后置处理器的案例, 然后实现自己的bean后置处理器 bean的生命周期~传送门 在idea中 shortcuts: shiftshift , 输入 InitializingBean, 搜索. 在zzw-spring…...
AGV、AMR机器人控制器x86/RK3588/NV各有什么优劣势?
以下是关于AGV/AMR机器人控制器解决方案中x86、RK(瑞芯微)、NV(英伟达)平台优劣势的综合分析: 一、核心性能对比 平台优势局限性适用场景x861. 多核高频CPU支持复杂算法运算,适合大规模路径规划及多机…...
[ACTF2020 新生赛]Include [ACTF2020 新生赛]Exec
[ACTF2020 新生赛]Include 因为前端过滤的太多了 所以直接使用 日志包含 搞 包含这个 /var/log/nginx/access.log [ACTF2020 新生赛]Include蚁剑连接 翻看 flag{1ce7a81e-0339-44ef-a398-a7784d3efe37} [ACTF2020 新生赛]Exec [ACTF2020 新生赛]Exec 127.0.0.1 |echo <?…...
23种设计模式 -- 工厂模式
Java 工厂模式示例代码 下面我将展示三种常见的工厂模式实现:简单工厂模式、工厂方法模式和抽象工厂模式。 1. 简单工厂模式 java // 产品接口 interface Shape {void draw(); }// 具体产品类 class Circle implements Shape {Overridepublic void draw() {System…...
【25软考网工】第四章(2)WLAN通信技术、WLAN频谱与信道
目录 一、WLAN通信技术 1. 扩展频谱通信 1)扩频通信种类 2)技术关系 2. 应用案例 1)例题:扩频通信概念判定 2)例题:跳频扩频技术描述 3)例题:扩频通信技术标准 3.知识小结 二、WLAN频谱与信道 1. WLAN网络分…...
MetaEditor - 自动交易和技术指标编辑器
功能特点 代码编辑功能:支持多种编程语言,如 MQL4 和 MQL5,方便交易者根据自己的需求编写自动交易策略和技术指标。它提供了代码高亮、自动缩进、语法检查等功能,有助于提高代码编写的效率和准确性。调试与测试工具:配…...
关于华为云OneAccess登录认证过程介绍
这里主要介绍的是OAuth2认证的一个流程,分享一下实际开发过程中的实现逻辑 1、后端先根据接口文档的细节内容,然后拼接好url并且以String的类型返回给前端 2、此时前端会访问本连接,然后就会跳转到第三方页面,用户进行登录后&am…...
Spring - 简单实现一个 Spring 应用
一、为什么需要学习Spring框架? 1.企业级开发标配 超过60%的Java项目都使用Spring生态(数据来源:JetBrains开发者报告)。 2.简化复杂问题 通过IoC和DI,告别new关键字满天飞的代码。 3.职业竞争力 几乎所有Java岗…...
数据展示功能界面设计与实现及终端控制界面思路(17)
文章目录 一、本章说明二、传感数据展示功能实现2.1 实现目标2.2 具体实现2.2.1 需要在.pro 文件添加2.2.2 mainwindow.h 头文件2.2.3 mainwindow.cpp 文件2.2.4 实现界面展示三、项目源码文件四、终端节点控制五、Qt打包EXE一、本章说明 注:本节为【基于STM的环境监测系统(…...
opendds编译开发(c#封装)
opendds是对DDS协议的开源实现,具体可以查阅官网。 我使用的是源码编译开发,博文只针对windows平台。 环境准备 环境安装 1.visual studio 2022,我是企业版,安装需要选择c 2.Per下载安装,下载链接:https://strawberryperl.com…...
电子监管码预检剔除装置提示盒尺寸过短
问题:电子监管码预检剔除装置提示盒尺寸过短 现象:自动线监控程序报警提示“盒尺寸过短”。 原因:分盒设备拨盒的力度和距离、盒子在传送带的角度或传送带速度有所变化都会导致自动线出现“盒尺寸过短” 解决方法:根据传送带的速…...
【C++11特性】Lambda表达式(匿名函数)
一、函数对象 在C中,我们把所有能当作函数使用的对象当作函数对象。 一般来说,如果我们列出一个对象,而它的后面又跟有由花括号包裹的参数列表,就像fun(arg1, arg2, …),这个对象就被称为函数对象。函数对象大致可分为…...
【北京】昌平区某附小v3700存储双控故障维修案例
2025年4月22日,接到一位通过网络找到我们的北京老师求助,反馈该学校一台V3700存储的磁盘分区无法正常读取了,老师在机房检查时发现存储后面2个控制器均亮警告灯。急需修复该设备读取里面资料用于周末运动会所需。于是在网上找到我们协助进行排…...
汤晓鸥:计算机视觉的开拓者与AI产业化的先行者
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 汤晓鸥:计算机视觉的开拓者与AI产业化的先行者 一、学术与创业之路 1. 从…...
MATLAB实现神经网络的OCR识别
使用说明: 运行要求: MATLAB R2020b 或更新版本已安装 Deep Learning Toolbox推荐使用GPU加速(训练时在代码开头添加 gpuDevice(1)) 代码特点: 使用MATLAB自带的MNIST手写数字数据集包含数据可视化、网络架构…...
车载软件架构 --- AUTOSAR的方法论
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…...
Java 的创新与变革之路:从 JDK 7 到 JDK 23
Java 作为一种广泛应用的编程语言,其发展历程与 JDK(Java Development Kit)的演进紧密相连。自 JDK 7 起,Java 语言不断引入重大特性,持续重塑开发格局,推动性能、功能和开发体验的提升。本文将深入剖析 JD…...
LangChain、LlamaIndex、MCP、Spring AI、Ollama 和 DeepSeek 的定义、关系及典型架构设计
以下是 LangChain、LlamaIndex、MCP、Spring AI、Ollama 和 DeepSeek 的定义、关系及典型架构设计解析: 一、核心组件定义与功能 1. LangChain 定位:构建复杂 大语言模型(LLM)应用的通用框架。功能: 支持链式调用&am…...
Docker安装的mysql限制ip访问
1.问题背景 docker安装了mysql服务,服务器为Redhat9,我们希望通过防火墙规则直接限制访问的来源ip,只允许特定ip进行访问,其余ip需要被禁止。 2.排查过程 1.首先尝试了通过firewalld方式添加对应的防火墙规则, sud…...
怎么样才能在idea中写入spark程序
一、准备环境 1.安装Scala插件 专业版IDEA自带Scala插件,社区版需手动安装 确保插件版本与IDEA版本匹配 2.选择用哪个构建工具 sbt:适用于依赖管理简单、快速迭代的项目,需提前安装sbt工具24。 Maven:适合熟悉Java生态…...
MySQL的数据类型
目录 数据类型分类 数值类型 tinyint bit 编辑 小数(float) 小数(decimal) 字符串类型 char varchar 日期和时间类型 String类型 enum set find_ in_ set函数 我们今天来学习MySQL的数据分类。 数据类型分类 MySQL的字符型就是相当于字符串类型,所…...
Vue 中局部指令(directives)的用法详解
在 Vue.js 中,指令是一种特殊的属性,用于在 DOM 元素上绑定特殊行为。除了 Vue 内置的指令(如v-model、v-if),还可以自定义指令来封装可复用的功能。 什么是局部指令? 局部指令是在单个组件中定义和使用的…...
mac 基于Docker安装minio服务器
在 macOS 上基于 Docker 安装 MinIO 是一个高效且灵活的方案,尤其适合本地开发或测试环境。以下是详细的安装与配置步骤,结合了最佳实践和常见问题的解决方案: 一、安装 Docker Desktop 下载安装包 访问 Docker 官网,下载适用于 …...
Redis常用数据结构解析:从原理到实战应用
作为一名开发者,我深知Redis在缓存、队列等场景中的重要性。但要用好Redis,必须对其核心数据结构有透彻理解。本文将结合我的实践经验,详细讲解Redis五种最常用的数据结构及其典型应用场景,帮助你在开发中游刃有余。 一、String&…...
Druid监控sql导致的内存溢出
问题 druid监控sql在网页端显示,我的服务插入sql比较大,druid把执行过的sql保存在DruidDataSource类的成员变量JdbcDataSourceStat dataSourceStat; JdbcDataSourceStat类中的LinkedHashMap<String, JdbcSqlStat> sqlStatMap中&#…...
基于Python镜像创建docker镜像时pip install一直出现NewConnectionError的一种解决办法
用dockerfile创建docker镜像,在pip的时候一直是出现错误: 13.21 WARNING: Retrying (Retry(total4, connectNone, readNone, redirectNone, statusNone)) after connection broken by NewConnectionError(<pip._vendor.urllib3.connection.HTTPSConn…...
【机器学习】朴素贝叶斯
目录 一、朴素贝叶斯的算法原理 1.1 定义 1.2 贝叶斯定理 1.3 条件独立性假设 二、朴素贝叶斯算法的几种常见类型 2.1 高斯朴素贝叶斯 (Gaussian Naive Bayes) 【训练阶段】 - 从数据中学习模型参数 【预测阶段】 - 对新样本 Xnew 进行分类 2. 2 多项式朴素贝叶斯 (…...
服务器硬件老化导致性能下降的排查与优化
随着企业数字化转型的深入,服务器作为IT基础设施的核心载体,其稳定性与性能直接影响业务连续性。然而,硬件老化导致的性能衰减问题普遍存在且易被忽视。本报告通过系统性分析服务器硬件老化现象,提出多维度排查方法与优化方案&…...
学习记录:DAY19
Docker 部署与项目需求分析 前言 人总是本能地恐惧未知,令生活陷入到经验主义的循环之中。但我们终将面对。今天的目标是把 Docker 部署学完,然后对项目进行需求分析。 日程 下午 4:30:Docker 部署项目部分学完了,做下笔记。晚…...
机器学习中的数据转换:关键步骤与最佳实践
机器学习中的数据转换:关键步骤与最佳实践 摘要 :在机器学习领域,数据是模型的核心,而数据的转换是构建高效、准确模型的关键步骤之一。本文深入探讨了机器学习中数据转换的重要性、常见的数据类型及其转换方法,以及在…...
【C++教程】三目运算符
C的三目运算符(条件运算符)是一种简洁的条件表达式工具,其形式为 条件 ? 表达式1 : 表达式2。以下是对其用法的详细总结: 1. 基本用法 条件判断:若条件为真,返回表达式1的值;否则返回表达式2…...
鼠标获取坐标 vs 相机获取坐标
Cesium鼠标点击获取坐标 vs 相机视角获取坐标 鼠标点击获取坐标流程图 #mermaid-svg-WwyCUbcFQekWG97C {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-WwyCUbcFQekWG97C .error-icon{fill:#552222;}#mermaid-svg-W…...
HarmonyOS SDK助力鸿蒙版今日水印相机,真实地址防护再升级
今日水印相机是一款真实记录"工作"和"生活"的水印拍照APP。作为专业的可信影像服务平台,今日水印相机依托时间、地点、身份三重数字水印技术,为企业和个人提供考勤打卡、外勤巡检、生活美好时刻记录等场景的可信存证服务。 面对虚拟…...
数组滑动窗口单调栈单调队列trick集【leetcode hot100 c++速查!!!】
文章目录 栈经典模版题-括号最小栈字符串解码每日温度柱状图的最大矩形 堆数组中的第k个最大元素前k个高频元素数据流中的中位数 数组最大子数组和合并区间轮转数组除自身以外数组的乘积 我们尝试将这三类问题放在一个专题中进行讨论,是因为它们有很多公共的部分。 …...
半监督学习与强化学习的结合:新兴的智能训练模式
📌 友情提示: 本文内容由银河易创AI(https://ai.eaigx.com)创作平台的gpt-4o-mini模型生成,旨在提供技术参考与灵感启发。文中观点或代码示例需结合实际情况验证,建议读者通过官方文档或实践进一步确认其准…...
C++ 可调用实体 (详解 一站式)
目录 可调用实体 函数对象 函数指针 成员函数指针 空指针的使用(了解) 可调用实体 讲到调用这个词,我们首先能够想到普通函数和函数指针,在学习了类与对象的基础知识后,还增加了成员函数,那么它们都被…...
架构师与高级工程师:职业差异与进阶之路
“学而不思则罔,思而不学则殆。”——孔子 解释:该名言强调了学习和思考的结合,对应文章中工程师若想晋升为架构师,不能仅满足于对工具的学习,还要深入探究事物本质,培养深度思考能力,体现了思…...
声纹监测技术在新能源汽车的应用场景解析
新能源汽车声纹监测技术可应用于多个场景,以下是详细解析: 故障诊断 电机系统故障检测:新能源汽车的电机在运行过程中会发出特定的声音。通过声纹监测技术,采集电机运行时的声音信号并进行分析,能够及时发现电机轴承磨…...
About why docker application mode taskmanager not down in time
Tips: docker flink application mode 当任务完成或者jobmanager cancel,则taskmanager会因为flink集群机制继续保留监听等待jobmanager发送任务命令,当超过大约6 mins,taskmanager便会自动exited退出。...
2025-4-27-C++ 学习 数组(2)
数组 2025-4-27-C 学习 数组(2)P2550 [AHOI2001] 彩票摇奖题目描述输入格式输出格式输入输出样例 #1输入 #1输出 #1 说明/提示题解代码 P2615 [NOIP 2015 提高组] 神奇的幻方题目背景题目描述输入格式输出格式输入输出样例 #1输入 #1输出 #1 输入输出样例…...
timerfd定时器时间轮定时器
目录 一、timerfd定时器 二、timerfd定时器代码演示 三、时间轮定时器 一、timerfd定时器 timerfd是一种通过文件描述符管理定时器的机制 #include <sys/timerfd.h> int timerfd_create(int clockid, int flags); 作用:创建定时器的文件描述符 返回值&…...
什么是数据中心代理IP?有哪些用途?
在海外代理IP的选择中,数据中心代理IP是一个热门选项。这些代理服务器为用户分配了非ISP(互联网服务提供商)提供的IP地址,而是由第三方云服务提供商所提供的,通常位于数据中心内的服务器上,由托管和云公司所…...
机器学习分类模型性能评估:应对类别不平衡的策略与指标
在机器学习的世界里,模型们就像一群努力破案的侦探,而数据就是它们的“犯罪现场”。今天,咱们的主角——一个自命不凡的分类模型,接到了一个看似简单的任务:揪出那些患有罕见疾病的患者。这听起来是不是很容易…...
论文导读 - 基于边缘计算、集成学习与传感器集群的便携式电子鼻系统
基于边缘计算、集成学习与传感器集群的便携式电子鼻系统 原论文地址:https://www.sciencedirect.com/science/article/abs/pii/S0925400522015684 引用此论文(GB/T 7714-2015): WANG T, WU Y, ZHANG Y, et al. Portable electr…...
Molex莫仕连接器:增强高级驾驶辅助系统,打造更安全的汽车
随着对先进、高耗电量的系统的需求日益增长,电气化进程不断加速,汽车行业正处于一个十字路口。现代汽车面临着关键挑战,即满足不断增长的电力需求,特别是高级驾驶辅助系统(ADAS)等关键技术的需求。 由于现今的汽车比以往需要更多的…...
[密码学实战]SDF之密钥管理类函数(二)
[密码学实战]SDF之密钥管理类函数(二) 一、标准解读:GM/T 0018-2023核心要求 1.1 SDF接口定位 安全边界:硬件密码设备与应用系统间的标准交互层 功能范畴: #mermaid-svg-af5D1B1iHx3K8vSU {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16…...
多实例情况下,实例名较长dbca失败
dbca创建数据库,但是失败,提示ORA-01158 看来千锤百炼的dbca脚本还是菜,直觉上讲不应该mount上,看一下Action 本地已存在多个实例且名称前缀类似,下一步应该分析dbca日志和实例的alert.log 改为 一个简短的实例名就…...
模电——PN结
一、铺垫 这篇文章将会吊打一切、只会从电子、电场力的角度来阐述PN结为啥会形成、和变薄、变厚;不再考虑空穴这种东西;——提出空穴的人,真不是东西 我敢打赌,全网,我的说法不一定对,但是绝对是唯一可以…...
c++11 : 特殊类设计
目录 一 设计一个类:只能在堆上创建对象 二 设计一个类:只能在栈上创建对象 三 设计一个类:不能被拷贝 四 设计一个类:不能被继承 五 设计一个类: 只能创建一个对象(单例模式) 六 饿汉和懒汉模式的对比 一 设计一个类…...
算法笔记.kruskal算法求最小生成树
题目:(来源:AcWing) 给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。 求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。 给定一张边带权的无向…...