Spring学习(四)AOP基础

2020-08-26 17:20:57 浏览数 (1)

一、Spring Aop示例

Spring aop的存在,可以帮助我们少些很多与业务无关但是又冗余的代码,比如,我们可以将计算接口执行时间或者一些通用日志打印的代码抽离出来写到spring的切面中,来减少业务代码和我们这些通用代码的耦合性。

(1)示例中我声明了一个用户业务逻辑相关的接口,在接口中定义了一个登录的方法。

代码语言:javascript复制
public interface UserService {
    void login() throws InterruptedException;
}

(2)声明一个实现类,并实现了UserService接口,并给该实现类加上spring注解,由Spring加载时初始化。

代码语言:javascript复制
@Service
public class UserServiceImpl implements UserService {
    @Override
    public void login() throws InterruptedException {
        System.out.println("用户登录执行开始");
        Thread.sleep(2000);
        System.out.println("用户登录执行结束");
    }
}

(3)开始定义AOP切面。

· 先配置一个切入点,切入点主要的是表达式。

· 再配置AOP中的通知,表示进入切面时,需要做的通用代码的处理。

· 给AOP类加上@Aspect注解,指定该类是AOP类。

代码如下图所示:

代码语言:javascript复制
@Aspect
@Component
public class AopService {

    @Pointcut("execution(* com.ywl.leetcode.spring.ready..*.*(..))")
    public void runAop() {
    }
    
    @Around("runAop()")
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知前");
        long startTime = System.currentTimeMillis();
        Object proceed = pjp.proceed();

System.out.println("方法执行时间:" (Syste

m.currentTimeMillis() - startTime) "毫秒");

代码语言:javascript复制
代码语言:javascript复制
        System.out.println("环绕通知后");
    }
}

(4)开启AOP执行开关。

代码语言:javascript复制
@ComponentScan("com.ywl.leetcode")
@EnableAspectJAutoProxy
public class AppConfig {
}

(5)运行代码。

代码语言:javascript复制
public static void main(String[] args) throws InterruptedException {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    UserService userService = context.getBean(UserService.class);
    userService.login();
}

运行结果:

计算登录时间的代码成功在aop中执行。

(6)观察有aop和无aop情况下,UserService的示例。

· 无aop,UserService为正常的java类

· 有aop,UserService变成了Jdk动态代理对象

二、阅读源码,观察bean是在哪步被替换为代理对象的

对UserService的bean的初始化过程进行debug发现,在doCreateBean中变成了代理对象。

进入doCreateBean中继续观察。

· doCreateBean

(1)createBeanInstance中,调用后置处理器,推断合适的构造函数,并通过反射new出了UserService的早期对象。

(2)applyMergedBeanDefinitionPostProcessors中,调用后置处理器,目的是合并注解,根据策略使用对应的后置处理器。

@Resource、@PostConstruct注解使用了CommonAnnotationBeanPostProcessor处理器。

@Autowired、@Value注解使用了AutowiredAnnotationBeanPostProcessor处理器。

(3)addSingletonFactory中,暴露了bean早期引用,并放入到二级缓存中,以解决bean的循环引用。

(4)populateBean中,根据后置处理器进行属性依赖注入。

(5)initializeBean执行完毕,发现UserService变为了代理对象。

· initializeBean

(1)invokeAwareMethods,执行对应方法,set依赖的属性。

代码语言:javascript复制
private void invokeAwareMethods(final String beanName, final Object bean) {
   if (bean instanceof Aware) {
      if (bean instanceof BeanNameAware) {
         ((BeanNameAware) bean).setBeanName(beanName);
      }
      if (bean instanceof BeanClassLoaderAware) {
         ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
      }
      if (bean instanceof BeanFactoryAware) {
         ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
      }
   }
}

(2)applyBeanPostProcessorsBeforeInitialization,真正调用后置处理器的postProcessBeforeInitialization方法。

(3)invokeInitMethods,执行bean的初始化方法。

从这块源码的执行顺序其实可以看出初始化方法的一些执行顺序为:postProcessBeforeInitialization—>afterPropertiesSet—>initMethod

(4)applyBeanPostProcessorsAfterInitialization,真正调用后置处理器的postProcessAfterInitalization,并且userService变为了代理对象。

· beanPostProcessors

只有第三个和动态代理有关系。

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization—>createProxy,进行代理工厂的组装。

代码语言:javascript复制
protected Object createProxy(
      Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
   //...
   ProxyFactory proxyFactory = new ProxyFactory();
   proxyFactory.copyFrom(this);
   if (!proxyFactory.isProxyTargetClass()) {
      if (shouldProxyTargetClass(beanClass, beanName)) {
         proxyFactory.setProxyTargetClass(true);
      }
      else {
         evaluateProxyInterfaces(beanClass, proxyFactory);
      }
   }

   Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
   proxyFactory.addAdvisors(advisors);
   proxyFactory.setTargetSource(targetSource);
   customizeProxyFactory(proxyFactory);
   proxyFactory.setFrozen(this.freezeProxy);
   if (advisorsPreFiltered()) {
      proxyFactory.setPreFiltered(true);
   }
   //代理方法执行的核心方法
   return proxyFactory.getProxy(getProxyClassLoader());
}

三、代理方法执行的核心方法

代码语言:javascript复制
public Object getProxy(ClassLoader classLoader) {
   return createAopProxy().getProxy(classLoader);
}

· createAopProxy

代码语言:javascript复制
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
      Class<?> targetClass = config.getTargetClass();
      if (targetClass == null) {
         throw new AopConfigException("TargetSource cannot determine target class: "  
               "Either an interface or a target is required for proxy creation.");
      }
      if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
         return new JdkDynamicAopProxy(config);
      }
      return new ObjenesisCglibAopProxy(config);
   }
   else {
      return new JdkDynamicAopProxy(config);
   }
}

上图所示的代码其实就是spring aop中选择动态代理的核心方法。主要思想其实为,如果代理类为接口,就使用JDK动态代理。如果不为接口,就使用CGLIB动态代理。

上图中的config.isOptimize和conifg.isProxyTargetClass,如果当你没有进行配置的话,就读取的是spring的默认配置,具体的配置信息:

代码语言:javascript复制
public @interface EnableAspectJAutoProxy {

   /**
    * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
    * to standard Java interface-based proxies. The default is {@code false}.
    */
   boolean proxyTargetClass() default false;
   /**
    * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
    * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
    * Off by default, i.e. no guarantees that {@code AopContext} access will work.
    * @since 4.3.1
    */
   boolean exposeProxy() default false;
}

根据spring中的注释可以得出,默认采用的是JDK动态代理。

· 使代理类变成CGLIB动态代理类

只需要将UserServiceImpl的接口去掉即可,修改后代码如下:

代码语言:javascript复制
@Service("userServiceImpl")
public class UserServiceImpl {
    public void login() throws InterruptedException {
        System.out.println("用户登录执行开始");
        Thread.sleep(2000);
        System.out.println("用户登录执行结束");
    }
}

此时的UserService变成了CGLIB动态代理类。

至于cglib和jdk动态代理的具体执行过程,放到下一篇动态代理中,单独来进行分析。

四、AOP-通知

aop中的通知(即代理加强方法)加上上述示例中的环绕通知,共有五种通知。

· @Before:前置通知,在方法执行之前执行。

· @After:后置通知,在方法执行之后执行。

· @AfterRunning:返回通知,在方法返回结果之后执行。

· @AfterThrowing:异常通知,在方法执行出现异常之后执行。

· @Around:环绕通知,方法执行前与执行后执行。

0 人点赞