1. 相关概念
1.1 AOP 相关术语
- 连接点(Joinpoint):所谓连接点是指那些被拦截到的点,在spring中,这些点指方法,因为spring只支持方法类型的连接,实际上,jionpoint还可以是field或类构造器。
- 切点(Pointcut):被增强的连接点。例如:add()
- 通知或增强(Advice):所谓通知是指拦截到 joinpoint 之后所要做的事情,分为前通知、后置通知、异常通知、最终通知、环绕通知。
- 目标对象(Target):代理的目标对象.。如果没有 AOP,目标业务类需要自己实现所有逻辑,而在 AOP的帮助下,目标业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用 AOP 动态织入到特定的连接点上。
- 切面(Aspect):切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP 就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
- 织入(Weaving)织入是将aspects对象应用到target对象并导致proxy对象创建的过程。
1.2 通知类型
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知(After-returning):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为;
1.3 spring 提供了 2 种 AOP 实现方式
1.3.1 Schema-based
- 每个通知都需要实现接口或类
- 配置 spring 配置文件时在<aop:config>配置
1.3.2 AspectJ
- 每个通知不需要实现接口或类
- 配置 spring 配置文件是在<aop:config>的子标签
- <aop:aspect>中配置
2. Schema-based 方式实现
2.1 前置、后置通知
2.1.1 包依赖
<?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>com.zth.spring</groupId>
<artifactId>spring09</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<spring.version>5.1.4.RELEASE</spring.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-expression -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.4.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>spring09</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.1.2 原始类
@Component
public class Email {
public void send(){
System.out.println("发送中。。。");
}
}
2.1.3 通知类
前置通知类
package com.zth.asp;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author zth
* @Date 2019-07-19 10:26
*/
@Component
public class BeforeAdvice implements MethodBeforeAdvice {
/**
*
* @param method 切点方法对象 Method 对象
* @param args 切点方法参数
* @param target 切点在哪个对象中
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("发送前邮件。。。");
}
}
后置通知类:
package com.zth.asp;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author zth
* @Date 2019-07-19 10:26
*/
@Component
public class AfterAdvice implements AfterReturningAdvice {
/**
*
* @param returnValue 切点方法返回值
* @param method 切点方法对象
* @param args 切点方法参数
* @param target 切点方法所在的类对象
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("发送邮件后。。。");
}
}
2.1.4 spring 配置文件
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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:annotation-config/>
<context:component-scan base-package="com"/>
<!--配置切面-->
<aop:config>
<!--配置切点-->
<aop:pointcut id="point01" expression="execution(* com.zth.service.*.*(..))"/>
<!--通知-->
<aop:advisor advice-ref="beforeAdvice" pointcut-ref="point01"/>
<aop:advisor advice-ref="afterAdvice" pointcut-ref="point01"/>
</aop:config>
</beans>
2.1.5 测试类
package com.zth;
import com.zth.service.Email;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author zth
* @Date 2019-07-19 10:52
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value={"/ApplicationContext.xml"})
public class MyTest {
@Autowired
Email email;
@Test
public void test(){
email.send();
}
}
2.1.6 执行结果
2.2 异常通知
2.2.1 创建异常通知类
package com.zth.asp;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author zth
* @Date 2019-07-19 11:41
*/
@Component
public class ExceptionAdvice implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, Exception ex) {
System.out.println("执行异常通知");
}
/* public void afterThrowing(Exception ex) throws Throwable {
System.out.println("执行异常通过-schema-base 方式");
}*/
}
- 新建一个类实现 throwsAdvice 接口
- 必须自己写方法,且必须叫 afterThrowing
- 有两种参数方式 , 必须是 1 个或 4 个
- 异常类型要与切点报的异常类型一致
2.2.2 spring 配置文件
<!--配置切面-->
<aop:config>
<!--配置切点-->
<aop:pointcut id="point01" expression="execution(* com.zth.service.*.*(..))"/>
<!--通知-->
<aop:advisor advice-ref="exceptionAdvice" pointcut-ref="point01"/>
</aop:config>
【注】其他步骤同上
2.2.3 执行结果
没有异常:
添加异常:
@Component
public class Email {
public void send() throws Exception {
System.out.println("发送中。。。");
throw new Exception();
}
}
执行结果:
2.2 环绕通知
2.2.1 新建环绕通知类
package com.zth.asp;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.stereotype.Component;
@Component
public class MyArround implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕前置。。。");
Object result = invocation.proceed();
System.out.println("环绕后置。。。");
return result;
}
}
2.2.2 spring 配置文件
<!--配置切面-->
<aop:config>
<!--配置切点-->
<aop:pointcut id="point01" expression="execution(* com.zth.service.*.*(..))"/>
<!--通知-->
<aop:advisor advice-ref="myArround" pointcut-ref="point01"/>
</aop:config>
【注】其他同上
2.2.3 测试结果:
3. AspectJ 方式实现
3.1 前置、后置通知
3.1.1 原始类
@Component
public class Email {
public void send(){
System.out.println("发送中。。。");
}
}
3.1.2 通知类
package com.zth.asp;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class Logger {
public void before() {
System.out.println("before...");
}
public void after() {
System.out.println("after....");
}
public void exception(){
System.out.println("出错了。。。");
}
}
3.1.3 spring 配置文件
<!--配置切面-->
<aop:config>
<aop:aspect ref="logger">
<aop:pointcut id="point01" expression="execution(* com.zth.service.*.*(..))"/>
<aop:before method="before" pointcut-ref="point01"/>
<aop:after method="after" pointcut-ref="point01"/>
<aop:after-throwing method="exception" pointcut-ref="point01"/>
</aop:aspect>
</aop:config>
3.1.4 执行结果
有异常:
没有异常:
3.2 环绕通知
3.2.1 创建通知类
package com.zth.asp;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class Logger {
public Object myArround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前置。。。");
Object result = joinPoint.proceed();
System.out.println("环绕后置。。。");
return result;
}
}
3.2.2 spring 配置文件
<aop:config>
<aop:aspect ref="logger">
<aop:pointcut id="point01" expression="execution(* com.zth.service.*.*(..))"/>
<aop:around method="myArround" pointcut-ref="point01"/>
</aop:aspect>
</aop:config>
【注】其他步骤同上
3.2.3 执行结果
- <aop:after/> 后置通知,是否出现异常都执行
- <aop:afte-returing />后置通知,只有当切点正确执行时 执行
- <aop:after/>和 <aop:afte-returing />和<aop:afte-throwing/> 执行顺序和配置顺序有关
带参数的切点:
- execution() 括号不能扩上 args
- 中间使用 and 不能使用&& 由 spring 把 and 解析成&&
- args() 有几个参数,arg-names 里面必须有几个参数
- arg-names=”” 里面名称必须和通知方法参数名对应
4. 使用注解(基于 Aspect)
4.1 applicationContent.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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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:annotation-config/>
<context:component-scan base-package="com"/>
//启用Aspectj自动代理
<aop:aspectj-autoproxy />
</beans>
4.2代理类
package com.zth.asp;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class Logger {
@Pointcut("execution(* com.zth.service.*.*(..))")
private void anyPublicOperation() {}
@Before("anyPublicOperation()")
public void before() {
System.out.println("before...");
}
@After("anyPublicOperation()")
public void after() {
System.out.println("after....");
}
@AfterThrowing("anyPublicOperation()")
public void exception(){
System.out.println("出错了。。。");
}
@Around("anyPublicOperation()" )
public Object myArround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前置。。。");
Object result = joinPoint.proceed();
System.out.println("环绕后置。。。");
return result;
}
}
4.3 测试结果