一、AOP概述
AOP(Aspect Oriented Programming)是一种设计模式,是软件设计领域中的面向切面编程(方面)。它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态同一添加额外功能的一种技术。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。通俗描述:不通过修改源代码方式,在主干功能里面添加新功能;
Spring 的 AOP 使用的动态代理是:JDK动态代理+CGLIB动态代理。Spring 在这两种动态代理中灵活切换,如果是代理接口,会默认使用 JDK动态代理,如果要代理某个类,这个类没有接口,就会切换使用 CGLIB动态代理。当然,也可以强制通过一些配置让 Spring 只使用 CGLIB。
一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务。这些交叉业务几乎是通用的。如果每一个业务处理过程中,都掺杂这些交叉业务代码进去的话,就会存在两个方面的问题;
- 第一个问题是:交叉业务在多个业务流程中反复出现,显然这个交叉业务没有得到复用,并且修改这些交叉业务代码的话,需要修改多处。
- 第二个问题是:程序员无法专注核心业务代码的编写,在编写核心业务代理的同时还需要处理这些交叉业务。
因此,使用 AOP 可以很轻松解决以上的两个问题。
①横切关注点
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同的增强。这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
②连接点(Joinpoint)
连接点指的是在整个执行流程中,可以织入切面的位置。把方法排成一排,每一个横切位置看作x轴方向,把方法从上到下执行的顺序看成y轴,x轴 和 y轴 的交叉点就是连接点。连接点也是一个纯逻辑概念,不是语法定义的。连接点描述的是位置。
③切入点(Pointcut)
切入点指的是在程序执行流程中,真正织入切面的方法。(一个切入点对应多个连接点)切入点是定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上说)。Spring 的 AOP 技术 可以通过切入点定位到特定的连接点。切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。切点本质上就是方法。
④通知(Advice)
通知又叫增强,就是具体你要织入的代码。每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫做通知方法。
- 前置通知:在被代理的目标方法执行前 执行
- 返回通知:在被代理的目标方法成功结束 后执行
- 异常通知:在被代理的目标方法异常结束 后执行
- 后置通知:在被代理的目标方法最终结束 后执行
- 环绕通知:使用 try...catch...finally 结构围绕整个被代理的目标方法,包括上面四种通知的所有位置
⑤切面(Aspect)
封装横切关注点(通知方法)的类被称为切面,即 切入点+通知 就是 切面;
⑥目标(Target)
被代理的目标对象,即被织入通知的对象。
⑦代理(Proxy)
向目标对象应用通知之后创建的代理对象,即一个目标对象被织入通知后产生的新对象。
⑧织入(Weaving)
把通知应用到目标对象上的过程称为织入。
二、AOP的底层原理
2.1、代理模式
AOP 底层使用动态代理。它有两种情况动态代理,一种是有接口情况,使用 JDK 动态代理,创建接口实现类代理对象,增强类的方法;另一种是没有接口情况,使用 CGLIB 动态代理,创建子类的代理对象,增强类的方法;
代理模式是 23 中设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来 —— 解耦。调用目标方法是先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中再一起也有利于同一维护。
代理模式中有三大角色,第一个是 代理类(代理主题),第二个是 目标类(真实主题),第三个是 代理类和目标类的公共接口(抽象主题);客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口。
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为 “代理” 的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。
2.2、静态代理
静态代理模式要求目标类(被代理类)和代理类都实现了同一个接口。目标类中重写接口中抽象方法,用来实现主要功能。同时,将目标类声明为代理类中的一个成员变量。代理类同样重写接口中的抽象方法,并在相对应的方法中调用目标类中实现的对应方法。
①添加依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
②代理类和目标类的公共接口
package star.light.proxy;
// 代理类和目标类的公共接口
public interface Calculator {
int divide(int i,int j);
}
②目标类
package star.light.proxy;
// 目标类
public class CalculatorImpl implements Calculator {
// 目标方法
@Override
public int divide(int i, int j) {
int result = i / j;
System.out.println("方法内部,result:" + result);
return result;
}
}
④静态代理理类
package star.light.proxy;
// 静态代理理类
public class CalculatorStaticProxy implements Calculator {
// 将目标对象作为代理对象的一个属性,这种关系叫做关联关系,比继承关系耦合度低
private Calculator target;
// 创建代理对象的时候,传一个目标对象给代理对象
public CalculatorStaticProxy(Calculator target) {
this.target = target;
}
// 代理方法
@Override
public int divide(int i, int j) {
// 附加功能有代理类中的代理方法来实现
int result = 0;
try {
System.out.println("[日志] divide 方法开始了,参数是:" + i + "," + j);
// 调用目标对象的目标方法来实现核心业务逻辑
result = target.divide(i,j);
System.out.println("[日志] divide 方法正常执行,结果是:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("[日志] divide 方法发生异常了了,异常信息是:" + e.getMessage());
} finally {
System.out.println("[日志] divide 方法执行完毕结束了,结果是:" + result);
}
return result;
}
}
⑤测试程序
package star.light.test;
import org.junit.Test;
import star.light.proxy.CalculatorImpl;
import star.light.proxy.CalculatorStaticProxy;
public class AopTest {
@Test
public void testStaticProxy(){
// 创建目标对象
Calculator targer = new CalculatorImpl();
// 创建代理对象
Calculator proxy = new CalculatorStaticProxy(targer);
proxy.divide(3,2);
System.out.println();
proxy.divide(1,0);
}
}
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其它地方也需要附加日记功能,那还需要更多的静态代理类,那样就产生了大量重复的代码,日志功能还是分散的,没有同一管理。
2.3、动态代理
2.3.1、什么是动态代理
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。动态代理有两种。一种是 JDK 动态代理,要求必须有接口,最终生成的代理类和目标类实现相同的结果,在 com.sum.proxy 包下,类名为 $proxy+数字。另一种是 CGLIB 动态代理,它既可以代理接口有可以代理类,最终生成的代理类会继承目标类,并且和目标类在相同的包下。
2.3.2、使用JDK动态代理
使用 JDK 动态代理,使用 Proxy 类里面的 newProxyInstance() 创建代理对象。该方法有三个参数,
- 第一参数为:类加载器,用来指定加载动态生成的代理类的类加载器,并且 JDK 要求目标类的加载器必须和代理类加载器使用同一个加载器;
- 第二个参数:用来获取目标对象实现的所有接口的 class 对象的数组;
- 第三参数为:调用处理器,是一个接口,在调用处理器接口中编写的就是增强程序;
Proxy.newProxyInstance() 方法的执行,做了两件事,一件事是:在内存中动态生成了一个代理类的字节码 .class 文件;另一件事:new 对象了,通过内存中生成的代理类这个代码,实例化了代理对象;
①代理类和目标类的公共接口
Calculator.java 内容同上;
②目标类
CalculatorImpl.java 内容同上;
③JDK动态代理理类
package star.light.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class JdkDynamicProxy {
// 将被代理的目标对象声明为成员变量
private Object target;
public JdkDynamicProxy(Object target) {
// 赋值给成员变量
this.target = target;
}
public Object getProxy(){
ClassLoader classLoader = this.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
/**
* invoke()方法不是我们程序员负责调用的,而是JDK负责调用的
* 当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用
* @param proxy 代理对象的引用
* @param method 目标对象上的目标方法
* @param args 目标方法上的实参
* @return 目标对象的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("日志,方法" + method.getName() + "开始执行了,参数:" + Arrays.toString(args));
// 调用目标对象的目标方法
// 方法的四要素:哪个对象、哪个方法、传什么参数、返回什么值
result = method.invoke(target,args);
System.out.println("日志,方法" + method.getName() + "正常执行,结果:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("日志,方法" + method.getName() + ",发生异常了,异常信息:" + e.getMessage());
} finally {
System.out.println("日志,方法" + method.getName() + ",执行完毕了,结果是:" + result);
}
return result;
}
};
// 如果代理对象调用代理方法之后,需要返回结果的化,invoke()必须将目标对象的返回值继续返回
return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
}
}
④在测试程序中添加如下测试方法
@Test
public void testJdkDynamicProxy(){
// 创建目标对象
Calculator targer = new CalculatorImpl();
// 创建代理对象
JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy(targer);
Calculator proxy = (Calculator) jdkDynamicProxy.getProxy();
proxy.divide(3, 2);
System.out.println();
proxy.divide(1, 0);
}
2.3.2、CGLIB动态代理
CGLIB 既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用 final 修饰。
①引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
②目标类
package star.light.proxy;
// 目标类
public class Calculatrice {
// 目标方法
public int divide(int i, int j) {
int result = i / j;
System.out.println("方法内部,result:" + result);
return result;
}
}
③CGLIB动态代理
package star.light.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Arrays;
public class CglibDynamicProxy {
private Object target;
public CglibDynamicProxy(Object target) {
this.target = target;
}
public Object getProxy(){
// 创建字节码增强器对象
// 这个是CGLIB库当中的核心对象,就是依赖它生成代理类
Enhancer enhancer = new Enhancer();
// 告诉CGLIB目标类是谁
enhancer.setSuperclass(target.getClass());
// 设置回调(等同于JDK动态代理当中的处理器InvicationHandler)
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
try {
System.out.println("日志,方法" + method.getName() + "开始执行了,参数:" + Arrays.toString(objects));
// 调用目标对象的目标方法
// 方法的四要素:哪个对象、哪个方法、传什么参数、返回什么值
result = methodProxy.invokeSuper(target, objects);
System.out.println("日志,方法" + method.getName() + "正常执行,结果:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("日志,方法" + method.getName() + ",发生异常了,异常信息:" + e.getMessage());
} finally {
System.out.println("日志,方法" + method.getName() + ",执行完毕了,结果是:" + result);
}
return result;
}
});
// 创建代理对象
// 这一步会做两件事:
// 第一件事:在内存中生成目标对象的子类,其实就是代理类的字节码
// 第二件事:创建代理对象
return enhancer.create();
}
}
⑤在测试程序中添加如下测试方法
@Test
public void testCglibDynamicProxy(){
// 创建目标对象
Calculatrice targer = new Calculatrice();
// 创建代理对象
CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy(targer);
Calculatrice proxy = (Calculatrice) cglibDynamicProxy.getProxy();
proxy.divide(3, 2);
System.out.println();
proxy.divide(1, 0);
}
对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:
- --add-opens java.base/java.lang=ALL-UNNAMED
- --add-opens java.base/sun.net.util=ALL-UNNAMED
三、基于注解的AOP
AspectJ 是 Eclipse 组织的一个支持 AOP 的框架。AspectJ 框架是独立于 Spring 框架之外的一个框架,Spring 框架使用了AspectJ。AspectJ 本质上是静态代理,将代理逻辑织入被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver 就是织入器。Spring只是借用了 AspectJ 中的注解。
①添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.9</version>
</dependency>
②目标类
目标类需要交给IOC容器管理
package star.light.annotation;
import org.springframework.stereotype.Component;
// 目标类需要交给IOC容器管理
@Component
public class Calputer {
public int divide(int i, int j) {
int result = i / j;
System.out.println("方法内部,result:" + result);
return result;
}
}
③创建切面
切面类需要交给IOC容器管理,切面类必须通过 @Aspect 注解标识为切面。在切面中需要通过指定的注解将方法标识为通知方法。
- @Before:前置通知的注解,在目标对象方法执行之前执行
- @After:后置通知注解,在目标方法的 finally 子句中执行
- @AfterReturning:返回通知注解,在目标对象方法放返回值之后执行
- @AfterThrowing:异常通知注解,在目标对象方法的 catch 子句中执行
- @Around:环绕通知注解
切入点表达式用来通知往哪些方法上切入。切入点表达式需要设置在标识通知的注解的 value 属性中,它的语法格式如下:execution([权限修饰符] 返回值类型 [全限定类名]方法名(参数列表)[异常])。其中,*
:代表一个任意类型的参数;..
:代表零个或多个任意类型的参数;我们可以使用 @Pointcut 注解来声明一个公共的切入点表达式。
- 权限修饰符
- 如果没写,就表示4个权限都包括
- 返回值类型
- 可以使用 "*" 号代替“返回值”部分,表示“返回值”不限
- 在方法的返回值部分,如果想要明确指定一个返回值类型,那么必须同时写权限修饰符
- 全限定类名
- 在包名的部分,一个 "*" 号只能代表包的层次结构中的一层,表示这一层是任意的
- 在包名的部分,使用 ".." 代表当前包以及子包下的所有类
- 在包名的部分,使用 "*.." 表示包名任意、包的层次深度任意
- 在类名的部分,类名部分整体用 "*" 号替代,表示类名任意
- 在类名的部分,可以使用 "*" 号替代类名的一部分
- 省略时表示所有的类
- 方法名
- 在方法名部分,可以使用 "*" 号表示方法名任意
- 在方法名部分,可以使用 "*" 号代替方法名的一部分
- 方法参数列表
- "()" 表示没有参数的方法
- "(*)" 表示一个参数的方法
- "(..)" 表示参数类型和参数个数任意的形参列表
- 在方法参数列表部分,基本数据类型和对应的包装类
- 异常
- 省略时表示类型类型的异常,有异常也行,没异常也行,什么类型的异常都行
我们可以在通知方法的参数位置,设置 JoinPoint 类型的参数用来获取连接点所对应方法的信息。
package star.light.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;
@Component // 切面类需要交给IOC容器管理
@Aspect // 将当前主组件标识为切面
public class LoggerAspect {
// @Pointcut声明一个公共的切入点表达式
@Pointcut("execution(* star.light.annotation.Calputer.*(..))")
public void pointCut(){
// 这个方法名只是一个标记,方法名随意
// 方法体中也不需要写任何代码
}
/**
* 第一个"*"表示任意的访问修饰符和返回值类型
* 第二个"*"表示类中任意的方法
* "(..)"表示任意的参数列表
* 类的地方也可以使用"*",表示包下所有的类
* @param joinPoint 获取连接点的信息,在Spring容器调用这个方法时自动传过来
*/
@Before("execution(* star.light.annotation.Calputer.*(..))")
public void beforeAdivceMethod(JoinPoint joinPoint){
// 获取连接点所对应方法的签名
Signature signature = joinPoint.getSignature();
// 获取连接点所对应方法的方法名
String name = signature.getName();
// 获取连接点所对应的参数
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect,前置通知,方法:" + name + ",参数:" + Arrays.toString(args));
}
@After("pointCut()")
public void afterAdiviceMethod(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 = "exception")
public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable exception){
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,异常通知,方法:" + signature.getName() + ",异常信息:" + exception);
}
// 环绕通知的方法的返回值一定要和目标对象方法的返回值一致
@Around("pointCut()")
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;
}
}
④创建 Spring 配置文件
在 Spring 的配置文件中 设置 <aop:aspectj-autoproxy />
开启基于注解的 AOP 功能
<?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">
<!--
开启组件扫描
1、如果要扫描多个包,多个包使用逗号隔开
2、如果要扫描多个包,可以写扫描包上层目录
-->
<context:component-scan base-package="star.light.annotation" ></context:component-scan>
<!--
开启AspectJ的自动代理
Spring容器在扫描类的时候,查看该类是否有@AspectJ注解,如果有,则给这个类生成代理对象
proxy-target-class="true" 表示强制使用CGLIB动态代理
proxy-target-class="false" 这是默认值,表示接口使用JDK动态代理,反之使用CGLIB动态代理
-->
<aop:aspectj-autoproxy />
</beans>
⑤在测试程序中添加如下测试方法
@Test
public void testAopByAnnotation(){
// 获取IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
Calputer calputer = applicationContext.getBean(Calputer.class);
calputer.divide(3,2);
System.out.println();
calputer.divide(1,0);
}
四、切面的优先级
①目标类
Calputer.java 内容同上;
②创建切面
我们可以通过 @Order 注解的 value 属性设置优先级,默认值 Integer 的最大值。@Order 注解的 value 属性值越小,优先级越高。
package star.light.annotation;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Order(1)
public class ValidateAspect {
@Before("star.light.annotation.LoggerAspect.pointCut()")
public void beforeMethod(){
System.out.println("ValidateAspect --> 前置通知(优先级为1)");
}
}
package star.light.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(10)
public class VerifyAspect {
@Before("star.light.annotation.LoggerAspect.pointCut()")
public void beforeMethod(){
System.out.println("VerifyAspect --> 前置通知(优先级为10)");
}
}
LoggerAspect.java 内容同上;
③创建 Spring 配置文件
Spring 的配置文件同上;
④在测试程序中添加如下测试方法
测试方法同上;
五、完全注解开发
①目标类
Calputer.java 内容同上;
②创建切面
LoggerAspect.java、ValidateAspect.java、VerifyAspect.java 内容同上;
③创建配置类
package star.light.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = {"star.light.annotation"}) //开启组件扫描
@EnableAspectJAutoProxy(proxyTargetClass = true) //开启AspectJ生成代理对象
public class AspectConfig {
}
④在测试程序中添加如下测试方法
@Test
public void testAopUseAnnotation(){
//加载配置类
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AspectConfig.class);
Calputer calputer = applicationContext.getBean(Calputer.class);
calputer.divide(1,2);
}
六、基于配置文件的AOP
①目标类
package star.light.xml;
// 目标类需要交给IOC容器管理
public class Chip {
public int divide(int i, int j) {
int result = i / j;
System.out.println("方法内部,result:" + result);
return result;
}
}
②创建切面
package star.light.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import java.util.Arrays;
// 切面类需要交给IOC容器管理
public class LogAspect {
/**
* @param joinPoint 获取连接点的信息
*/
public void beforeAdivceMethod(JoinPoint joinPoint){
// 获取连接点所对应方法的方法名
Signature signature = joinPoint.getSignature();
// 获取连接点所对应方法的方法名
String name = signature.getName();
// 获取连接点所对应的参数
Object[] args = joinPoint.getArgs();
System.out.println("LogAspect,前置通知,方法:" + name + ",参数:" + Arrays.toString(args));
}
public void afterAdiviceMethod(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("LogAspect,后置通知,方法:" + signature.getName());
}
// 在返回通知中若要获取目标对象方法的返回值,
// 只需要在配置文件中通过<aop:after-returning>标签的returning属性
// 就可以将通知方法方法的某个参数指定为接收目标对象方法的返回值的参数
public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){
Signature signature = joinPoint.getSignature();
System.out.println("LogAspect,返回通知,方法:" + signature.getName() + ",结果:" + result);
}
// 在异常通知中若要获取目标对象方法的异常信息,
// 只需要在配置文件中通过<aop:after-throwing>标签的throwing属性
// 就可以将通知方法方法的某个参数指定为接收目标对象方法出现的异常的参数
public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable exception){
Signature signature = joinPoint.getSignature();
System.out.println("LogAspect,异常通知,方法:" + signature.getName() + ",异常信息:" + exception);
}
// 环绕通知的方法的返回值一定要和目标对象方法的返回值一致
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;
}
}
③创建 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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目标类需要交给IOC容器管理 -->
<bean id="chip" class="star.light.xml.Chip"></bean>
<!-- 切面类需要交给IOC容器管理 -->
<bean id="logAspect" class="star.light.xml.LogAspect"></bean>
<aop:config>
<!-- 设置一个公共的切入点表达式 -->
<aop:pointcut id="pointCut" expression="execution(* star.light.xml.Chip.*(..))"/>
<!-- 将IOC容器中的某个bean设置为切面 -->
<aop:aspect ref="logAspect" order="1">
<!-- 配置前置通知 -->
<aop:before method="beforeAdivceMethod" pointcut-ref="pointCut" />
<!-- 配置后置通知 -->
<aop:after method="afterAdiviceMethod" pointcut-ref="pointCut" />
<!-- 配置返回通知 -->
<aop:after-returning method="afterReturningAdviceMethod" returning="result" pointcut-ref="pointCut" />
<!-- 配置异常通知 -->
<aop:after-throwing method="afterThrowingAdviceMethod" throwing="exception" pointcut-ref="pointCut" />
<!-- 配置环绕通知 -->
<aop:around method="aroundAdviceMethod" pointcut-ref="pointCut" />
</aop:aspect>
</aop:config>
</beans>
⑤在测试程序中添加如下测试方法
@Test
public void testAopByXml(){
// 获取IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
Chip chip = applicationContext.getBean("chip", Chip.class);
chip.divide(3,2);
System.out.println();
chip.divide(3,0);
}
标签:19,Spring,代理,System,result,AOP,println,方法,public
From: https://www.cnblogs.com/nanoha/p/16858628.html