Spring中很强大的两种特性就是IOC和AOP,相信很多同学对此也比较了解,尤其是当面试的时候,通常也会被问及。这里,我就再简单介绍下Spring如何实现基于注解的AOP示例。

一、Spring AOP概述

AOP:【动态代理】

指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;

1、导入aop模块;Spring AOP:(spring-aspects)

2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)

3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行;

通知方法:

  • 前置通知(@Before):logStart:在目标方法(div)运行之前运行
  • 后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
  • 返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
  • 异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
  • 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())

4、给切面类的目标方法标注何时何地运行(通知注解);

5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中;

6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)

7、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】

实现AOP编程三步:

  • 将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
  • 在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
  • 开启基于注解的aop模式;@EnableAspectJAutoProxy

二、创建项目

创建Maven项目Spring-Learning,并编辑pom.xml文件,如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>io.binghe.spring</groupId>
<artifactId>Spring-Learning</artifactId>
<version>1.0.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

三、业务实现

1.定义业务类

在项目的io.binghe.spring.aop包下创建MatchCalculator类。此类模拟的是实际的业务场景。这里,为了简单,我在这个类中就直接定义了一个除法方法,输入两个int类型的参数,返回除法的结果数据。同时,在方法中打印相关的信息,代码如下所示。

package io.binghe.spring.aop;

/**
* @author binghe
* @version 1.0.0
* @description 模拟业务类
*/
public class MatchCalculator {

public int div(int i, int j){
System.out.println("MatchCalculator...div...");
return i / j;
}
}

2.定义切面类LogAspect

在项目的io.binghe.spring.aop包下创建LogAspect类,此类为MatchCalculator类对应的切面类,代码如下所示。

package io.binghe.spring.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;

import java.util.Arrays;

/**
* @author binghe
* @version 1.0.0
* @description Spring的切面类
*/

@Aspect
public class LogAspect {

@Pointcut("execution(public int io.binghe.spring.aop.MatchCalculator.*(..))")
public void pointCut(){}

@Before("pointCut()")
public void logStart(JoinPoint joinPoint){
System.out.println(joinPoint.getSignature().getName() + "运行...参数列表是:{"+ Arrays.asList(joinPoint.getArgs()) +"}");
}

@After("pointCut()")
public void logEnd(JoinPoint joinPoint){
System.out.println(joinPoint.getSignature().getName() + "结束...");
}

@AfterReturning(value = "pointCut()", returning = "result")
public void logReturn(JoinPoint joinPoint, Object result){
System.out.println(joinPoint.getSignature().getName() + "正常返回...运行结果:{"+result+"}");
}

@AfterThrowing(value = "pointCut()", throwing = "e")
public void logException(JoinPoint joinPoint, Exception e){
System.out.println(joinPoint.getSignature().getName() + "异常...异常信息:{"+e+"}");
}
}

其中:

当类中有多个方法定义的切面相同时,可以单独定义一个方法,不必写方法体,统一设置@Pointcut。如代码中的

@Pointcut("execution(public int io.binghe.spring.aop.MatchCalculator.*(..))")
public void pointCut(){}

execution(public int io.binghe.spring.aop.MatchCalculator.*(..))代表的是对io.binghe.spring.aop.MatchCalculator中的所有public类型,返回值为int类型的方法起作用,不管方法是否有参数。

当单独指定了一个方法设置好@Pointcut后,类中的其他方法如果要跟单独指定的方法设置相同的切面时,只需要引用设置@Pointcut方法的名字即可。如下代码所示。

@Before("pointCut()")
public void logStart(JoinPoint joinPoint){
System.out.println(joinPoint.getSignature().getName() + "运行...参数列表是:{"+ Arrays.asList(joinPoint.getArgs()) +"}");
}

在实现Spring的切面类时,可以在方法中增加JoinPoint类型的参数接收实际业务的方法名称和参数列表等信息。如下代码所示

@Before("pointCut()")
public void logStart(JoinPoint joinPoint){
System.out.println(joinPoint.getSignature().getName() + "运行...参数列表是:{"+ Arrays.asList(joinPoint.getArgs()) +"}");
}

注意:无论切面类中的方法有多少个参数,JoinPoint类型的参数必须是第一个参数,否则Spring将无法识别。

还可以在切面类的方法中接收业务类方法的返回结果和异常信息,如下所示。

@AfterReturning(value = "pointCut()", returning = "result")
public void logReturn(JoinPoint joinPoint, Object result){
System.out.println(joinPoint.getSignature().getName() + "正常返回...运行结果:{"+result+"}");
}

@AfterThrowing(value = "pointCut()", throwing = "e")
public void logException(JoinPoint joinPoint, Exception e){
System.out.println(joinPoint.getSignature().getName() + "异常...异常信息:{"+e+"}");
}

最后,需要注意的是需要在类上,增加@Aspect注解,注明此类是AOP切面类。如下所示

@Aspect
public class LogAspect {

3.定义Spring配置类MainConfigOfAOP

在io.binghe.spring.config包下创建MainConfigOfAOP类,以@Bean注解的方式将业务类MatchCalculator和切面类LogAspect添加到Spring的容器中,需要注意的是需要在配置类MainConfigOfAOP上添加@EnableAspectJAutoProxy注解。代码如下所示。

package io.binghe.spring.config;

import io.binghe.spring.aop.LogAspect;
import io.binghe.spring.aop.MatchCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
* @author binghe
* @version 1.0.0
* @description Spring配置类
*/
@Configuration
@EnableAspectJAutoProxy
public class MainConfigOfAOP {

@Bean
public MatchCalculator matchCalculator(){
return new MatchCalculator();
}

@Bean
public LogAspect logAspect(){
return new LogAspect();
}
}

4.创建测试类IOCTestAOP

此类比较简单,不再赘述,代码如下所示。

package io.binghe.spring.test;

import io.binghe.spring.aop.MatchCalculator;
import io.binghe.spring.config.MainConfigOfAOP;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
* @author binghe
* @version 1.0.0
* @description AOP实例测试类
*/
public class IOCTestAOP {

private AnnotationConfigApplicationContext applicationContext = null;

@Before
public void initTest(){
applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
}

@Test
public void test01(){
MatchCalculator calculator = applicationContext.getBean(MatchCalculator.class);
calculator.div(1, 0);
}
}