如何使用Spring AOP及基本原理

2019-01-19  本文已影响0人  WekingZhang

阅读本文,你将了解到如何使用Spring AOP及AOP的基本原理,文末还与大家分享AOP的使用情景

在面向对象编程中(OOP)中,我们编程的关注点在于某个对象实体有哪些具体功能及其子类功能实现的不同。不同于OOP,面向切面编程(AOP)更多关注的业务流程。在不侵入业务代码的前提下,我们可以通过AOP编程,为业务流程某个具体环节(连接点)增加业务逻辑(通知),这些业务逻辑可能是打印日志、安全控制、事务控制等等。
使用AOP前必须理解清楚AOP相关的几个概念:通知(Advice)、切点(pointcut)、切面(aspect)、。

通过理解这几个概念,面向切面编程(AOP)就是要解决何时何地执行何种操作的问题。
除了以上的三个概念,AOP还有其他的概念,在这里也简单说明一下:

1.使用示例

理解清楚AOP相关的几个概念后,我们可以看一个AOP的使用示例。
创建切面,其中注解@Pointcut定义了切点信息,@Before("log")和logPrint方法定义了通知信息。

//logAOP.java
@Component
@Aspect
public class LogAOP {
    //切点信息
    @Pointcut("execution(* cn.test.pro.project.GsProjectService.*(..))")
    public void log(){

    }
    //前置增强
    @Before("log()")
    public void logPrint(JoinPoint joinPoint){
        System.out.println("---------logPrint();-------"+joinPoint.getTarget().getClass());
    }

}

目标类的信息

//GsProjectService.java
@Service
public class GsProjectService {
    @Autowired
    private GsProjectMapper gsProjectMapper;

    public String getById(String id){
        return "admin";
    }
}

配置文件spring-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
         http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context-4.0.xsd
          http://www.springframework.org/schema/tx
          http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

    <!-- 扫描注解 -->
    <context:component-scan base-package="cn.test.pro">
        <context:exclude-filter type="annotation"       
           expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
    <!--基于aspectj的注解驱动-->
    <aop:aspectj-autoproxy/>
</beans>

启动类相关信息

//Main.java
public class Main {
    public static void main(String[] args){
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
        GsProjectService gsProjectService = (GsProjectService) ac.getBean("gsProjectService");
        System.out.println(gsProjectService.getById("1"));
    }
}

输出结果:

---------logPrint();-------class cn.test.pro.project.GsProjectService
cn.test.pro.project.GsProject@3370f42
2. 基本原理

说明:本文提到的AOP的基本原理是主要说明使用注解的AOP,基于XML配置的AOP类似。看本节时建议先阅读Java 动态代理机制解析

说起Spring AOP的基本原理,我们要从配置文件中配置说起:

<!--基于aspectj的注解驱动-->
    <aop:aspectj-autoproxy/>

在xml配置文件中增加如上配置后,就开启了基于注解的AOP功能。我们知道Spring 启动时会读取配置文件,并对文件中的配置项进行解析。

  1. 当Spring读取到该配置项后,会根据该行的命名空间AOP,查找对应的命名空间处理器AOPNamespaceHandler;

2.在AOPNamespaceHandler中,我们看到如下的代码:

public class AopNamespaceHandler extends NamespaceHandlerSupport {
    public AopNamespaceHandler() {
    }

    public void init() {
        this.registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        this.registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        this.registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
        this.registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }
}

在方法init()中,我们看到"aspectj-autoproxy"配置信息的解析交给了类 AspectJAutoProxyBeanDefinition进行解析。

  1. 现在Spring知道要使用类AspectJAutoProxyBeanDefinition进行配置解析,类AspectJAutoProxyBeanDefinition是接口BeanDefinitionParser的实现类,接着Spring调用该类的parse方法进行解析;
//AspectJAutoProxyBeanDefinition.java
public BeanDefinition parse(Element element, ParserContext parserContext) {
 AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
        this.extendBeanDefinition(element, parserContext);
        return null;
    }
  1. 我们特别注意parse方法中的
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);

