Spring中AOP详解和总结

一、前言

在上一篇中,介绍了使用代理来实现日志的记录,该方法在平时工作中不易于使用,因为要有一定的设计模式的基础。下面就来介绍下Spring的一个非常核心的概念AOP,即面向切面编程。

二、AOP术语说明

为了理解AOP,必须先了解AOP的相关术语:

1、通知(Advice):

在AOP中,切面的工作被称为通知。通知定义了切面“是什么”以及“何时”使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。
Spring切面可以应用5种类型的通知:

(1)前置通知(Before):前置通知, 在方法执行之前执行;
(2)后置通知(After):后置通知, 在方法执行之后执行 ;
(3)返回通知(After-returning):返回通知, 在方法返回结果之后执行;
(4)异常通知(After-throwing):异常通知, 在方法抛出异常之后;
(5)环绕通知(Around):环绕通知, 围绕着方法执行;
2、连接点(Join point):

连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加行为。

3、切点(Pointcut):

如果说通知定义了切面“是什么”和“何时”的话,那么切点就定义了“何处”。比如我想把日志引入到某个具体的方法中,这个方法就是所谓的切点。

4、切面(Aspect):

切面是通知和切点的结合。通知和切点共同定义了切面的全部内容———它是什么,在何时和何处完成其功能。

三、使用基于注解的方式实现AOP

1、需要引入相关的jar,使用maven,此处略;
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
2、在Spring的配置文件中加入 aop 的命名空间,此处略;
3、需要在Spring的配置文件中,加入如下配置:
<!-- 自动扫描的包 -->
<context:component-scan base-package="com.scorpios.spring.aop.impl"></context:component-scan>

<!-- 使 AspectJ 的注解起作用 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

组件扫描(component scanning):Spring 能够从classpath下自动扫描, 侦测和实例化具有特定注解的组件。

特定组件包括:

@Component: 基本注解, 标识了一个受Spring管理的组件
@Respository: 标识持久层组件
@Service: 标识服务层(业务层)组件
@Controller: 标识表现层组件

对于扫描到的组件, Spring有默认的命名策略: 使用非限定类名, 第一个字母小写。 也可以在注解中通过value属性值标识组件的名称

3、编写业务方法接口
public interface ArithmeticCalculator {

    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i, int j);
    int div(int i, int j);
}
4、实现业务方法 (注意此处Component注解)
@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    @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;
    }
}
5、 编写切面类:(是一般的Java类,在其中添加要额外实现的功能,注意注解)
@Order(2)
@Aspect
@Component
public class LoggingAspect {

    /**
     * 定义一个方法,用于声明切入点表达式,一般的,该方法中不再需要其他的代码 使用@Pointcut来声明切入点表达式
     * 后面的其他通知直接使用方法名来引用当前的切入点表达式。
     */
    @Pointcut("execution(public * com.scorpios.spring.aop.impl.ArithmeticCalculator.*(..))")
    public void declareJoinPointExpression() {
    }

    // 声明该方法是一个前置通知:在目标方法开始之前执行
    @Before("declareJoinPointExpression()")
    public void BeforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("[BeforeMethod] the method " + methodName + " begins with " + args);
    }

    // 后置通知:在目标方法执行后(无论是否发生异常),执行的通知
    // 在后置通知中还不能访问目标方法执行的结果
    @After("declareJoinPointExpression()")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("[afterMethod] the method " + methodName + " ends.");
    }

    // 在方法正常结束时执行的代码
    // 返回通知时可以访问到方法的返回值的!
    @AfterReturning(value = "declareJoinPointExpression()", returning = "result")
    public void afterReturing(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("[afterReturing] the method " + methodName + " ends with :" + result);
    }

    // 在目标方法出现异常时会执行的代码
    // 可以访问到异常对象;且可以指定在出现特定异常时在执行通知代码
    @AfterThrowing(value = "declareJoinPointExpression()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("[afterThrowing] the method " + methodName + " occurs with :" + ex);
    }

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

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

        try {
            // 前置通知
            System.out.println(
                    "[aroundMethod before] the method " + methodName + " begins with " + Arrays.asList(pjp.getArgs()));
            result = pjp.proceed();
            // 返回通知
            System.out.println("[aroundMethod returning] the method ends with " + result);
        } catch (Throwable e) {
            // 异常通知
            System.out.println("[aroundMethod exception] the method " + methodName + "occurs exception:" + e);
        }

        // 后置通知
        System.out.println("[aroundMethod after] the method " + methodName + " ends");
        return result;
    }


}
6、测试代码
public class Main {

    public static void main(String[] args) {

        ApplicationContext apx = new ClassPathXmlApplicationContext("applicationContext.xml");

        ArithmeticCalculator arithmeticCalculator = apx.getBean(ArithmeticCalculator.class);

        int result = arithmeticCalculator.add(2, 8);
        System.out.println("-->" + result);
    }
}
7、运行结果

这里写图片描述

8、具有优先级的切面类(注意切入点表达式,引用LoggingAspect中定义的表达式)
@Order(1)
@Aspect
@Component
public class ValidateAspect {

    // 声明该方法是一个前置通知:在目标方法开始之前执行
    @Before("LoggingAspect.declareJoinPointExpression()")
    public void ValidateBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("**** validate  " + args);
    }

}
9、再次执行测试代码,结果如下

这里写图片描述

四、总结

1、引入jar包;

2、在Spring的配置文件中加入aop的命名空间。

3、在配置文件中配置自动扫描的包:

<context:component-scan base-package="com.scorpios.spring.aop.*"></context:component-scan>

4、加入使 AspjectJ 注解起作用的配置:

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>为匹配的类自动生成动态代理对象.

5、编写切面类:

6、配置切面

a.切面必须是IOC中的bean: 实际添加了@Component注解 
b.声明是一个切面: 通过添加@Aspect注解声明一个bean是一个切面!
c.声明通知: 即额外加入功能对应的方法. 
    前置通知: @Before("execution(public int com.scorpios.spring.aop.ArithmeticCalculator.*(int, int))")

@Before 表示在目标方法执行之前执行 @Before 标记的方法的方法体.
@Before 里面的是切入点表达式:

7、在通知中访问连接细节: 可以在通知方法中添加 JoinPoint 类型的参数, 从中可以访问到方法的签名和方法的参数.

五、补充:

关于切入点表达式的解释:
这里写图片描述
如图所示,我们使用execution()指示器选择UserServiceImpl的sayHello方法。方法表达式以“*”号开始,表明了我们不关心方法返回值的类型。然后,我们指定了全限定类名和方法名。对于方法参数列表,我们使用两个点号(..)表明切点要选择任意的sayHello()方法,无论该方法的入参是什么。

猜你喜欢

转载自blog.csdn.net/zxd1435513775/article/details/80764753
今日推荐