mini-ssm Lab6 - Spring AOP
在上个lab中我们实现了Aop的核心功能,在这个lab我们将AOP接入spring创建Bean的过程
Spring创建Bean的流程
在实现我们的Aop之前先看看Spring源码是怎么实现Bean的创建的?
- postProcessBeforeInstantiation 接口: 查找所有的切面和Advisor,并将切面的通知解析,构建成初步的增强器,加入到缓存中来。
- 创建对象实例
- 属性注入
- 三级缓存,解决代理对象循环依赖
- postProcessAfterInitialization 接口,从缓存取出所有的将所有的增强器,创建代理工厂,并织入增强器,创建代理对象
我们看到,Spring在非特殊情况下,采用先属性注入再生成代理类的流程。
这样做的好处是:
- 功能解耦,拓展点灵活
mini-ssm创建Bean的流程
- 创建对象实例
- 创建代理类
- 属性注入
好处:
- 实现简单
Aspect循环依赖问题
在做Aop之前我们需要拿到切面类,知道对什么方法做了什么增强。
在代理对象前我们要获取到所有的切面类,再对对象进行增强,
但是如果切面类要被注入,且注入对象为代理对象,此时就产生了一个问题:
切面对象依赖代理对象,代理对象创建需要切面对象。
解决办法:
- 切面类中的属性设置为懒加载
懒加载思想
被 @Lazy 标记的属性,在 populateBean 注入依赖时,会直接注入一个 proxy 对象。并且,不会触发注入对象的加载。
这样的话,就不会产生 bean 的循环加载问题了。
容器启动完成后,A在需要使用B的方法时,会执行代理对象的逻辑,获取到TargetSource,调用getResource从三层缓存中获取B的真实对象,由于B此时已经被spring完整地创建好了,处于一级缓存singletonObjects当中,因此拿到之后可以放心使用。
上面的话使用Jdk代理翻译成代码大概是下面这样,但是应该使用Cglib代理,一般Aspect类没有接口
@AllArgsConstructor
static class handler implements InvocationHandler{
Class<?> targetClass;
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
target=getTarget();
Object ret = method.invoke(target, args);
return ret;
}
private Object getTarget() {
//从ContextUtil中获取bean,就是单例的,所以不用双检锁
if(target==null){
target=ContextUtil.getBean(targetClass);
}
return target;
}
}
mini-ssm 实现 {id="mini-ssm_1"}
public class LazyBeanAspect {
@Around
public void around(ProceedingJoinPoint pjp) throws Throwable {
Object target = pjp.getTarget();
if(target==null){
pjp.setTarget(ContextUtil.getBean(pjp.getTargetClass()));
}
pjp.proceed();
}
...
}
ContextUtil
/**
* @author _qqiu
*/
public class ContextUtil {
private static ApplicationContext applicationContext;
public static void setApplicationContext(ApplicationContext applicationContext) {
ContextUtil.applicationContext = applicationContext;
}
/**
* 根据类型获取Bean
* @param clazz class
* @param <T> T
* @return 对象
*/
public static <T> T getBean(Class<T> clazz) {
try {
return applicationContext.getBean(clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 根据组件名称获取Bean
* @param beanName beanName
* @return 对象
*/
public static Object getBean(String beanName) {
try {
return applicationContext.getBean(beanName);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
mini-ssm 流程
mini-ssm 如何解决循环依赖+动态代理?
- 在实例化Bean前初始化Aspect类
- Aspect内的属性都是懒加载的属性,解决了循环依赖的问题。
- 执行getBean
- 找一级缓存,找到返回 已被注入的,被代理完的Bean
- 找二级缓存,如果在二级缓存有说明发生了循环依赖,返回未被注入的,被代理完的Bean
- 创建原型bean对象
- 生成代理对象proxy
- 将代理对象放进二级缓存
- 属性注入给原型bean对象,注入的元素也是调用getBean所以也是被代理完成的。
- 将已被注入的,被代理完的Bean放入一级缓存