Spring--AOP

Posted Wayfo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring--AOP相关的知识,希望对你有一定的参考价值。

AspectJ:Java 社区里最完整最流行的 AOP 框架.

在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP

1).加入jar包

Spring 中启用 AspectJ 注解支持
org.aspectjweaver-1.8.9.RELEASE.jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar

commons-logging-1.1.1.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar

2).在配置文件中加入aop的命名空间

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

3).基于注解的方式

①.在配置文件中加入如下配置

<aop:aspectj-autoproxy/>

当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.

②.创建切面类
切面类要被IOC容器管理,加入@Component注解;切面还需要加入@Aspect注解

用 AspectJ 注解声明切面,要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理.在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.

③.在切面类中声明各种通知

通知是标注有某种注解的简单的 Java 方法.
AspectJ 支持 5 种类型的通知注解:

@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行 
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行

i.前置通知:
在方法执行之前执行的通知
前置通知使用 @Before 注解, 并将切入点表达式的值作为注解值

ii.后置通知
后置通知在目标方法执行后(无论是否发生异常),执行的通知

iii.返回通知
无论连接点是正常返回还是抛出异常, 后置通知都会执行. 如果只想在连接点正常返回的时候记录日志,应使用返回通知代替后置通知在返回通知中, 只要将 returning 属性添加到@AfterReturning 注解中, 就可以访问连接点的返回值. 该属性的值即为用来传入返回值的参数名称. 必须在通知方法的签名中添加一个同名参数. 在运行时, Spring AOP 会通过这个参数传递返回值.

iiii.异常通知
只在连接点抛出异常时才执行异常通知
将 throwing 属性添加到 @AfterThrowing 注解中, 也可以访问连接点抛出的异常. Throwable 是所有错误和异常类的超类. 所以在异常通知方法可以捕获到任何错误和异常.
如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行.

iiiii.环绕通知

环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点.对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.

注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常

④.Demo

package yang.mybatis.test.AOP;

public interface Calculator {

    int add(int i, int j);
    int sub(int i, int j);
    
    int mul(int i, int j);
    int div(int i, int j);
    
}

 目标对象(Target)

package yang.mybatis.test.AOP;

import org.springframework.stereotype.Component;

@Component("calculator")
public class CalculatorImpl implements Calculator {

    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }

}

 切面(Aspect)

package yang.mybatis.test.AOP;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 1. 加入 jar 包
 * org.aspectj.weaver-1.6.8.RELEASE.jar
 * spring-aspects-4.0.0.RELEASE.jar
 * 
 * 2. 在 Spring 的配置文件中加入 aop 的命名空间。 
 * 
 * 3. 基于注解的方式来使用 AOP
 * 3.1 在配置文件中配置自动扫描的包: <context:component-scan base-package="..."></context:component-scan>
 * 3.2 加入使 AspjectJ 注解起作用的配置: <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
 * 为匹配的类自动生成动态代理对象. 
 * 
 * 4. 编写切面类: 
 * 4.1 一个一般的 Java 类
 * 4.2 在其中添加要额外实现的功能. 
 *
 * 5. 配置切面
 * 5.1 切面必须是 IOC 中的 bean: 实际添加了 @Component 注解
 * 5.2 声明是一个切面: 添加 @Aspect
 * 5.3 声明通知: 即额外加入功能对应的方法. 
 * 5.3.1 前置通知: @Before("execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))")
 * @Before 表示在目标方法执行之前执行 @Before 标记的方法的方法体. 
 * @Before 里面的是切入点表达式: 
 * 
 * 6. 在通知中访问连接细节: 可以在通知方法中添加 JoinPoint 类型的参数, 从中可以访问到方法的签名和方法的参数. 
 * 
 * 7. @After 表示后置通知: 在方法执行之后执行的代码. 
 */

