一、 使用AOP之前的配置


我使用Spring来完成AOP的配置,AOP和IoC作为Spring两项最主要的特点受到良好的支持,另外使用Maven作为构建工具,在使用AOP前,先加入依赖包,完成pom.xml文件。


<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.8</version>
</dependency>


二、 AOP前置通知


在上一节中已经说到,连接点由表示连接的位置和程序执行点决定,这里首先说明前置通知,即在业务逻辑之前执行的功能。


先配置Spring的配置文件aop.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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        "
        >
    <context:component-scan base-package="com.spring"/>
    <aop:aspectj-autoproxy/>
</beans>

<context:component-scan base-package="**"/>这就不用说了吧,基本上用过Spring的都知道,但是这里有一个问题,是我自己犯傻了,在base-package中的包表示的是一类,即,如上代码中,不仅com.spring包能被扫描到,任何前缀是它的包都能被扫描,所以我把这开始时写的很细,导致切面类没有包含进去,傻傻地还一直在找错误。


<aop:aspectj-autoproxy /> 光是看名字就知道它是什么作用,它使Aspectj的注解起作用,并且自动为满足切点的类生成在理对象,必须配置。


然后建立一个简单的业务功能,例如计算器;


package com.spring.service;

import org.springframework.stereotype.Component;

@Component
public class Calculator {

    public int add(int i, int j){
        return i+j;
    }

    public int sub(int i, int j){
        return i-j;
    }

    public int time(int i, int j){
        return i*j;
    }

    public int divide(int i, int j){
        return i/j;
    }
}

然后新建一个切面类:


@Component
@Aspect
public class LoggingAspect {
    protected Logger logger = LoggerFactory.getLogger(this.getClass());
    /**/
}

切面类必须用@Aspect注解标明这是一个切面类,同时还需要把该切面类放到Spring的IoC容器中,所以这里用@Component注解标识,同时用slf4j来完成日志功能。


下面就是主要的内容,完成前置通知,前置通知就是在目标方法前执行,实现很简单,就是一个注释的使用。除了用注解还有xml配置方式,具体内容看下面的链接。


@Before("execution(public int com.spring.service.Calculator.*(int, int))")
public void loggingBefore(JoinPoint joinPoint){
    logger.info("method begin with name {} and args {}", joinPoint.getSignature().getName(), joinPoint.getArgs());
}

这个@Before和junit@Before千万不能弄混了,每次自动导入时都可能出现这样的问题。


execution(public int com.spring.service.Calculator.*(int, int))



关于切点表达式的内容很多,这里不具体讲,但是用到的最多的就是这里的execution表达式,它用来匹配方法执行的连接点,许多其他的表达式可以在本文下面给出的链接中找。



另外可以通过JoinPoint来访问目标Target信息,包括各种标识符和参数,非常方便。



下面可以对代码进行测试一下:



ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
Calculator service = context.getBean(Calculator.class);
int result = service.add(1,2);
System.out.println(result);



得到结果如下:



spring前置处理器和后置处理器_spring



三、 AOP后置通知


有前置通知,就有后置通知,其他的和前置通知没有什么区别,只是换了一个注解而已,代替@Before使用@After, 后置通知在目标方法执行后 执行,无论是否发生异常都会执行


@After("execution(public int com.spring.service.Calculator.*(int, int))")
public void loggingAfter(JoinPoint joinPoint){
    logger.info("method end");
}

测试代码类似上边如下:


ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
Calculator service = context.getBean(Calculator.class);
int result = service.divide(1,2);
System.out.println(result);

结果如下:




spring前置处理器和后置处理器_spring前置处理器和后置处理器_02



但是又有另外的结果如下:对于这个我不是很明白,是不是后置通知在另外的线程运行,我不是很确定,如果有大神能够指出,感激不尽。



spring前置处理器和后置处理器_maven_03



另外在发生异常时,依然能够完成通知,结果如下:



spring前置处理器和后置处理器_前置通知_04



另外要注意的 在后置通知中不能访问目标方法执行后返回的结果,要实现这一点要看下一节。