mini-ssm Lab6 - Spring AOP

28 阅读3分钟

mini-ssm Lab6 - Spring AOP

mini-ssm github

在上个lab中我们实现了Aop的核心功能,在这个lab我们将AOP接入spring创建Bean的过程

Spring创建Bean的流程

在实现我们的Aop之前先看看Spring源码是怎么实现Bean的创建的?

  1. postProcessBeforeInstantiation 接口: 查找所有的切面和Advisor,并将切面的通知解析,构建成初步的增强器,加入到缓存中来。
  2. 创建对象实例
  3. 属性注入
  4. 三级缓存,解决代理对象循环依赖
  5. postProcessAfterInitialization 接口,从缓存取出所有的将所有的增强器,创建代理工厂,并织入增强器,创建代理对象

我们看到,Spring在非特殊情况下,采用先属性注入再生成代理类的流程。

这样做的好处是:

  1. 功能解耦,拓展点灵活

mini-ssm创建Bean的流程

  1. 创建对象实例
  2. 创建代理类
  3. 属性注入

好处:

  1. 实现简单

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 如何解决循环依赖+动态代理?

  1. 在实例化Bean前初始化Aspect类
  2. Aspect内的属性都是懒加载的属性,解决了循环依赖的问题。
  3. 执行getBean
    1. 找一级缓存,找到返回 已被注入的被代理完的Bean
    2. 找二级缓存,如果在二级缓存有说明发生了循环依赖,返回未被注入的被代理完的Bean
    3. 创建原型bean对象
    4. 生成代理对象proxy
    5. 将代理对象放进二级缓存
    6. 属性注入给原型bean对象,注入的元素也是调用getBean所以也是被代理完成的。
    7. 已被注入的被代理完的Bean放入一级缓存