转载请注明出处: http://qiudeqing.com/java_web/2016/10/02/spring-aop.html

日志, 安全, 事物管理, 缓存这样多个模块都需要的功能应该抽离出来, Spring aop就是为这个工作准备.

AOP允许模块不进行任何修改的情况下添加所需功能.

术语

Spring AOP概述

Spring AOP有以下形式

Spring AOP基于动态代理并且限制范围为方法拦截.

通过Spring的aop命名空间, 你可以将POJO转化为切面. POJO只能提供方法支持. 必须使用XML配置.

如果你的AOP需求超过了简单的方法拦截(例如构造函数或者属性拦截), 你需要实现AspectJ切面. 也就是上面的第四种方法.

实现AOP

1. pom.xml添加依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.qiudeqing</groupId>
  <artifactId>spring-aop</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>spring-aop Maven Webapp</name>
  <url>http://maven.apache.org</url>

    <properties>
        <spring.version>4.1.7.RELEASE</spring.version>
        <junit.version>4.12</junit.version>

    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.5.4</version>
        </dependency>
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.5.4</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
  <build>
    <finalName>spring-aop</finalName>
  </build>
</project>

2. 定义正常业务逻辑

SimpleService接口

package com.qiudeqing.service;

/**
 * Created by qiudeqing on 16/10/14.
 */
public interface SimpleService {
    public void printNameId();
    public void checkName();
    public String sayHello(String message);
}

SimpleServiceImpl实现

package com.qiudeqing.service;

/**
 * Created by qiudeqing on 16/10/14.
 */
public class SimpleServiceImpl implements SimpleService {
    private String name;
    private int id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void printNameId() {
        System.out.println("SimpleService :method printNamedId(): My name is " + name + " my id is " + id);
    }

    public void checkName() {
        if (name.length() < 20) {
            throw new IllegalArgumentException();
        }
    }

    public String sayHello(String message) {
        System.out.println("SimpleService : Method sayHello() : Hello ! " + message);
        return message;
    }
}

3. 创建各种所需的aspect

使用AspectJ提供的各种注解拦截各种业务方法, @Aspect注解的类叫做aspect, aspect包含pointcut, advice, introduction声明.

有一下五种类型的advice:

3.1 @Before

设置advice为@Before将会在join point之前执行Aspect

package com.qiudeqing.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * Created by qiudeqing on 16/10/13.
 */
@Aspect
public class DoBeforeAspect {

    @Before("execution(* com.qiudeqing.service.SimpleService.sayHello(..))")
    public void doBefore(JoinPoint joinPoint) {
        System.out.println("***AspectJ*** DoBefore() is running!! interceped: " + joinPoint.getSignature().getName());
    }
}

applicationContext.xml声明aspect bean并且启用aop:aspectj-autoproxy

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/jee
        http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
        http://www.springframework.org/schema/task
        http://www.springframework.org/schema/task/spring-task-3.2.xsd">

    <aop:aspectj-autoproxy />

    <bean id="simpleServiceBean" class="com.qiudeqing.service.SimpleServiceImpl">
        <property name="name" value="Hello" />
        <property name="id" value="12345" />
    </bean>

    <bean id="doBeforeAspect" class="com.qiudeqing.aspect.DoBeforeAspect" />

</beans>

添加junit测试, 可以看到sayHello()执行前执行了doBefore Aspect

package com.qiudeqing.aspect;

import com.qiudeqing.service.SimpleService;
import org.junit.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by qiudeqing on 16/10/14.
 */
public class DoBeforeTest {

    @Test
    public void doBeforeTest() {
        ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        SimpleService simpleService = (SimpleService)context.getBean("simpleServiceBean");
        simpleService.printNameId();
        System.out.println("---------------------------");
        try {
            simpleService.checkName();
        } catch (Exception e) {
            System.out.println("SimpleService checkName() : Exception thrown..");
        }
        System.out.println("-----------------");
        simpleService.sayHello("javacodegeeks");
        System.out.println("---------------");
        context.close();
    }

}

结果:

SimpleService :method printNamedId(): My name is Hello my id is 12345
---------------------------
SimpleService checkName() : Exception thrown..
-----------------
***AspectJ*** DoBefore() is running!! interceped: sayHello
SimpleService : Method sayHello() : Hello ! javacodegeeks
---------------

3.2 @After

@After类型的advice将在join point执行之后执行. 例如: 如果join point是函数执行, @After advice会在方法返回后或者抛出异常后执行.

DoAfterAspect.java

package com.qiudeqing.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;

/**
 * Created by qiudeqing on 16/10/14.
 */
@Aspect
public class DoAfterAspect {

    @After("execution(* com.qiudeqing.service.SimpleService.sayHello(..))")
    public void doAfter(JoinPoint joinPoint) {
        System.out.println("***AspectJ*** DoAfter() is running!! interceped: " + joinPoint.getSignature().getName());
    }
}

applicationContext.xml中添加bean配置

<bean id="doAfterAspect" class="com.qiudeqing.aspect.DoAfterAspect" />

此时再次运行前面的doBeforeTest会自动执行doAfterAspect配置的方法.

执行结果:

SimpleService :method printNamedId(): My name is Hello my id is 12345
---------------------------
SimpleService checkName() : Exception thrown..
-----------------
***AspectJ*** DoBefore() is running!! interceped: sayHello
SimpleService : Method sayHello() : Hello ! javacodegeeks
***AspectJ*** DoAfter() is running!! interceped: sayHello
---------------

3.3 @AfterReturning

@AfterReturn类型的aspect会在方法返回值之后执行, aspect可以获取方法返回值

DoAfterReturningAspect.java

package com.qiudeqing.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

/**
 * Created by qiudeqing on 16/10/14.
 */
@Aspect
public class DoAfterReturningAspect {

    @AfterReturning(pointcut = "execution(* com.qiudeqing.service.SimpleService.sayHello(..))", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("***AspectJ*** DoAfterReturning() is running!! intercepted : " + joinPoint.getSignature().getName());
        System.out.println("Method returned value is : " + result);
    }
}

applicationContext.xml配置:

<bean id="doAfterReturningAspect" class="com.qiudeqing.aspect.DoAfterReturningAspect" />

再次运行doBeforeText会自动在函数返回之后执行aspect:

SimpleService :method printNamedId(): My name is Hello my id is 12345
---------------------------
SimpleService checkName() : Exception thrown..
-----------------
***AspectJ*** DoBefore() is running!! interceped: sayHello
SimpleService : Method sayHello() : Hello ! javacodegeeks
***AspectJ*** DoAfterReturning() is running!! intercepted : sayHello
Method returned value is : javacodegeeks
***AspectJ*** DoAfter() is running!! interceped: sayHello
---------------

3.4 @AfterThrowing

@AfterThrowing类型的Aspect在方法抛出异常时执行.

DoAfterThrowingAspect.java

package com.qiudeqing.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;

/**
 * Created by qiudeqing on 16/10/18.
 */
@Aspect
public class DoAfterThrowingAspect {

    @AfterThrowing(
            pointcut = "execution(* com.qiudeqing.service.SimpleService.checkName(..))",
            throwing = "error"
    )
    public void doAfterThrowing(JoinPoint joinPoint, Throwable error) {
        System.out.println("***AspectJ*** DoAfterThrowing() is running!! intercepted : " + joinPoint.getSignature().getName());
        System.out.println("Exception : " + error);
        System.out.println("******");
    }
}

applicationContext.xml配置

<bean id="doAfterThrowingAspect" class="com.qiudeqing.aspect.DoAfterThrowingAspect" />

再次运行结果:

SimpleService :method printNamedId(): My name is Hello my id is 12345
---------------------------
***AspectJ*** DoAfterThrowing() is running!! intercepted : checkName
Exception : java.lang.IllegalArgumentException
******
SimpleService checkName() : Exception thrown..
-----------------
***AspectJ*** DoBefore() is running!! interceped: sayHello
SimpleService : Method sayHello() : Hello ! javacodegeeks
***AspectJ*** DoAfterReturning() is running!! intercepted : sayHello
Method returned value is : javacodegeeks
***AspectJ*** DoAfter() is running!! interceped: sayHello
---------------

3.5 @Around

环绕通知(Around Advice)是包围一个连接点的通知,如方法调用.这是最强大的一种通知类型. 环绕通知可以再方法调用前后完成自定义的行为.

它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行.

环绕通知时最常用的通知类型, 和AspectJ一样, S热评提供所有类型的通知, 我们推荐使用尽可能简单的通知类型来实现所需功能.例如你只是需要一个方法的返回值来更新焕春, 最好使用后置通知而不是环绕通知.

DoAroundAspect.java

package com.qiudeqing.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Arrays;


/**
 * Created by qiudeqing on 16/10/18.
 */
@Aspect
public class DoAroundAspect {
    @Around("execution(* com.qiudeqing.service.SimpleService.sayHello(..))")
    public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("***AspectJ*** DoAround() is running!! interceped : " + joinPoint.getSignature().getName() +
            " \narguments : " + Arrays.toString(joinPoint.getArgs()));
        System.out.println("***AspectJ*** DoAround() before is running!");
        joinPoint.proceed();
        System.out.println("***AspectJ*** DoAround() after is running!");
    }
}

applicationContext.xml

<bean id="doAroundAspect" class="com.qiudeqing.aspect.DoAroundAspect" />

运行结果:

SimpleService :method printNamedId(): My name is Hello my id is 12345
---------------------------
***AspectJ*** DoAfterThrowing() is running!! intercepted : checkName
Exception : java.lang.IllegalArgumentException
******
SimpleService checkName() : Exception thrown..
-----------------
***AspectJ*** DoBefore() is running!! interceped: sayHello
***AspectJ*** DoAround() is running!! interceped : sayHello
arguments : [javacodegeeks]
***AspectJ*** DoAround() before is running!
SimpleService : Method sayHello() : Hello ! javacodegeeks
***AspectJ*** DoAround() after is running!
***AspectJ*** DoAfterReturning() is running!! intercepted : sayHello
Method returned value is : null
***AspectJ*** DoAfter() is running!! interceped: sayHello
---------------

参考资料