目录
1.AOP简述
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
2.相关术语
2.1横切关注点
- 从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
- 这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
2.2通知
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
- 前置通知:在被代理的目标方法前执行
- 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
- 异常通知:在被代理的目标方法异常结束后执行(死于非命)
- 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
- 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
2.3切面
封装通知方法的类。
2.4目标
被代理的目标对象。
2.5代理
向目标对象应用通知之后创建的代理对象。
2.6连接点
这也是一个纯逻辑概念,不是语法定义的。
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。
2.7切入点
定位连接点的方式。
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。
如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。
Spring 的 AOP 技术可以通过切入点定位到特定的连接点。
切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条
件。
2.8作用
- 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
- 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。
2.9切入点表达式语法
Example:@Pointcut("execution(* com.spring.aop.annotation.CalculatorImpl.*(..))")
3.基于注解的AOP
3.1技术说明
- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
- cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
- AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
3.2添加依赖
<?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-aop</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>
</project>
3.3准备被代理的目标资源
3.31接口
package com.spring.aop.annotation;
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
3.32实现类
package com.spring.aop.annotation;
import org.springframework.stereotype.Component;
@Component
public class CalculatorImpl implements Calculator {
public int add(int i, int j) {
int result=i+j;
System.out.println("方法内部:result="+result);
return result;
}
public int sub(int i, int j) {
int result=i-j;
System.out.println("方法内部:result="+result);
return result;
}
public int mul(int i, int j) {
int result=i*j;
System.out.println("方法内部:result="+result);
return result;
}
public int div(int i, int j) {
int result=i/j;
System.out.println("方法内部:result="+result);
return result;
}
}
3.4创建切面类并配置
package com.spring.aop.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 1、在切面中,需要通过指定的注解将方法标识为通知方法
* @Before:前置通知,在目标对象方法执行之前执行
* @After:后置通知,在目标对象方法的finally字句中执行
* @AfterReturning:返回通知,在目标对象方法返回值之后执行
* @AfterThrowing:异常通知,在目标对象方法的catch字句中执行
*
* 2、切入点表达式:设置在标识通知的注解的value属性中
* execution(public int com.spring.aop.annotation.CalculatorImpl.add(int, int)
* execution(* com.spring.aop.annotation.CalculatorImpl.*(..)
* 第一个*表示任意的访问修饰符和返回值类型
* 第二个*表示类中任意的方法
* ..表示任意的参数列表
* 类的地方也可以使用*,表示包下所有的类
*
* 3、重用切入点表达式
* //@Pointcut声明一个公共的切入点表达式
* @Pointcut("execution(* com.spring.aop.annotation.CalculatorImpl.*(..))")
* public void pointCut(){}
* 使用方式:@Before("pointCut()")
*
* 4、获取连接点的信息
* 在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点所对应方法的信息
* //获取连接点所对应方法的签名信息
* Signature signature = joinPoint.getSignature();
* //获取连接点所对应方法的参数
* Object[] args = joinPoint.getArgs();
*
* 5、切面的优先级
* 可以通过@Order注解的value属性设置优先级,默认值Integer的最大值
* @Order注解的value属性值越小,优先级越高
*
*/
@Component
@Aspect //将当前组件标识为切面
public class LoggerAspect {
@Pointcut("execution(* com.spring.aop.annotation.CalculatorImpl.*(..))")
public void pointCut(){}
//@Before("execution(public int com.atguigu.spring.aop.annotation.CalculatorImpl.add(int, int))")
//@Before("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")
@Before("pointCut()")
public void beforeAdviceMethod(JoinPoint joinPoint) {
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
//获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}
@After("pointCut()")
public void afterAdviceMethod(JoinPoint joinPoint){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",执行完毕");
}
/**
* 在返回通知中若要获取目标对象方法的返回值
* 只需要通过@AfterReturning注解的returning属性
* 就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
*/
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",结果:"+result);
}
/**
* 在异常通知中若要获取目标对象方法的异常
* 只需要通过AfterThrowing注解的throwing属性
* 就可以将通知方法的某个参数指定为接收目标对象方法出现的异常的参数
*/
@AfterThrowing(value = "pointCut()", throwing = "ex")
//写入异常类型,用Throwable和Exception均可
public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable ex){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",异常:"+ex);
}
@Around("pointCut()")
//环绕通知的方法的返回值(object),一定要和目标对象方法的返回值一致(object)
//类似动态代理,手动将上面的所有通知整合在一起
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("环绕通知-->前置通知");
//表示目标对象方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->返回通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->异常通知");
} finally {
System.out.println("环绕通知-->后置通知");
}
return result;
}
}
以下为优先级顺序测试类:
package com.spring.aop.annotation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Order(1)
//@Order(较小的数):优先级高,默认为integer最大值
public class ValidateAspect {
//@Before("execution(* com.spring.aop.annotation.CalculatorImpl.*(..))")
@Before("com.spring.aop.annotation.LoggerAspect.pointCut()")
public void beforeMethod(){
System.out.println("ValidateAspect->前置通知");
}
}
3.5IOC容器管理
aop-annotation.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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
基于注解的AOP的实现:
1、将目标对象和切面交给IOC容器管理(注解+扫描)
2、开启AspectJ的自动代理,为目标对象自动生成代理
3、将切面类通过注解@Aspect标识
-->
<context:component-scan base-package="com.spring.aop.annotation"></context:component-scan>
<!-- 开启基于注解的AOP-->
<aop:aspectj-autoproxy/>
</beans>
3.6测试类
package com.spring.test;
import com.spring.aop.annotation.Calculator;
import com.spring.aop.annotation.CalculatorImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AOPTest {
@Test
public void testAOPByAnnotation(){
ApplicationContext ioc=new ClassPathXmlApplicationContext("aop-annotation.xml");
Calculator calculator = ioc.getBean(Calculator.class);
//calculator.add(1,1);
//calculator.sub(2,1);
calculator.div(1,1);
}
}
4.基于XML的AOP(了解)
4.1切面类
package com.spring.aop.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
public class LoggerAspect {
public void beforeAdviceMethod(JoinPoint joinPoint) {
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
//获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}
public void afterAdviceMethod(JoinPoint joinPoint){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",执行完毕");
}
/**
* 在返回通知中若要获取目标对象方法的返回值
* 只需要通过@AfterReturning注解的returning属性
* 就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
*/
public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",结果:"+result);
}
/**
* 在异常通知中若要获取目标对象方法的异常
* 只需要通过AfterThrowing注解的throwing属性
* 就可以将通知方法的某个参数指定为接收目标对象方法出现的异常的参数
*/
//写入异常类型,用Throwable和Exception均可
public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable ex){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",异常:"+ex);
}
//环绕通知的方法的返回值(object),一定要和目标对象方法的返回值一致(object)
//类似动态代理,手动将上面的所有通知整合在一起
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("环绕通知-->前置通知");
//表示目标对象方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->返回通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->异常通知");
} finally {
System.out.println("环绕通知-->后置通知");
}
return result;
}
}
package com.spring.aop.xml;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
public class ValidateAspect {
public void beforeMethod(){
System.out.println("ValidateAspect->前置通知");
}
}
4.2IOC容器管理
aop-xml.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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--扫描组件-->
<context:component-scan base-package="com.spring.aop.xml"></context:component-scan>
<aop:config>
<!--设置一个公共的切入点表达式-->
<aop:pointcut id="poincut" expression="execution(* com.spring.aop.xml.Calculator.*(..))"/>
<!--将IOC容器中的某个bean设置为切面-->
<aop:aspect ref="loggerAspect">
<aop:before method="beforeAdviceMethod" pointcut-ref="poincut"></aop:before>
<aop:after method="afterAdviceMethod" pointcut-ref="poincut"></aop:after>
<aop:after-returning method="afterReturningAdviceMethod" returning="result" pointcut-ref="poincut"></aop:after-returning>
<aop:after-throwing method="afterThrowingAdviceMethod" throwing="ex" pointcut-ref="poincut"></aop:after-throwing>
<aop:around method="aroundAdviceMethod" pointcut-ref="poincut"></aop:around>
</aop:aspect>
<aop:aspect ref="validateAspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="poincut"></aop:before>
</aop:aspect>
</aop:config>
</beans>
可以点个免费的赞吗!!!