基础2:AOP基础

AOP基本概念

代码混乱:越来越多的非业务需求(日志、验证)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同时,还要必须兼顾其他多个关注点。

代码分散:以打印日志为例,为了满足打印日志需求,需要在多个模块里多次编写重复相同的日志代码,如果日志需求发生变化,就必须修改所有模块。

这里为了解决此类问题,其解决方案有点像代理设计模式:使用一个代理,将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上。

基于这种设计思想,Spring发明了AOP。

AOP(Aspect-Oriented Programming,面向切面编程),是一种新的方法论,是对传统OOP即面向对象编程的补充。

AOP主要编程对象是切面aspect,而切面模块化横切关注点。

在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里、以什么方式应用,并且不必修改受影响的现有类,这样一来横切关注点就被模块化到特殊的切面对象里。

这样一来AOP主要好处有2个:

①每个事物逻辑位于一个位置,代码不分散,便于维护和升级;

②业务模块更简洁,只包含核心业务代码;

AOP常用术语如下:

切面Aspect:横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象;

通知Advice:切面必须要完成的工作;

目标Target:被通知的对象;

代理Proxy:向目标对象应用通知之后创建的对象;

连接点joinpoint:程序执行的某个特定位置,如果某个方法调用前、调用后、方法抛出异常后等。连接点由2个信息来确定:方法表示的程序执行点、相对点表示的方位;

切点pointcut:每个类都拥有多个连接点,即连接点是程序类中客观存在的事务。AOP通过切点来定位到特定的连接点,举个例子:连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一关系,一个切点可以匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法来作为连接点的查询条件;

AspectJ是JAVA社区中最完整也是最流行的AOP框架,在Spring2.0以上版本中,可以使用基于AspectJ注解或者基于XML配置的AOP。

基于注解来配置AOP-知识点1:5个通知开发流程以及5个通知各自作用

要在Spring应用中使用AspectJ注解,需要经过如下步骤:

①在classpath下包含AspectJ类库:aopalliance.jar
/aspectj.weaver.jar/spring-aspects.jar,例如从AspectJ官网https://www.eclipse.org/aspectj/downloads.php下载对应的jar包,解压jar后在lib目录下找到上述的3个文件, 然后在idea中配置:File-Project Structure-Modules-dependencies-jars or directories

②将aopSchema添加到<beans/>根元素中;

③在<beans/>配置文件中定义一个空的XML元素<aop:aspectj-autoproxy/>标签,这样当Spring IOC容器检测到配置文件中的该aspectj标签时,会自动为那些与AspectJ切面匹配的Bean类来创建代理;

④把某个类声明为一个切面:首先把这个类放到IOC容器中,即使用@Component注解来标注,然后再声明为一个切面,即使用@Aspect注解;

⑤根据业务需要,选择下面某种通知:

@Before(前置通知,在方法执行之前执行)

@After(后置通知,方法执行之后执行,无论方法是否发生异常)

@AfterRunning(返回通知,在方法正常执行结束、返回结果之后执行,该通知可以访问方法所返回的结果)

@AfterThrowing(异常通知,在方法抛出异常之后执行,该通知可以访问方法发生的异常,且可以指定在出现特定异常时再去执行通知)

@Around(环绕通知,围绕着方法执行);

@Component

@Aspect

public class LoggingAspect {

    /**

     * 前置通知,在方法执行之前执行

     * */

    @Before("execution(public com.suntj.spring.aop.Org com.suntj.spring.aop.impl.OrgService.getOrgByName(java.lang.String))")

    public void beforeMethod(

            JoinPoint joinpoint){

        System.out.println("beforeMethod "

                + "method name = "

                + joinpoint.getSignature().getName()

                + ", method args = "

                + Arrays.asList(joinpoint.getArgs()).toString());

    }

    /**

     * 后置通知,方法执行之后执行,无论方法是否发生异常

     * */

    @After("execution(public com.suntj.spring.aop.Org com.suntj.spring.aop.impl.OrgService.getOrgByName(java.lang.String))")

    public void afterMethod(JoinPoint joinpoint){

        System.out.println("afterMethod method name = "

                + joinpoint.getSignature().getName() + " end");

    }

    /**

     * 返回通知,在方法正常执行结束、返回结果之后执行,该通知可以访问方法所返回的结果

     * */

    @AfterReturning(

            value="execution(public com.suntj.spring.aop.Org com.suntj.spring.aop.impl.OrgService.getOrgByName(java.lang.String))",

            returning = "result")

    public void afterReturningMethod(JoinPoint joinpoint,Object result){

        System.out.println("afterReturningMethod method name = "

                + joinpoint.getSignature().getName() + " end with " + result);

    }

    /**

     * 异常通知,在方法抛出异常之后执行,该通知可以访问方法发生的异常,且可以指定在出现特定异常时再去执行通知

     * */

    @AfterThrowing(

            value="execution(public com.suntj.spring.aop.Org com.suntj.spring.aop.impl.OrgService.getOrgByName(java.lang.String))",

            throwing = "ex")

    public void afterThrowingMethod(JoinPoint joinpoint, NullPointerException ex){

        System.out.println("afterThrowingMethod method name = "

                + joinpoint.getSignature().getName()

                + " exception with" + ex);

    }

 

