Spring-AOP(面向切面)
场景模拟(计算器)
功能接口
public interface Calculator {
int add(int i, int j);
int minus(int i, int j);
int multiply(int i, int j);
int div(int i, int j);
}
实现类
public class CalculateLogImpl implements Calculator {
@Override
public int add(int i, int j) {
System.out.println("[日志]add方法开始,参数是"+ i+" " + j);
int result = i + j;
System.out.println("方法内部:resultAdd = " + result);
System.out.println("[日志]add方法结束,结果是:" + result);
return result;
}
@Override
public int minus(int i, int j) {
System.out.println("[日志]minus 方法开始,参数是"+ i+" " + j);
int result = i - j;
System.out.println("方法内部:resultMinus = " + result);
System.out.println("[日志]minus 方法结束,结果是:" + result);
return result;
}
@Override
public int multiply(int i, int j) {
System.out.println("[日志]multiply 方法开始,参数是"+ i+" " + j);
int result = i * j;
System.out.println("方法内部:resultMultiply = " + result);
System.out.println("[日志]multiply 方法结束,结果是:" + result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("[日志]div 方法开始,参数是"+ i+" " + j);
int result = i / j;
System.out.println("方法内部:resultDiv = " + result);
System.out.println("[日志]div 方法结束,结果是:" + result);
return result;
}
}
在含有日志输出的实现类中可以了解到:与核心业务功能没有关系的日志输出加杂在模块中,对核心业务功能有干扰。
思路:解耦
,将附加功能从业务功能模块中抽取出来
代理模式
概念
静态代理
目标对象
接口
public interface Calculator {
int add(int i, int j);
int minus(int i, int j);
int multiply(int i, int j);
int div(int i, int j);
}
实现类
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部:resultAdd = " + result);
return result;
}
@Override
public int minus(int i, int j) {
int result = i - j;
System.out.println("方法内部:resultMinus = " + result);
return result;
}
@Override
public int multiply(int i, int j) {
int result = i * j;
System.out.println("方法内部:resultMultiply = " + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部:resultDiv = " + result);
return result;
}
}
代理类
public class CalculatorStaticProxy implements Calculator {
//被代理的目标对象传递过来
private Calculator calculator;
public CalculatorStaticProxy(Calculator calculator) {
this.calculator = calculator;
}
@Override
public int add(int i, int j) {
System.out.println("[日志]add方法开始,参数是"+ i+" " + j);
//调用目标对象的方法实现核心业务
int result = calculator.add(i, j);
System.out.println("[日志]add方法结束,结果是:" + result);
return result;
}
@Override
public int minus(int i, int j) {
System.out.println("[日志]minus 方法开始,参数是"+ i+" " + j);
int result = calculator.minus(i, j);
System.out.println("[日志]minus 方法结束,结果是:" + result);
return result;
}
@Override
public int multiply(int i, int j) {
System.out.println("[日志]multiply 方法开始,参数是"+ i+" " + j);
int result = calculator.multiply(i,j);
System.out.println("[日志]multiply 方法结束,结果是:" + result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("[日志]div 方法开始,参数是"+ i+" " + j);
int result = calculator.div(i, j);
System.out.println("[日志]div 方法结束,结果是:" + result);
return result;
}
}
测试
@Test
public void testStaticProxy(){
Calculator calculator = new CalculatorStaticProxy(new CalculatorImpl());
calculator.add(10, 5);
System.out.println("-------------------");
calculator.minus(10, 5);
System.out.println("-------------------");
calculator.multiply(10, 5);
System.out.println("-------------------");
calculator.div(10, 5);
}
/*
* [日志]add方法开始,参数是10 5
方法内部:resultAdd = 15
[日志]add方法结束,结果是:15
-------------------
[日志]minus 方法开始,参数是10 5
方法内部:resultMinus = 5
[日志]minus 方法结束,结果是:5
-------------------
[日志]multiply 方法开始,参数是10 5
方法内部:resultMultiply = 50
[日志]multiply 方法结束,结果是:50
-------------------
[日志]div 方法开始,参数是10 5
方法内部:resultDiv = 2
[日志]div 方法结束,结果是:2
* */
动态代理
Proxy工具类
public class ProxyUtil {
//目标对象
private Object target;
public ProxyUtil(Object target) {
this.target = target;
}
//反回代理对象
public Object getProxy(){
//使用Proxy中的newProxyInstance()
/**
* Proxy.newProxyInstance()
* ClassLoader:加载动态生成代理类的类加载器
* Class<?>[] interfaces:目标对象实现的所有接口的class类型数组
* InvocationHandler:设置代理对象实现目标对象方法的过程
*/
//ClassLoader:加载动态生成代理类的类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//Class<?>[] interfaces:目标对象实现的所有接口的class类型数组
Class<?>[] interfaces = target.getClass().getInterfaces();
//InvocationHandler:设置代理对象实现目标对象方法的过程
InvocationHandler invocationHandler = new InvocationHandler(){
/**
*
* @param proxy :代理对象
* @param method :需要重写目标对象的方法
* @param args:method方法中的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable {
System.out.println("[动态代理][日志]" + method.getName() + ", 参数:" + Arrays.toString(args));
//调用目标的方法
Object result = method.invoke(target, args);
System.out.println("[动态代理][日志]" + method.getName() + ", 结果:" + result);
return result;
}
};
return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
}
}
测试类
@Test
public void testProxy(){
//动态创建代理对象
ProxyUtil proxyUtil = new ProxyUtil(new CalculatorImpl());
Calculator proxy = (Calculator) proxyUtil.getProxy();
proxy.add(1, 2);
System.out.println("---------");
proxy.minus(1, 2);
System.out.println("---------");
proxy.multiply(1, 2);
System.out.println("---------");
proxy.div(1, 2);
}
/*
* [动态代理][日志]add, 参数:[1, 2]
方法内部:resultAdd = 3
[动态代理][日志]add, 结果:3
---------
[动态代理][日志]minus, 参数:[1, 2]
方法内部:resultMinus = -1
[动态代理][日志]minus, 结果:-1
---------
[动态代理][日志]multiply, 参数:[1, 2]
方法内部:resultMultiply = 2
[动态代理][日志]multiply, 结果:2
---------
[动态代理][日志]div, 参数:[1, 2]
方法内部:resultDiv = 0
[动态代理][日志]div, 结果:0
* */
AOP概念及相关术语
简介
相关术语
横切关注点
通知(增强)
增强,就是想要增强的功能,比如:安全、事务、日志等。每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
前置通知
:在被代理的目标方法前执行
返回通知
:在被代理的目标方法成功结束后执行(寿终正寝)
异常通知
:在被代理的目标方法异常结束后执行(死于非命)
后置通知
:在被代理的目标方法最终结束后执行(盖棺定论)
环绕通知
:使用try...catch...finally
结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置。
切面
封装通知方法的类。
目标
被代理的目标对象。
代理
向目标对象应用通知之后创建的代理对象
连接点
切入点
切入点通过org.springframework.aop.Pointcut
接口进行描述,它使用类和方法作为连接点的查询条件。
作用
简化代码
:把方法中固定位置的重复代码抽取出来,让抽取的方法更专注于自己的核心功能,提高内聚性。
代码增强
:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了
基于注解的AOP
AspectJ
:是AOP思想的一种实现。本质上是静态代理,将代理逻辑"植入"被代理的目标,编译得到的字节码文件,所以最终效果是动态的。weaver就是植入器。Spring只是借用了AspectJ中的注解。
动态代理分类
JDK动态代理
:有接口,生成接口实现类代理对象,代理对象和目标对象都实现同样的接口。
cglib动态代理
:没有接口,继承目标类,生成子类代理对象
准备工作
引入相关的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.9</version>
</dependency>
创建目标资源
public interface Calculator {
int add(int i, int j);
int minus(int i, int j);
int multiply(int i, int j);
int div(int i, int j);
}
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部:resultAdd = " + result);
return result;
}
@Override
public int minus(int i, int j) {
int result = i - j;
System.out.println("方法内部:resultMinus = " + result);
return result;
}
@Override
public int multiply(int i, int j) {
int result = i * j;
System.out.println("方法内部:resultMultiply = " + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部:resultDiv = " + result);
return result;
}
}
配置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/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
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.louis.annotation_aop"></context:component-scan>
<!--开启aspectJ自动代理,为目标对象生成代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
创建切面类
切入点表达式
用*号代替"权限修饰符"和"返回值部分"表示"权限修饰符"和"返回值"不限
①一个"*“号只能代表包的层次结构中的一层,表示这一层是任意的。如:*.com匹配hello.com但不匹配hello.louis.com
②使用”*…"表示任意包名、包的层次深度任意
①类名整体使用*代替,表示类名任意
②可以使用*代替类名的一部分。如:*xxx表示匹配所有名称以xxx结尾的类或接口
①可以使用*代替,表示方法名任意
②可以使用*代替方法名的一部分。如:*xxx表示匹配所有名称以xxx结尾的方法
①可以使用(…)表示任意参数列表
②可以使用(int,…)表示参数列表以一个int类型的参数开头
③基本数据类型和对应的包装类型是不一样的(切入点中使用int和实际方法中使用Integer是不匹配的)
如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符。如:excution(public int…Service.(…,int))
切面类(通知类型)
@Aspect
@Component //表示在spring的ioc容器中进行管理
public class LogAspect {
//设置切入点和通知类型
//通知类型:
// 前置:@Before(value="通过切入点表达式配置切入点")
//切入点表达式:execution(访问修饰符 增强方法返回类型 方法所在类的全路径.方法名(参数列表))
// @Before("execution(* com.louis.annotation_aop.impl.LogAspect.*(..))")
@Before("execution(public int com.louis.annotation_aop.impl.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("前置通知,增强的方法名称" + name + " , 参数" + Arrays.toString(args));
}
// 返回:@AfterReturning,返回通知与后置通知有很大的区别,后置通知在返回通知之后执行,返回通知能够得到目标方法的返回值,使用属性returning
//它的参数可以随便命名,但是切面方法的参数必须和上面的一致
@AfterReturning(value = "execution(* com.louis.annotation_aop.impl.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
String name = joinPoint.getSignature().getName();
System.out.println("返回通知, 增强方法名称" + name + " , 返回结果" + result);
}
// 异常:@AfterThrowing:目标方法出现异常,这个通知会执行,并且能够获得目标方法的异常信息
@AfterThrowing(value = "execution(* com.louis.annotation_aop.impl.CalculatorImpl.*(..))", throwing = "Exception")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable Exception){
String name = joinPoint.getSignature().getName();
System.out.println("异常通知, 增强方法名称" + name + " , 返回结果" + Exception);
}
// 后置:@After()
@After("execution(public int com.louis.annotation_aop.impl.CalculatorImpl.*(..))")
public void afterMethod(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println("后置通知, 增强方法名称" + name);
}
// 环绕:@Around()
@Around("execution(public int com.louis.annotation_aop.impl.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
String argsString = Arrays.toString(args);
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;
}
}
测试
@Test
public void testAOPAdd(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Calculator calculator = context.getBean(Calculator.class);
calculator.add(1,0);
}
/*
环绕通知, 目标方法之前执行
前置通知,增强的方法名称add , 参数[1, 0]
方法内部:resultAdd = 1
返回通知, 增强方法名称add , 返回结果1
后置通知, 增强方法名称add
环绕通知, 目标方法返回值之后执行
环绕通知, 目标方法执行完毕
* */
@Test
public void testDiv(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Calculator calculator = context.getBean(Calculator.class);
calculator.div(1,0);
}
/*
环绕通知, 目标方法之前执行
前置通知,增强的方法名称div , 参数[1, 0]
异常通知, 增强方法名称div , 返回结果java.lang.ArithmeticException: / by zero
后置通知, 增强方法名称div
环绕通知, 目标方法出现异常执行
环绕通知, 目标方法执行完毕
* */
基于注解的AOP
重用切入点和切面优先级
重用切入点
//重用切入点表达式,定义一个方法,之后在需要切入点的时候直接调用这个方法
/*
* 在同一个类下,可以直接使用:@Before("pointcut()")
* 在不同的类中,需要加入类的路径: @Before("com.louis.annotation_aop.impl.LogAspect.pointcut()")
* */
@Pointcut(value = "execution(public int com.louis.annotation_aop.impl.CalculatorImpl.*(..))")
public void pointcut(){
}
切面优先级
切面类
@Component //表示在spring的ioc容器中进行管理
public class LogAspect {
//前置通知
public void beforeMethod(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("前置通知,增强的方法名称" + name + " , 参数" + Arrays.toString(args));
}
// 返回通知
public void afterReturningMethod(JoinPoint joinPoint, Object result){
String name = joinPoint.getSignature().getName();
System.out.println("返回通知, 增强方法名称" + name + " , 返回结果" + result);
}
// 异常通知
public void afterThrowingMethod(JoinPoint joinPoint, Throwable Exception){
String name = joinPoint.getSignature().getName();
System.out.println("异常通知, 增强方法名称" + name + " , 返回结果" + Exception);
}
// 后置通知
public void afterMethod(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println("后置通知, 增强方法名称" + name);
}
// 环绕通知
public Object aroundMethod(ProceedingJoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
String argsString = Arrays.toString(args);
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;
}
//重用切入点表达式,定义一个方法,之后在需要切入点的时候直接调用这个方法
/*
* 在同一个类下,可以直接使用:@Before("pointcut()")
* 在不同的类中,需要加入类的路径: @Before("com.louis.annotation_aop.impl.LogAspect.pointcut()")
* */
@Pointcut(value = "execution(public int com.louis.xml_aop.impl.CalculatorImpl.*(..))")
public void pointcut(){
}
}
配置文件beanaop.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/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
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.louis.xml_aop"></context:component-scan>
<!--配置aop五种通知类型-->
<aop:config>
<!--配置切面类-->
<aop:aspect ref="logAspect">
<!--配置切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.louis.xml_aop.impl.CalculatorImpl.*(..))"/>
<!--配置五种通知类型-->
<!--前置通知-->
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
<!--后置通知-->
<aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
<!--返回通知-->
<aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointcut"></aop:after-returning>
<!--异常通知-->
<aop:after-throwing method="afterThrowingMethod" throwing="Exception" pointcut-ref="pointcut"></aop:after-throwing>
<!--环绕通知-->
<aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>
</aop:aspect>
</aop:config>
</beans>
测试
@Test
public void testAOPXMLAdd(){
ApplicationContext context = new ClassPathXmlApplicationContext("beanaop.xml");
Calculator calculator = context.getBean(Calculator.class);
calculator.add(1,0);
}
/*
前置通知,增强的方法名称add , 参数[1, 0]
环绕通知, 目标方法之前执行
方法内部:resultAdd = 1
环绕通知, 目标方法返回值之后执行
环绕通知, 目标方法执行完毕
返回通知, 增强方法名称add , 返回结果1
后置通知, 增强方法名称add
* */