//通过添加 @Aspect 注解声明一个 bean 是一个切面!
@Aspect
@Component
public class LoggingAspect {
    //利用方法签名编写 AspectJ 切入点表达式
    @Before("execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))")
    //可以在通知方法中声明一个类型为 JoinPoint 的参数. 然后就能访问链接细节. 如方法名称和参数值.
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object [] args = joinPoint.getArgs();
        
        System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
    }
    //在后置通知中,因为方法可能会出异常,所以不能访问目标方法的执行的结果
    @After("execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends");
    }
    //返回通知,无异常时执行,返回通知是可以访问到方法的返回值的
    @AfterReturning(value = "execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int)))",
    returning = "result")
    public void afterReturnMethod(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends with "+result);
    }
    //在目标方法出现异常时会执行的代码
    //可以访问到异常对象;且可以指定在出现特定异常时执行通知代码
    @AfterThrowing(value = "execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int)))",
            throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Exception ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " occurs exception with "+ex);

    }

    /**
     * 环绕通知需要携带 ProceedingJoinPoint 类型的参数.
     * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
     * 且环绕通知必须有返回值, 返回值即为目标方法的返回值
     */
/*    @Around("execution(public int public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))")
    public Object aroundMethod(ProceedingJoinPoint pjd){

        Object result = null;
        String methodName = pjd.getSignature().getName();

        try {
            //前置通知
            System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
            //执行目标方法
            result = pjd.proceed();
            //返回通知
            System.out.println("The method " + methodName + " ends with " + result);
        } catch (Throwable e) {
            //异常通知
            System.out.println("The method " + methodName + " occurs exception:" + e);
            throw new RuntimeException(e);
        }
        //后置通知
        System.out.println("The method " + methodName + " ends");

        return result;
    }*/

}

 

      ApplicationContext applicationContext = 

          new ClassPathXmlApplicationContext("applicationContext.xml"); Calculator calculator = (Calculator) applicationContext.getBean("calculator"); System.out.println(calculator.getClass().getName()); int result = calculator.add(11, 12); System.out.println("result:" + result); result = calculator.div(21,1); System.out.println("result:" + result);

 4).指定切面的优先级

在同一个连接点上应用不止一个切面时, 除非明确指定, 否则它们的优先级是不确定的.
切面的优先级可以通过利用 @Order 注解指定.值越小, 优先级越高.

@Order(0)    
@Aspect
@Component
public class LoggingAspect {
}
@Order(1)    
@Aspect
@Component
public class ValidateAspect {
}
同一个连接点上LoggingAspect 切面先于ValidateAspect切面执行 

5).切入点表达式和重用切入点

典型的切入点表达式时根据方法的签名来匹配各种方法:
execution * com.nchu.spring.Calculator.*(..):
// 匹配 Calculator 中声明的所有方法,第一个 * 代表任意修饰符及任意返回值.
第二个 * 代表任意方法. .. 匹配任意数量的参数. 若目标类与接口与该切面在同一个包中, 可以省略包名.
execution public * ArithmeticCalculator.*(..): // 匹配 Calculator 接口的所有公有方法. execution public double ArithmeticCalculator.*(..): // 匹配 Calculator 中返回 double 类型数值的方法 execution public double ArithmeticCalculator.*(double, ..): //匹配第一个参数为 double 类型的方法, .. 匹配任意数量任意类型的参数 execution public double ArithmeticCalculator.*(double, double): //匹配参数类型为 double, double 类型的方法.

 

在编写 AspectJ 切面时, 可以直接在通知注解中书写切入点表达式. 但同一个切点表达式可能会在多个通知中重复出现.

在 AspectJ 切面中, 可以通过 @Pointcut 注解将一个切入点声明成简单的方法. 切入点的方法体通常是空的, 因为将切入点定义与应用程序逻辑混在一起是不合理的. 切入点方法的访问控制符同时也控制着这个切入点的可见性. 如果切入点要在多个切面中共用, 最好将它们集中在一个公共的类中. 在这种情况下, 它们必须被声明为 public. 在引入这个切入点时, 必须将类名也包括在内. 如果类没有与这个切面放在同一个包中, 还必须包含包名.其他通知可以通过方法名称引入该切入点.