    /**

     * 环绕通知,围绕着方法执行

     * 该通知必须使用ProceedingJoinPoint类型的参数,环绕通知可以看做是:前置 + 后置 + 返回 + 异常

     * ProceedingJoinPoint可以决定是否执行目标方法,且环绕通知必须有返回值,返回值为目标方法的返回值

     * */

    @Around("execution(public com.suntj.spring.aop.Org com.suntj.spring.aop.impl.OrgService.getOrgByName(java.lang.String))")

    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint ){

        Object result = null;

        try {

            // 执行前置通知

            System.out.println("aroundMethod beforeMethod "

                    + "method name = "

                    + proceedingJoinPoint.getSignature().getName()

                    + ", method args = "

                    + Arrays.asList(proceedingJoinPoint.getArgs()).toString());

            // 执行目标方法

            result = proceedingJoinPoint.proceed();

            // 返回通知

            System.out.println("aroundMethod afterReturningMethod method name = "

                    + proceedingJoinPoint.getSignature().getName() + " end with " + result);

        }catch (Throwable throwable) {

//            throwable.printStackTrace();

            // 异常通知

            System.out.println("aroundMethod afterThrowingMethod method name = "

                    + proceedingJoinPoint.getSignature().getName()

                    + " exception with" + throwable);

        }finally {

            // 后置通知

            System.out.println("aroundMethod afterMethod method name = "

                    + proceedingJoinPoint.getSignature().getName() + " end");

        }

        return result;

    }

}

aroundMethod beforeMethod method name = getOrgByName, method args = [102100088885]

beforeMethod method name = getOrgByName, method args = [102100088885]

............OrgService.getOrgByName start

............OrgService.getOrgByName end

aroundMethod afterReturningMethod method name = getOrgByName end with Org{orgCode='null', orgName='102100088885', areaCode='null', telphone='null'}

aroundMethod afterMethod method name = getOrgByName end

afterMethod method name = getOrgByName end

afterReturningMethod method name = getOrgByName end with Org{orgCode='null', orgName='102100088885', areaCode='null', telphone='null'}

............OrgService.getOrgByName return

基于注解来配置AOP-知识点2:切面优先级

当多个切面,其内部AspectJ表达式关注的切点是同一个时,可以通过@Order(数字)注解来表示优先级,其中数字值越小,其切面优先级越高,越优先执行。

基于注解来配置AOP-知识点3:复用AspectJ表达式

如果想复用现有的AspectJ表达式,需要如下步骤:

①定义一个空方法,并且使用@Pointcut注解来定义表达式;

②在本文件/其他包路径下直接使用方法名来引用当前的切入点表达式;

@Component

@Aspect

@Order(1)

public class HeightAspect {

 

    @Pointcut("execution(public com.suntj.spring.aop.Org com.suntj.spring.aop.impl.OrgService.getOrgByName(java.lang.String))")

    public void AspectJPointCut(){}

 

    /**

     * 前置通知,在方法执行之前执行

     * */

    @Before("com.suntj.spring.aop.aspect.HeightAspect.AspectJPointCut()")

    // 同一个文件下可以这么写

//    @Before("AspectJPointCut()")

    public void beforeMethod(

            JoinPoint joinpoint){

        System.out.println("HeightAspect beforeMethod "

                + "method name = "

                + joinpoint.getSignature().getName()

                + ", method args = "

                + Arrays.asList(joinpoint.getArgs()).toString());

    }

}

基于XML来配置AOP

<?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:util="http://www.springframework.org/schema/util"

       xmlns:p="http://www.springframework.org/schema/p"

       xmlns:context="http://www.springframework.org/schema/context"

       xmlns:aop="http://www.springframework.org/schema/aop"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

       http://www.springframework.org/schema/beans/spring-beans.xsd

       http://www.springframework.org/schema/util

   http://www.springframework.org/schema/util/spring-util-3.0.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context-4.0.xsd

       http://www.springframework.org/schema/aop

       http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

 

    <bean id="org" class="com.suntj.spring.aop.Org"></bean>

    <bean id="orgServiceByXML" class="com.suntj.spring.aop.impl.OrgServiceByXML"

          p:org-ref="org"></bean>

 

    <!-- 配置切面bean -->

    <bean id="loggingAspectByXML" class="com.suntj.spring.aop.aspect.LoggingAspectByXML"></bean>

 

    <!-- 配置AOP -->

    <aop:config>

        <!-- 配置切点表达式 -->

        <aop:pointcut id="getOrgByName"

                      expression="execution(public com.suntj.spring.aop.Org com.suntj.spring.aop.impl.OrgServiceByXML.getOrgByName(java.lang.String))"/>

        <!-- 配置切面及通知 -->

        <aop:aspect ref="loggingAspectByXML" order="1">

            <aop:before method="beforeMethod" pointcut-ref="getOrgByName"/>

            <aop:after method="afterMethod" pointcut-ref="getOrgByName"/>

            <aop:after-throwing method="afterThrowingMethod" pointcut-ref="getOrgByName" throwing="ex"/>

            <aop:after-returning method="afterReturningMethod" pointcut-ref="getOrgByName" returning="result"/>

            <aop:around method="aroundMethod" pointcut-ref="getOrgByName"/>

        </aop:aspect>

    </aop:config>

</beans>