1、spring AOP介绍
2、spring aop 术语
3、spring手动代理--jdk动态代理
4、spring手动代理--cglib字节码增强
5、spring编写代理--半自动ProxyFactoryBean
6、spring aop编程:全自动
7、AspectJ介绍及切入点表达式
8、AspectJ 通知类型
9、AspectJ 基于xml
10、AspectJ 基于注解
11、基于注解进行 AOP 开发的demo
12、自定义注解 + AOP 编程
1、spring AOP介绍 <--返回目录
AOP:Aspect Oriented Programming的缩写,面向切面编程
使用AOP的好处:利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性;AOP采取【横向抽取机制】,取代了传统纵向继承体系重复性代码
AOP经典应用:事务管理、性能监视、安全检查、缓存、日志等。
Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码。
AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入
aop底层将采用代理机制进行实现
- 接口 + 实现类 :spring采用 jdk 的动态代理Proxy。
- 实现类:spring 采用 cglib字节码增强。
2、spring aop 术语 <--返回目录
1) target:目标类, 需要被代理的类。例如:UserService
2) Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
3) PointCut 切入点:已经被增强的连接点。例如:addUser()
4) advice 通知/增强,增强代码。例如:after、before
5) Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
6) proxy 代理类
7) Aspect(切面): 是切入点pointcut和通知advice的结合
一个线是一个特殊的面。一个切入点和一个通知,组成成一个特殊的面。
3、spring手动代理--jdk动态代理 <--返回目录
手动代理:自己打代码完成一个切面编程
jdk动态代理
- JDK动态代理,是对"装饰者"设计模式的简化。使用前提:必须有接口
1) 目标类:接口 + 实现类
2) 切面类:用于存通知,切面类取名MyAspect
3) 工厂类:编写工厂生成代理
目标类:接口+实现类(代码省略)
public interface UserService { public void addUser(); // 连接点 public void updateUser(); // 连接点 public void deleteUser(); // 连接点 }
切面类(里面的方法都是通知)
public class MyAspect { public void before(){ // 通知 System.out.println("鸡首"); } public void after(){ // 通知 System.out.println("牛后"); } }
生成代理的工厂
public class MyBeanFactory { public static UserService createService(){ //1 目标类 final UserService userService = new UserServiceImpl(); //2 切面类 final MyAspect myAspect = new MyAspect(); /* 3 代理类:将目标类(切入点)和 切面类(通知) 结合 --> 切面 * Proxy.newProxyInstance * 参数1:loader ,类加载器,动态代理类 运行时创建,任何类都需要类加载器将其加载到内存。 * 一般情况:当前类.class.getClassLoader(); * 目标类实例.getClass().get... * 参数2:Class[] interfaces 代理类需要实现的所有接口 * 方式1:目标类实例.getClass().getInterfaces() ;注意:只能获得自己接口,不能获得父元素接口 * 方式2:new Class[]{UserService.class} * 例如:jdbc 驱动 --> DriverManager 获得接口 Connection * 参数3:InvocationHandler 处理类,接口,必须进行实现类,一般采用匿名内部 * 提供 invoke 方法,代理类的每一个方法执行时,都将调用一次invoke * 参数31:Object proxy :代理对象 * 参数32:Method method : 代理对象当前执行的方法的描述对象(反射) * 执行方法名:method.getName() * 执行方法:method.invoke(对象,实际参数) * 参数33:Object[] args :方法实际参数 * */ UserService proxyService = (UserService)Proxy.newProxyInstance( MyBeanFactory.class.getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //前执行 myAspect.before(); //执行目标类的方法 Object obj = method.invoke(userService, args); //后执行 myAspect.after(); return obj; } }); return proxyService; } }
测试:
UserService userService = MyBeanFactory.createService();
userService.addUser();
4、spring手动代理--cglib字节码增强 <--返回目录
cglib字节码增强:没有接口,只有实现类。采用字节码增强框架cglib,在运行时创建目标类的子类,从而对目标类进行增强。
导入jar包:
核心:hibernate-distribution-3.6.10.Finallibytecodecglibcglib-2.2.jar
依赖:struts-2.3.15.3appsstruts2-blankWEB-INFlibasm-3.3.jar
spring-core..jar 已经整合以上两个内容
目标类:
public class UserServiceImpl { public void addUser() { System.out.println("添加方法"); } public void updateUser() { System.out.println("修改方法"); } public void deleteUser() { System.out.println("删除方法"); } }
切面类:
public class MyAspect { public void before(){ System.out.println("鸡首"); } public void after(){ System.out.println("牛后"); } }
代理工厂
public class MyBeanFactory { public static UserServiceImpl createService() { // 1 目标类 final UserServiceImpl userService = new UserServiceImpl(); // 2 切面类 final MyAspect myAspect = new MyAspect(); // 3 代理类,采用cglib,底层创建目标类的子类 // 3.1 核心类 Enhancer enhancer = new Enhancer(); // 3.2 确定父类 enhancer.setSuperclass(userService.getClass()); // 3.3 设置回调函数,MethodInterceptor接口等效与jdk Invocationhandler接口 enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 前执行 myAspect.before(); // 执行目标类的方法 Object obj = method.invoke(userService, args); // methodProxy.invokeSuper(proxy,args);//执行代理类的父类的方法,也即是执行目标类,因为目标类就是代理类的父类 // 后执行 myAspect.after(); return obj; } }); // 3.4 创建代理 UserServiceImpl proxyService = (UserServiceImpl) enhancer.create(); return proxyService; } }
5、spring编写代理--半自动 <--返回目录
所谓半自动:即使用 spring 提供的代理工厂,我们提供目标类(代理对象)和 通知(增强)。
既然是我们提供通知,那么 spring 如何知道哪个通知是前置,哪个是后置呢?所以,aop联盟提供了一个关于通知类型的规范(即一组接口)。
aop联盟通知类型:Spring按照通知Advice在目标类方法的连接点位置,可以分为5类
- 前置通知 org.springframework.aop.MethodBeforeAdvice,在目标方法执行前实施增强
- 后置通知 org.springframework.aop.AfterReturningAdvice,在目标方法执行后实施增强
- 环绕通知 org.aopalliance.intercept.MethodInterceptor,在目标方法执行前后实施增强
- 异常抛出通知 org.springframework.aop.ThrowsAdvice,在方法抛出异常后实施增强,方法包括目标方法代码和通知方法代码
- 引介通知 org.springframework.aop.IntroductionInterceptor,在目标类中添加一些新的方法和属性
环绕通知,必须手动执行目标方法
try{ //前置通知 //执行目标方法 //后置通知 } catch(){ //抛出异常通知 }
需要导入的jar:
- 核心4+1:beans, context, core, express + commons-logging
- aop编程:aop联盟(规范) com.springsource.org.aopalliance-1.0.0.jar,spring-aop(实现) spring-aop-3.2.0.RELEASE.jar
目标类:接口UserService和实现类UserServiceImpl
切面类
/* 切面类中确定通知,需要实现不同接口,接口就是规范,从而就确定方法名称。 采用"环绕通知"MethodInterceptor */ public class MyAspect implements MethodInterceptor{ @Override public Object invoke(MethodInvocation mi)throws Throwable { System.out.println("前"); //手动执行目标方法 Object obj = mi.proceed(); System.out.println("后"); return obj; } }
applicationContext.xml配置文件
<!--创建目标类--> <bean id="userServiceId" class="cn.oy.service.UserServiceImpl"/> <!--创建切面类--> <bean id="myAspectId" class="cn.oy.aspect.MyAspect"/> <!--创建代理类 使用工厂FactoryBean,底层调用getObject()方法返回特殊bean ProxyFactoryBean:用于创建代理工厂bean,生成特殊代理对象 interfaces:指定接口们,通过<array>可以设置多个值,只有一个值时,<property name="interfaces" value=""/> target:指定目标类 interceptorNames:切面类的名称。类型是字符串数组,如果只有一个值,<property name="interceptorNames" value="myAspectId"/> 底层机制:如果目标类有接口,采用jdk动态代理 如果没有接口,采用cglib 如果声明optimize = true ,无论是否有接口,都使用cglib --> <bean id="proxyServiceId" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="interfaces"> <array> <value>cn.oy.dao.UserService</value> </array> </property> <property name="target" ref="userServiceId"></property> <property name="interceptorNames" value="myAspectId"></property> </bean>
测试
String xmlPath = "applicationContext.xml"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); UserService userService = (UserService) applicationContext.getBean("proxyServiceId");//获取代理类 userService.addUser();
6、spring aop编程:全自动 <--返回目录
全自动:从spring容器获得目标类,如果配置了aop,spring将自动生成代理。从而我们就拿到了目标类的代理对象。
需要导入的jar:
- 核心4+1:beans, context, core, express + commons-logging
- aop编程:aop联盟(规范) com.springsource.org.aopalliance-1.0.0.jar,spring-aop(实现) spring-aop-3.2.0.RELEASE.jar
- 要确定目标类,要使用aspectj切入点表达式 com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
目标类:接口UserService和实现类UserServiceImpl
切面类
/** * 切面类中确定通知,需要实现不同接口,接口就是规范,从而就确定方法名称。 * * 采用"环绕通知" MethodInterceptor */ public class MyAspect implements MethodInterceptor { @Override public Object invoke(MethodInvocation mi) throws Throwable { System.out.println("前3"); //手动执行目标方法 Object obj = mi.proceed(); System.out.println("后3"); return obj; } }
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 http://www.springframework.org/schema/aop/spring-aop.xsd】"> <!-- 1 创建目标类 --> <bean id="userServiceId" class="com.oy.aop.UserServiceImpl"></bean> <!-- 2 创建切面类(通知) --> <bean id="myAspectId" class="com.oy.aop.MyAspect"></bean> <!-- 3 aop编程 3.1 导入命名空间 3.2 使用 <aop:config>进行配置 proxy-target-class="true" 声明时使用cglib代理 <aop:pointcut> 切入点 ,从目标对象获得具体方法 <aop:advisor> 特殊的切面,只有一个通知 和 一个切入点 advice-ref 通知引用 pointcut-ref 切入点引用 3.3 切入点表达式 execution(* com.oy.aop.*.*(..)) 选择方法 返回值任意 包 类名任意 方法名任意 参数任意 --> <aop:config proxy-target-class="true"> <aop:pointcut expression="execution(* com.oy.aop.*.*(..))" id="myPointCut"/> <aop:advisor advice-ref="myAspectId" pointcut-ref="myPointCut"/> </aop:config> </beans>
测试
String xmlPath = "applicationContext.xml"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); //获得目标类(由于配置了aop,spring将自动生成代理) UserService userService = (UserService) applicationContext.getBean("userServiceId"); userService.addUser();
7、AspectJ介绍及切入点表达式 <--返回目录
* AspectJ是一个基于Java语言的AOP框架
* 切入点表达式(掌握)
- 1.execution()用于描述方法【掌握】
语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常)
修饰符,一般省略
public 公共方法
* 任意
返回值,不能省略
void 返回没有值
String 返回值字符串
* 任意
包,[省略]
com.oy.crm 固定包
com.oy.crm.*.service crm包下面子包任意 (例如:com.oy.crm.staff.service)
com.oy.crm.. crm包下面的所有子包(含自己)
com.oy.crm.*.service.. crm包下面任意子包,固定目录service,service目录任意包
类,[省略]
UserServiceImpl 指定类
*Impl 以Impl结尾
User* 以User开头
* 任意
方法名,不能省略
addUser 固定方法
add* 以add开头
*Do 以Do结尾
* 任意
(参数)
() 无参
(int) 一个整型
(int ,int) 两个
(..) 参数任意
throws ,可省略,一般不写。
综合1
execution(* com.oy.crm.*.service..*.*(..))
综合2
<aop:pointcut expression="execution(* com.oy.*WithCommit.*(..)) ||
execution(* com.oy.*Service.*(..))" id="myPointCut"/>
- 2.within:匹配包或子包中的方法(了解)
within(com.oy.aop..*)
- 3.this:匹配实现接口的代理对象中的方法(了解)
this(com.oy.aop.user.UserDAO)
- 4.target:匹配实现接口的目标对象中的方法(了解)
target(com.oy.aop.user.UserDAO)
- 5.args:匹配参数格式符合标准的方法(了解)
args(int,int)
- 6.bean(id) 对指定的bean所有的方法(了解)
bean('userServiceId')
8、AspectJ 通知类型 <--返回目录
aop联盟定义通知类型,具有特性接口,必须实现,从而确定方法名称。
aspectj 通知类型,只定义类型名称,以及方法格式。
before:前置通知(应用:各种校验),在方法执行前执行,如果通知抛出异常,阻止方法运行
afterReturning:后置通知(应用:常规数据处理),方法正常返回后执行,如果方法中抛出异常,通知无法执行;必须在方法执行后才执行,所以可以获得方法的返回值。
around:环绕通知(应用:十分强大,可以做任何事情【掌握】),方法执行前后分别执行,可以阻止方法的执行;必须手动执行目标方法
afterThrowing:抛出异常通知(应用:包装异常信息),方法抛出异常后执行,如果方法没有抛出异常,无法执行
after:最终通知(应用:清理现场),方法执行完毕后执行,无论方法中是否出现异常
环绕通知
try{ //前置:before //手动执行目标方法 //后置:afterReturning } catch(){ //抛出异常 afterThrowing } finally{ //最终 after }
9、AspectJ 基于xml <--返回目录
使用AspectJ实现切面编程,需要导入的:
- 核心4+1:beans, context, core, express + commons-logging - aop联盟(规范) com.springsource.org.aopalliance-1.0.0.jar - spring-aop(实现) spring-aop-3.2.0.RELEASE.jar - aspect 规范 com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar - spring aspect 实现 spring-aspects-3.2.0.RELEASE.jar
步骤:
1) 目标类:接口 + 实现
2) 切面类:编写多个通知,采用aspectj 通知名称任意(方法名任意)
3) aop编程,将通知应用到目标类
目标类:接口和实现类
public interface UserService { public void addUser(); public void updateUser(); public void deleteUser(); } public class UserService { public void addUser(){ System.out.println("addUser"); } public void updateUser(){ System.out.println("updateUser"); } public void deleteUser(){ System.out.println("deleteUser"); } }
切面类
public class MyAspect { public void myBefore(JoinPoint joinPoint){ System.out.println("前置通知 : " + joinPoint.getSignature().getName()); } public void myAfterReturning(JoinPoint joinPoint,Object ret){ System.out.println("后置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret); } public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("前"); //手动执行目标方法 Object obj = joinPoint.proceed(); System.out.println("后"); return obj; } public void myAfterThrowing(JoinPoint joinPoint,Throwable e){ System.out.println("抛出异常通知 : " + e.getMessage()); } public void myAfter(JoinPoint joinPoint){ System.out.println("最终通知"); } }
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 http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 1 创建目标类 --> <bean id="userServiceId" class="com.oy.d_aspect.a_xml.UserServiceImpl"></bean> <!-- 2 创建切面类(通知) --> <bean id="myAspectId" class="com.oy.d_aspect.a_xml.MyAspect"></bean> <!-- 3 aop编程 <aop:aspect> 将切面类 声明“切面”,从而获得通知(方法) ref 切面类引用 <aop:pointcut> 声明一个切入点,所有的通知都可以使用。 expression 切入点表达式 id 名称,用于其它通知引用 --> <aop:config> <aop:aspect ref="myAspectId"> <aop:pointcut expression="execution(* com.oy.d_aspect.a_xml.UserServiceImpl.*(..))" id="myPointCut"/> <!-- 3.1 前置通知 <aop:before method="" pointcut="" pointcut-ref=""/> method : 通知,及方法名 pointcut :切入点表达式,此表达式只能当前通知使用。 pointcut-ref : 切入点引用,可以与其他通知共享切入点。 通知方法格式:public void myBefore(JoinPoint joinPoint){ 参数1:org.aspectj.lang.JoinPoint 用于描述连接点(目标方法),获得目标方法名等 例如: <aop:before method="myBefore" pointcut-ref="myPointCut"/> --> <!-- 3.2后置通知 ,目标方法后执行,获得返回值 <aop:after-returning method="" pointcut-ref="" returning=""/> returning 通知方法第二个参数的名称 通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object ret){ 参数1:连接点描述 参数2:类型Object,参数名 returning="ret" 配置的 例如: <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret" /> --> <!-- 3.3 环绕通知 <aop:around method="" pointcut-ref=""/> 通知方法格式:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ 返回值类型:Object 方法名:任意 参数:org.aspectj.lang.ProceedingJoinPoint 抛出异常 执行目标方法:Object obj = joinPoint.proceed(); 例如: <aop:around method="myAround" pointcut-ref="myPointCut"/> --> <!-- 3.4 抛出异常 <aop:after-throwing method="" pointcut-ref="" throwing=""/> throwing :通知方法的第二个参数名称 通知方法格式:public void myAfterThrowing(JoinPoint joinPoint,Throwable e){ 参数1:连接点描述对象 参数2:获得异常信息,类型Throwable ,参数名由throwing="e" 配置 例如: <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/> --> <!-- 3.5 最终通知 --> <aop:after method="myAfter" pointcut-ref="myPointCut"/> </aop:aspect> </aop:config> </beans>
10、AspectJ 基于注解 <--返回目录
spring 配置文件中配置:注解扫描,声明启动aspectJ 自动代理(JavaConfig 声明启动aspectJ 自动代理: @EnableAspectJAutoProty)
<!-- 扫描注解类-->
<context:component-scan base-package=""/>
<!-- 声明aop注解生效-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
切面类使用注解
@Component @Aspect public class MyAspect { // 切入点当前有效 @Before("execution(* cn.oy.service.UserServiceImpl.*(..))") public void myBefore(JoinPoint joinPoint) { System.out.println("前置通知 : " + joinPoint.getSignature().getName()); } // 声明公共切入点 @Pointcut("execution(* cn.oy.service.UserServiceImpl.*(..))") private void myPointCut() { } @AfterReturning(value="myPointCut()" ,returning="returnValue") public void myAfterReturning(JoinPoint joinPoint, Object returnValue) { System.out.println("后置通知 : " + joinPoint.getSignature().getName() + " , -->" + returnValue); } @Around(value = "myPointCut()") public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("前"); // 手动执行目标方法 Object obj = joinPoint.proceed(); System.out.println("后"); return obj; } @AfterThrowing(value="execution(* cn.oy.service.UserServiceImpl.*(..))" ,throwing="e") public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println("抛出异常通知 : " + e.getMessage()); } public void myAfter(JoinPoint joinPoint) { System.out.println("最终通知"); } }
替换 <aop:before method="myBefore" pointcut="execution(* com.oy.anno.UserServiceImpl.*(..))"/>
//切入点当前有效 @Before("execution(* com.oy.anno.UserServiceImpl.*(..))") public void myBefore(JoinPoint joinPoint){ System.out.println("前置通知 : " + joinPoint.getSignature().getName()); }
替换 <aop:before method="myBefore" pointcut-ref="" />
// 声明公共切入点 @Pointcut("execution(* cn.oy.service.UserServiceImpl.*(..))") private void myPointCut() { } @AfterReturning(value="myPointCut()" ,returning="returnValue") public void myAfterReturning(JoinPoint joinPoint, Object returnValue) { System.out.println("后置通知 : " + joinPoint.getSignature().getName() + " , -->" + returnValue); }
11、基于注解进行 AOP 开发的demo <--返回目录
依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
application.properties
server.port=8089
server.servlet.context-path=/BootDemo
切面类
package com.oy.aspect; import java.util.Arrays; import javax.servlet.http.HttpServletRequest; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; /** * 自定义切面类 * * @author oy * @version 1.0 * @date 2020年4月13日 * @time 下午5:55:06 */ @Component @Aspect public class LoggerAspect { // 定义公共切入点 @Pointcut("execution(public * com.oy.controller.IndexController.*(..))") public void webLog() { } @Before("webLog()") public void deBefore(JoinPoint joinPoint) throws Throwable { // 接收到请求,记录请求内容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 记录下请求内容 System.out.println("URL : " + request.getRequestURL().toString()); System.out.println("HTTP_METHOD : " + request.getMethod()); System.out.println("IP : " + request.getRemoteAddr()); System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs())); } /* @AfterReturning(returning = "ret", pointcut = "webLog()") public void doAfterReturning(Object ret) throws Throwable { // 处理完请求,返回内容 System.out.println("方法的返回值 : " + ret); } // 后置异常通知 @AfterThrowing("webLog()") public void throwss(JoinPoint jp) { System.out.println("方法异常时执行....."); } // 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行 @After("webLog()") public void after(JoinPoint jp) { System.out.println("方法最后执行....."); } // 环绕通知,环绕增强,相当于MethodInterceptor @Around("webLog()") public Object arround(ProceedingJoinPoint pjp) { System.out.println("方法环绕start....."); try { Object o = pjp.proceed(); System.out.println("方法环绕proceed,结果是 :" + o); return o; } catch (Throwable e) { e.printStackTrace(); return null; } } */ }
测试 controller
package com.oy.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/index") public class IndexController { @RequestMapping("/test1") public String test1(String name, int age) { return "test1"; } @RequestMapping("/test2") public String test2() { return "test1"; } }
启动 springboot 项目,访问 http://localhost:8089/BootDemo/index/test1?name=xxx&age=10,控制台打印结果
URL : http://localhost:8089/BootDemo/index/test1 HTTP_METHOD : GET IP : 0:0:0:0:0:0:0:1 CLASS_METHOD : com.oy.controller.IndexController.test1 ARGS : [xxx, 10]
12、自定义注解 + AOP 编程 <--返回目录
如果是要对类的几个方法进行增强(即切入点是类的某些方法,不是所有),使用注解就比较方便。
自定义注解
package com.oy.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) public @interface OutputLog { boolean value() default true; }
切面类
package com.oy.aspect; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; /** * 自定义切面类 * * @author oy * @version 1.0 * @date 2020年4月13日 * @time 下午5:55:06 */ @Component @Aspect public class LoggerAspect { // 定义切入点 @Pointcut("@annotation(com.oy.annotation.OutputLog)") public void webLog() { } // 环绕通知, 环绕增强, 相当于MethodInterceptor @Around("webLog()") public Object arround(ProceedingJoinPoint pjp) throws Throwable { long startTime = System.currentTimeMillis(); Object result = pjp.proceed(); long time = System.currentTimeMillis() - startTime; ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 获取handler的参数(不包括 request 和 response) List<Object> logArgs = Arrays.stream(pjp.getArgs()) .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse))) .collect(Collectors.toList()); System.out.println("请求uri: " + request.getRequestURI()); System.out.println("请求参数: " + logArgs); System.out.println("handler执行时长:" + time + " ms"); return result; } }
测试 controller
package com.oy.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.oy.annotation.OutputLog; @RestController @RequestMapping("/index") public class IndexController { @OutputLog @RequestMapping("/test1") public String test1(String name, int age) { for (int i = 0; i < 100000; i++) { System.out.println(i); } return "test1"; } @RequestMapping("/test2") public String test2() { return "test1"; } }
启动 springboot 项目,访问 http://localhost:8089/BootDemo/index/test1?name=xxx&age=10,控制台打印结果
99998 99999 请求uri: /BootDemo/index/test1 请求参数: [xxx, 10] handler执行时长:565 ms
参考: