目前 Spring AOP 一共有三种配置方式。
- Spring 1.2 基于接口的配置:最早的 Spring AOP是完全基于几个接口的。
- Spring 2.0 schema-based 配置:Spring 2.0以后使用XML的方式来配置,使用 命名空间<aop></aop>
- Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做 @AspectJ,但是这个和AspectJ其实没啥关系。
1. 基于接口的配置
首先先介绍Spring2.0之前的AOP实现方式。
1.1 基于Advice的实现。
先介绍一个最简单的使用,对目标类所有方法均加上前置通知。
pom文件
<?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>org.example</groupId>
<artifactId>Spring</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--spring AOP的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
<!--springIOC的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
</dependencies>
</project>
目标接口
package com.zyz.earlyAOP;
public interface Calculate {
/**
* 加法
* @param numA
* @param numB
* @return
*/
int add(int numA, int numB);
/**
* 减法
* @param numA
* @param numB
* @return
*/
int sub(int numA, int numB);
}
目标类,实现了两个方法
package com.zyz.earlyAOP;
public class TXCalculate implements Calculate {
@Override
public int add(int numA, int numB) {
System.out.println("执行目标方法:add");
return numA + numB;
}
@Override
public int sub(int numA, int numB) {
System.out.println("执行目标方法:sub");
return numA - numB;
}
}
通知类,在执行方法之前,加入前置通知。
package com.zyz.earlyAOP;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import java.util.Arrays;
public class TXAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object object) throws Throwable {
String methodName = method.getName();
System.out.println("执行目标方法【"+methodName+"】的<前置通知>,入参"+ Arrays.asList(args));
}
}
主配置类
package com.zyz.earlyAOP;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.context.annotation.Bean;
public class EarlyAopMainConfig {
@Bean
public Calculate txCalculate(){
return new TXCalculate();
}
@Bean
public TXAdvice txAdvice(){
return new TXAdvice();
}
@Bean
public ProxyFactoryBean calculate(){
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
//选择的Advisor或Advice
proxyFactoryBean.setInterceptorNames("txAdvice");
//目标类
proxyFactoryBean.setTarget(txCalculate());
return proxyFactoryBean;
}
}
启动类
package com.zyz.earlyAOP;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainStat {
public static void main(String[] args) {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(EarlyAopMainConfig.class);
Calculate calculateProxy = ctx.getBean("calculate", Calculate.class);
calculateProxy.add(1,2);
calculateProxy.add(2,1);
}
}
运行结果
执行目标方法【add】的<前置通知>,入参[1, 2]
执行目标方法:add
执行目标方法【sub】的<前置通知>,入参[2, 1]
执行目标方法:sub
1.2 引Advisor
在实际应用中,我们可能需要对一个类的不同方法,进行不同的Advice(增强),那么就需要引入Advisor。
这里只针对add方法进行增强。
修改
package com.zyz.earlyAOP;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.context.annotation.Bean;
public class EarlyAopMainConfig {
@Bean
public Calculate txCalculate(){
return new TXCalculate();
}
@Bean
public NameMatchMethodPointcutAdvisor txAspect(){
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
//选择的通知
advisor.setAdvice(txAdvice());
//选择的方法
advisor.setMappedName("add");
return advisor;
}
@Bean
public ProxyFactoryBean calculate(){
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
//选择的Advisor或Advice
proxyFactoryBean.setInterceptorNames("txAspect");
//目标类
proxyFactoryBean.setTarget(txCalculate());
return proxyFactoryBean;
}
}
启动类
package com.zyz.earlyAOP;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainStat {
public static void main(String[] args) {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(EarlyAopMainConfig.class);
Calculate calculateProxy = ctx.getBean("calculate", Calculate.class);
calculateProxy.add(1,2);
calculateProxy.sub(2,1);
}
}
执行结果
执行目标方法【add】的<前置通知>,入参[1, 2]
执行目标方法:add
执行目标方法:sub
NameMatchMethodPointcutAdvisor类也可以对多个方法进行增强。
advisor.setMappedNames(new String[]{"add","sub"});
但是这里有个问题,就是使用的时候,我们需要获取Calculate的代理类,即
Calculate calculateProxy = ctx.getBean("calculate", Calculate.class);
假定以后有多个代理类,那么代码就会反复修改,所以应该想办法解决问题。
1.3 Autoproxy
好在贴心的Spring提供了自动代理的功能,可以让我们在使用的时候完全不关心代理。
修改如下
package com.zyz.earlyAOP;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.context.annotation.Bean;
public class EarlyAopMainConfig {
@Bean
public Calculate txCalculate(){
return new TXCalculate();
}
@Bean
public TXAdvice txAdvice(){
return new TXAdvice();
}
@Bean
public NameMatchMethodPointcutAdvisor txAspect(){
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
//选择的通知
advisor.setAdvice(txAdvice());
//选择的方法
advisor.setMappedName("add");
return advisor;
}
@Bean
public BeanNameAutoProxyCreator autoProxyCreator() {
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
beanNameAutoProxyCreator.setBeanNames("tx*");
beanNameAutoProxyCreator.setInterceptorNames("txAspect");
return beanNameAutoProxyCreator;
}
}
package com.zyz.earlyAOP;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainStat {
public static void main(String[] args) {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(EarlyAopMainConfig.class);
Calculate calculateProxy = ctx.getBean("txCalculate", Calculate.class);
calculateProxy.add(1,2);
calculateProxy.sub(2,1);
}
}
2. schema-based XML配置
由于我对这种XML配置方法比较排斥(幸好Spring自己也发现了,所以后续推出了Springboot),所以这里先不介绍了,有兴趣的同学可以看看这篇。
3. 基于注解的配置
这里重点说下基于注解的配置。
3.1 使用方法
3.1.1 开启@AspectJ注解配置方式
有两种方式
在XML中配置:
<aop:aspectj-autoproxy/>
使用@EnableAspectJAutoProxy直接
@Configuration
@EnableAspectJAutoProxy
public class Config {
}
开启了上述配置之后,所有在容器中,被@AspectJ注解的 bean 都会被 Spring 当做是 AOP 配置类,称为一个 Aspect。
3.1.2 配置Pointcut(增强的切入点)
在Spring 中,我们可以认为 Pointcut 是用来匹配Spring 容器中所有满足指定条件的bean的方法。
下面完整列举一下 Pointcut 的匹配方式:
- execution:匹配方法签名
// 指定的方法
@Pointcut("execution(* testExecution(..))")
public void anyTestMethod() {}
- within:指定所在类或所在包下面的方法(Spring AOP 独有)
// service 层
// ".." 代表包及其子包
@Pointcut("within(com.zyz.earlyAOP..*)")
public void inSvcLayer() {}
- @annotation:方法上具有特定的注解
// 指定注解
@Pointcut("@annotation(com.zyz.earlyAOP.HaveAop)")
public void withAnnotation() {}
- bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 独有)
// controller 层
@Pointcut("bean(testController)")
public void inControllerLayer() {}
关于 Pointcut 的配置,Spring 官方建议,将所有通用的Pointcut配置都写到一个类里,官方还贴心的给出了一个例子:
package com.xyz.someapp;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemArchitecture {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.someapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.someapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() {}
/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.someapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() {}
/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.someapp.abc.service and com.xyz.def.service) then
* the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
* could be used instead.
*/
@Pointcut("execution(* com.xyz.someapp.service.*.*(..))")
public void businessService() {}
/**
* A data access operation is the execution of any method defined on a
* dao interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
3.1.3 配置Advice
注意,实际开发过程当中,Aspect 类应该遵守单一职责原则,不要把所有的Advice配置全部写在一个Aspect类里面。
这里为了简单起见,都写到一个类里
public class AspectAdvice {
@Pointcut("execution(* com.zyz.aspectAop.TXCalculate.*(..))")
public void pointCut(){}
@Before(value = "pointCut()")
public void methodBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】的<前置通知>,入参"+ Arrays.asList(joinPoint.getArgs()));
}
@After(value = "pointCut()")
public void methodAfter(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】的<后置通知>,入参"+Arrays.asList(joinPoint.getArgs()));
}
@AfterReturning(value = "pointCut()",returning = "result")
public void methodReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】的<返回通知>,入参"+Arrays.asList(joinPoint.getArgs()));
}
@AfterThrowing(value = "pointCut()")
public void methodAfterThrowing(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】的<异常通知>,入参"+Arrays.asList(joinPoint.getArgs()));
}
}
3.2 示例代码
目标接口
package com.zyz;
public interface Calculate {
/**
* 加法
* @param numA
* @param numB
* @return
*/
int add(int numA, int numB);
/**
* 减法
* @param numA
* @param numB
* @return
*/
int sub(int numA, int numB);
}
目标接口
package com.zyz.aspectAop;
import com.zyz.Calculate;
import org.springframework.stereotype.Component;
@Component
public class TXCalculate implements Calculate {
@Override
public int add(int numA, int numB) {
System.out.println("执行目标方法:add");
return numA + numB;
}
@Override
public int sub(int numA, int numB) {
System.out.println("执行目标方法:sub");
return numA - numB;
}
}
配置Pointcut和Advice
package com.zyz.aspectAop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Order
@Component
public class AspectAdvice {
@Pointcut("execution(* com.zyz.aspectAop.TXCalculate.*(..))")
public void pointCut(){}
@Before(value = "pointCut()")
public void methodBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】的<前置通知>,入参"+ Arrays.asList(joinPoint.getArgs()));
}
@After(value = "pointCut()")
public void methodAfter(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】的<后置通知>,入参"+Arrays.asList(joinPoint.getArgs()));
}
@AfterReturning(value = "pointCut()",returning = "result")
public void methodReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】的<返回通知>,入参"+Arrays.asList(joinPoint.getArgs()));
}
@AfterThrowing(value = "pointCut()")
public void methodAfterThrowing(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】的<异常通知>,入参"+Arrays.asList(joinPoint.getArgs()));
}
}
主配置类
package com.zyz.aspectAop;
import com.zyz.Calculate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class MainConfig {
@Bean
public Calculate calculate(){
return new TXCalculate();
}
@Bean
public AspectAdvice aspectAdvice(){
return new AspectAdvice();
}
}
启动类
package com.zyz.aspectAop;
import com.zyz.Calculate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainClass {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);
Calculate calculate = (Calculate) ctx.getBean("calculate");
int retVal = calculate.add(2,4);
}
}
输出:
执行目标方法【add】的<前置通知>,入参[2, 4]
执行目标方法:add
执行目标方法【add】的<返回通知>,入参[2, 4]
执行目标方法【add】的<后置通知>,入参[2, 4]