介绍这个方法前,我们还必须要知道AOP是通过动态代理机制实现的,而类AspectJAnnotationAutoProxyCreator正是完成由目标类(target Object)到代理类的转换,可以说该类是AOP实现的核心类。
我们接着看方法registerAspectJAnnotationAutoProxyCreatorIfNecessary的功能,从方法名上我们可以看出该方法主要完成的是将AspectJAnnotationAutoProxyCreator注册到Spring容器中的功能。这样在合适的时机,Spring就可以使用该类根据目标类动态生成代理类了。

  1. 什么是合适的时机呢?根据动态代理机制原理(可参考Java 动态代理机制解析)的介绍,生成代理类必须需要一个实例化的目标类。
    为了知道什么是合适的时机,我们还要看一下AspectJAnnotationAutoProxyCreator的类结构图,我们看到该类是接口BeanPostProcessor的实现类。

    AspectJAnnotationAutoProxyCreator的类结构图.png
    BeanPostProcessor是一种非常重要的接口,在创建Bean的过程中会调用BeanPostProcessor的postProcessAfterInitialization方法。spring的开发者也可以使用该接口的特性扩展bean的功能。而代理类的生成也正式在此处。
  2. 现在我们看一下AspectJAnnotationAutoProxyCreator的postProcessAfterInitialization方法

//方法的实现在AbstractAutoProxyCreator.java
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean != null) {
            //如果已经生成过代理,则直接从缓存中获取
            Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
            if(!this.earlyProxyReferences.contains(cacheKey)) {
                //生成代理对象
                return this.wrapIfNecessary(bean, beanName, cacheKey);
            }
        }

        return bean;
    }

我们再看一下方法wrapIfNecessary的实现

//方法的实现在AbstractAutoProxyCreator.java
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
         //如果当前已经被代理过,则直接返回;
        if(beanName != null && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        } else if(Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        } else if(!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
            //获取切面的所有信息(包含通知和切点信息)
            Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
            if(specificInterceptors != DO_NOT_PROXY) {
                this.advisedBeans.put(cacheKey, Boolean.TRUE);
                //根据切面信息和具体bean,创建该bean的代理类
                Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
                this.proxyTypes.put(cacheKey, proxy.getClass());
                return proxy;
            } else {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return bean;
            }
        } else {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    }

wrapIfNecessary方法主要分为两个步骤:首先找到所有切面的信息,然后根据切面信息生成代理类。

  1. 我们再详细看一下Spring是如何创建代理类的?
//方法的实现在AbstractAutoProxyCreator.java
protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
        if(this.beanFactory instanceof ConfigurableListableBeanFactory) {
            AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)this.beanFactory, beanName, beanClass);
        }

        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.copyFrom(this);
        if(!proxyFactory.isProxyTargetClass()) {
            if(this.shouldProxyTargetClass(beanClass, beanName)) {
                proxyFactory.setProxyTargetClass(true);
            } else {
                this.evaluateProxyInterfaces(beanClass, proxyFactory);
            }
        }

        Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors);
        Advisor[] var7 = advisors;
        int var8 = advisors.length;

        for(int var9 = 0; var9 < var8; ++var9) {
            Advisor advisor = var7[var9];
            proxyFactory.addAdvisor(advisor);
        }

        proxyFactory.setTargetSource(targetSource);
        this.customizeProxyFactory(proxyFactory);
        proxyFactory.setFrozen(this.freezeProxy);
        if(this.advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }

        return proxyFactory.getProxy(this.getProxyClassLoader());
    }

//方法实现在ProxyFactory.java
public Object getProxy(ClassLoader classLoader) {
        return this.createAopProxy().getProxy(classLoader);
    }
//方法实现在ProxyCreatorSupport.java中
protected final synchronized AopProxy createAopProxy() {
        if(!this.active) {
            this.activate();
        }

        return this.getAopProxyFactory().createAopProxy(this);
    }
//方法实现在DefaultAopProxyFactory中
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if(!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
            return new JdkDynamicAopProxy(config);
        } else {
            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.");
            } else {
                return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass)?new ObjenesisCglibAopProxy(config):new JdkDynamicAopProxy(config));
            }
        }
    }

经历了多个方法间的调用,我们终于看到了关注了代码。在DefaultAopProxyFactory的方法createAopProxy中,我们看到了Spring 是如何选择JDK和CGLIB两种动态代理机制的:

3. 使用场景

了解了Spring AOP的使用示例及基本原理后,我们一块看两种Spring AOP的应用场景。

(1)增加统一日志

在第一节使用示例中,为我们展示在方法调用前增加日志打印。在Web开发中,我们可以实现Controller层或者Service统一日志打印,避免重复性日志打印代码。

(2)动态切换数据源

Spring对于多数据源有很好的支持。在Spring中,我们可以通过继承AbstractRoutingDataSource实现在程序运行时动态选择数据源。具体实现方案可以查看spring 动态切换数据源 多数据库

参考:
《Spring实战》 第三版
《Spring源码深度解析》
https://docs.spring.io/spring-framework/docs/current/javadoc-api/overview-summary.html

上一篇下一篇

猜你喜欢

热点阅读