改造后的切面

package yang.mybatis.test.AOP;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;
//通过添加 @Aspect 注解声明一个 bean 是一个切面!
@Order(0)
@Aspect
@Component
public class LoggingAspect {
    /**
     * 该方法用于声明切入点表达式,一般的该方法中不需要添加其他代码
     */
    @Pointcut("execution(public int yang.mybatis.test.AOP.CalculatorImpl.*(int,int))")
    public void pointcut(){}

    /**
     * 利用方法签名编写 AspectJ 切入点表达式
     * @param joinPoint
     */
    @Before("pointcut()")
    //可以在通知方法中声明一个类型为 JoinPoint 的参数. 然后就能访问链接细节. 如方法名称和参数值.
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object [] args = joinPoint.getArgs();
        
        System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
    }

    /**
     * 在后置通知中,因为方法可能会出异常,所以不能访问目标方法的执行的结果
     * @param joinPoint
     */
    @After("pointcut()")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends");
    }

    /**
     * 返回通知,无异常时执行,返回通知是可以访问到方法的返回值的
     * @param joinPoint
     * @param result
     */

    @AfterReturning(value = "pointcut()",
    returning = "result")
    public void afterReturnMethod(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends with "+result);
    }

    /**
     * 在目标方法出现异常时会执行的代码
     *可以访问到异常对象;且可以指定在出现特定异常时执行通知代码
     * @param joinPoint
     * @param ex
     */

    @AfterThrowing(value = "pointcut()",
            throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Exception ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " occurs exception with "+ex);

    }

    /**
     * 环绕通知需要携带 ProceedingJoinPoint 类型的参数.
     * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
     * 且环绕通知必须有返回值, 返回值即为目标方法的返回值
     */
/*    @Around("pointcut()")
    public Object aroundMethod(ProceedingJoinPoint pjd){

        Object result = null;
        String methodName = pjd.getSignature().getName();

        try {
            //前置通知
            System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
            //执行目标方法
            result = pjd.proceed();
            //返回通知
            System.out.println("The method " + methodName + " ends with " + result);
        } catch (Throwable e) {
            //异常通知
            System.out.println("The method " + methodName + " occurs exception:" + e);
            throw new RuntimeException(e);
        }
        //后置通知
        System.out.println("The method " + methodName + " ends");

        return result;
    }*/

}

 6、基于XML的配置

将上文中的两个类中的配置移除,在applicationContext.xml文件中加入以下配置

 

<bean id="calculator" class="yang.mybatis.test.AOP.XML.CalculatorImpl"></bean>
    <bean id="loggingAspect" class="yang.mybatis.test.AOP.XML.LoggingAspect"></bean>
    <!--AOP配置-->
    <aop:config>
        <!-- 配置切点表达式 -->
        <aop:pointcut id="pointcut" expression="execution(public int yang.mybatis.test.AOP.XML.CalculatorImpl.*(..))"></aop:pointcut>
        <!-- 配置切面及通知 -->
        <aop:aspect ref="loggingAspect" order="0">
            <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
            <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
            <aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing>
            <aop:after-returning method="afterReturnMethod" pointcut-ref="pointcut" returning="result"></aop:after-returning>
            <!--<aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>-->
        </aop:aspect>
        <aop:aspect ref="vlidationAspect" order="1">
            <aop:before method="validateArgs" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

 

以上是关于Spring--AOP的主要内容,如果未能解决你的问题,请参考以下文章

Spring AOP

Spring源码高级笔记之——Spring AOP应用

Spring aop 拦截不到Dao

Spring Aop基础总结

Spring AOP示例代码

spring aop配置及实现