摘要:面向切面的程序设计思想也是面向切面软件开发的基础。与切面相关的编程概念还包括元对象协议主题混入和委托。切面声明类似于中的类声明,在中会包含着一些以及相应的。
spring-boot-aop 什么是aop
面向切面的程序设计(Aspect-oriented programming,AOP,又译作面向方面的程序设计、剖面导向程序设计)是计算机科学中的一种程序设计思想,旨在将横切关注点与业务主体进行进一步分离,以提高程序代码的模块化程度。通过在现有代码基础上增加额外的通知(Advice)机制,能够对被声明为“切点(Pointcut)”的代码块进行统一管理与装饰,如“对所有方法名以‘set*’开头的方法添加后台日志”。该思想使得开发人员能够将与代码核心业务逻辑关系不那么密切的功能(如日志功能)添加至程序中,同时又不降低业务代码的可读性。面向切面的程序设计思想也是面向切面软件开发的基础。
面向切面的程序设计将代码逻辑切分为不同的模块(即关注点(Concern),一段特定的逻辑功能)。几乎所有的编程思想都涉及代码功能的分类,将各个关注点封装成独立的抽象模块(如函数、过程、模块、类以及方法等),后者又可供进一步实现、封装和重写。部分关注点“横切”程序代码中的数个模块,即在多个模块中都有出现,它们即被称作“横切关注点(Cross-cutting concerns, Horizontal concerns)”。
日志功能即是横切关注点的一个典型案例,因为日志功能往往横跨系统中的每个业务模块,即“横切”所有有日志需求的类及方法体。而对于一个信用卡应用程序来说,存款、取款、帐单管理是它的核心关注点,日志和持久化将成为横切整个对象结构的横切关注点。
切面的概念源于对面向对象的程序设计的改进,但并不只限于此,它还可以用来改进传统的函数。与切面相关的编程概念还包括元对象协议、主题(Subject)、混入(Mixin)和委托(Delegate)。
AOP中的相关概念看过了上面解释,想必大家对aop已经有个大致的雏形了,但是又对上面提到的切面之类的术语有一些模糊的地方,接下来就来讲解一下AOP中的相关概念,了解了AOP中的概念,才能真正的掌握AOP的精髓。
Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target(目标对象):织入 Advice 的目标对象.。
Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
spring aopSpring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器,它在运行期通过代理方式向目标类织入增强代码。在Spring中可以无缝地将Spring AOP、IoC和AspectJ整合在一起。Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。在Java中动态代理有两种方式:JDK动态代理和CGLib动态代理
jdk proxy
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** ** * @author leone * @since 2018-11-09 **/ public class JdkProxy { interface IUserService { Integer delete(Integer userId); } static class UserServiceImpl implements IUserService { @Override public Integer delete(Integer userId) { // 业务 System.out.println("delete user"); return userId; } } // 自定义InvocationHandler static class UserServiceProxy implements InvocationHandler { // 目标对象 private Object target; public UserServiceProxy(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("------方法调用前---------"); //执行相应的目标方法 Object result = method.invoke(target, args); System.out.println("------方法调用后---------"); return result; } } public static void main(String[] args) { IUserService userService = new UserServiceImpl(); // 创建调用处理类 UserServiceProxy handler = new UserServiceProxy(userService); // 得到代理类实例 IUserService proxy = (IUserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), new Class[]{IUserService.class}, handler); // 调用代理类的方法 Integer userId = proxy.delete(3); System.out.println(userId); } }
cglib proxy
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** ** * @author leone * @since 2018-11-09 **/ public class CglibProxy { static class UserService implements MethodInterceptor { private Object target; /** * 业务方法 * * @param userId * @return */ public Integer delete(Integer userId) { System.out.println("delete user"); return userId; } /** * 利用Enhancer类生成代理类 * * @param target * @return */ public Object getInstance(Object target) { this.target = target; // 创建加强器,用来创建动态代理类 Enhancer enhancer = new Enhancer(); // 为加强器指定要代理的业务类(即:为下面生成的代理类指定父类) enhancer.setSuperclass(target.getClass()); // 设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦 enhancer.setCallback(this); // 创建动态代理类对象并返回 return enhancer.create(); } /** * @param o * @param method * @param objects * @param methodProxy * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("------方法调用前---------"); Object object = methodProxy.invokeSuper(o, objects); System.out.println("------方法调用后---------"); return object; } } public static void main(String[] args) { UserService userService = new UserService(); UserService proxy = (UserService) userService.getInstance(userService); Integer userId = proxy.delete(2); System.out.println(userId); } }
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,可以强制使用CGLIB实现AOP
2、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
Advice的主要类型org.springframework.boot spring-boot-starter-aop
@Before:该注解标注的方法在业务模块代码执行之前执行,其不能阻止业务模块的执行,除非抛出异常;
@AfterReturning:该注解标注的方法在业务模块代码执行之后执行;
@AfterThrowing:该注解标注的方法在业务模块抛出指定异常后执行;
@After:该注解标注的方法在所有的Advice执行完成后执行,无论业务模块是否抛出异常,类似于finally的作用;
@Around:该注解功能最为强大,其所标注的方法用于编写包裹业务模块执行的代码,其可以传入一个ProceedingJoinPoint用于调用业务模块的代码,无论是调用前逻辑还是调用后逻辑,都可以在该方法中编写,甚至其可以根据一定的条件而阻断业务模块的调用;
@DeclareParents:其是一种Introduction类型的模型,在属性声明上使用,主要用于为指定的业务模块添加新的接口和相应的实现。
切点表达式1.通配符
[*] 匹配任意字符,但只能匹配一个元素
[..] 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用
[+] 必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类
2.逻辑运算符
表达式可由多个切点函数通过逻辑运算组成
&& 与操作,求交集,也可以写成and
例如 execution(* chop(..)) && target(Horseman) 表示Horseman及其子类的chop方法
|| 或操作,任一表达式成立即为true,也可以写成 or
例如 execution(* chop(..)) || args(String) 表示名称为chop的方法或者有一个String型参数的方法
! 非操作,表达式为false则结果为true,也可以写成 not
例如 execution(* chop(..)) and !args(String) 表示名称为chop的方法但是不能是只有一个String型参数的方法
execution() 方法匹配模式串
表示满足某一匹配模式的所有目标类方法连接点。如execution(* save(..))表示所有目标类中的 save()方法。
由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。如下是execution表达式的语法
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
execution(<修饰符> <返回类型> <类路径> <方法名>(<参数列表>) <异常模式> )
modifiers-pattern:方法的可见性,如public,protected;
ret-type-pattern:方法的返回值类型,如int,void等;
declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
name-pattern:方法名类型,如buisinessService();
param-pattern:方法的参数类型,如java.lang.String;
throws-pattern:方法抛出的异常类型,如java.lang.Exception;
切点函数@annotation(annotation-type) 方法注解类名
如下示例表示匹配使用com.leone.aop.AopTest注解标注的方法:
@annotation(com.leone.aop.AopTest)
args(param-pattern) 方法入参切点函数
如下示例表示匹配所有只有一个参数,并且参数类型是java.lang.String类型的方法:
args(java.lang.String)
@args(annotation-type) 方法入参类注解切点函数
如下示例表示匹配使用了com.leone.aop.AopTest注解标注的类作为参数的方法:
@args(com.leone.aop.AopTest)
within(declaring-type-pattern) 类名匹配切点函数
within表达式只能指定到类级别,如下示例表示匹配com.leone.aop.UserService中的所有方法:
within(com.leone.aop.UserService)
@within(annotation-type) 类注解匹配切点函数
如下示例表示匹配使用org.springframework.web.bind.annotation.RestController注解标注的类:
@within(org.springframework.web.bind.annotation.RestController)
target(declaring-type-pattern) 类名切点函数
如下示例表示匹配com.leone.aop.UserService中的所有方法:
target(com.leone.aop.UserService)
this
spring-boot-aop 实战配置切面类,实现代理
1.在类上使用 @Component 注解把切面类加入到IOC容器中
2.在类上使用 @Aspect 注解使之成为切面类
import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; /** * 描述一个切面类 * * @author leone * @since 2018-06-21 **/ @Slf4j @Aspect @Component public class AopConfig { /** * 1.通配符 * [*] 匹配任意字符,但只能匹配一个元素 ** [..] 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用 *
* [+] 必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类 *
* 切点表达式分为 修饰符 返回类型 包路径 方法名 参数 *
* 2.切点表达式 *
* 3.逻辑运算符 * 表达式可由多个切点函数通过逻辑运算组成 * ** && 与操作,求交集,也可以写成and *
* 例如 execution(* chop(..)) && target(Horseman) 表示Horseman及其子类的chop方法 *
* ** || 或操作,任一表达式成立即为true,也可以写成 or *
* 例如 execution(* chop(..)) || args(String) 表示名称为chop的方法或者有一个String型参数的方法 *
* ** ! 非操作,表达式为false则结果为true,也可以写成 not *
* 例如 execution(* chop(..)) and !args(String) 表示名称为chop的方法但是不能是只有一个String型参数的方法 */ @Pointcut("execution(* com.leone.boot.aop.service.*.*(..))") public void pointCut() { } /** * 环绕通知在 target 开始和结束执行 * * @param point * @return */ @Around(value = "pointCut()") public Object around(ProceedingJoinPoint point) { long start = System.currentTimeMillis(); String methodName = point.getSignature().getName(); log.info("around method name: {} params: {}", methodName, Arrays.asList(point.getArgs())); try { log.info("around end time: {}", (System.currentTimeMillis() - start) + " ms!"); return point.proceed(); } catch (Throwable e) { log.error("message: {}", e.getMessage()); } return null; } /** * 前置通知在 target 前执行 * * @param joinPoint */ // @Before("@annotation(com.leone.boot.aop.anno.AopBefore)") // @Before("within(com.leone.boot.aop.controller.*)") // @Before("@within(org.springframework.web.bind.annotation.RestController)") // @Before("target(com.leone.boot.aop.controller.UserController)") @Before("@target(com.leone.boot.aop.anno.ClassAop) && @annotation(com.leone.boot.aop.anno.AopBefore)") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List
测试类
import com.leone.boot.aop.anno.AopBefore; import com.leone.boot.aop.anno.ClassAop; import com.leone.boot.aop.interf.UserService; import com.leone.boot.common.entity.User; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * @author leone * @since 2018-06-21 **/ @Slf4j @ClassAop @RestController @RequestMapping("/api") public class UserController { private UserService userService; public UserController(UserService userService) { this.userService = userService; } @AopBefore @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) public User findOne(@PathVariable Long userId) { return userService.findOne(userId); } @AopBefore @RequestMapping("/user") public User save(User user) { return user; } }
github
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/74627.html
摘要:通知和切点共同定义了关于切面的全部内容,它是什么时候,在何时和何处完成功能引入允许我们向现有的类添加新的方法或者属性组装方面来创建一个被通知对象。这可以在编译时完成例如使用编译器,也可以在运行时完成。和其他纯框架一样,在运行时完成织入。 原文:190301-SpringBoot基础篇AOP之基本使用姿势小结 一般来讲,谈到Spring的特性,绕不过去的就是DI(依赖注入)和AOP(切...
摘要:我们会写切面来拦截对这些业务类和类的调用。切面定义何时拦截一个方法以及做什么和在一起成为切面连接点当代码开始执行,并且切点的条件满足时,通知被调用。 前言 这篇文章会帮助你使用Spring Boot Starter AOP实现AOP。我们会使用AspectJ实现四个不同的通知(advice),并且新建一个自定义的注解来追踪方法的执行时间。 你将会了解 什么是交叉分割关注点(cross...
摘要:首先先来看我们事先定义的以及。可以看到会修改方法的返回值,使其返回。例子测试的行为最简单的测试方法就是直接调用,看看它是否使用返回。先看这段代码这些是利用提供的和来判断是否被代理了的实现是通过动态代理来做的。 Github地址 Spring提供了一套AOP工具,但是当你把各种Aspect写完之后,如何确定这些Aspect都正确的应用到目标Bean上了呢?本章将举例说明如何对Spring...
摘要:示例代码如下添加的设置默认的配置对应的是原来的如何使用注解从主库到备库的切换 摘要: 本篇文章的场景是做调度中心和监控中心时的需求,后端使用TDDL实现分表分库,需求:实现关键业务的查询监控,当用Mybatis查询数据时需要从主库切换到备库或者直接连到备库上查询,从而减小主库的压力,在本篇文章中主要记录在Spring Boot中通过自定义注解结合AOP实现直接连接备库查询。 一.通过A...
摘要:接口日志有啥用在我们日常的开发过程中,我们可以通过接口日志去查看这个接口的一些详细信息。在切入点返回内容之后切入内容可以用来对处理返回值做一些加工处理。 接口日志有啥用 在我们日常的开发过程中,我们可以通过接口日志去查看这个接口的一些详细信息。比如客户端的IP,客户端的类型,响应的时间,请求的类型,请求的接口方法等等,我们可以对这些数据进行统计分析,提取出我们想要的信息。 怎么拿到接口...
阅读 2179·2021-10-18 13:28
阅读 2483·2021-10-11 10:59
阅读 2313·2019-08-29 15:06
阅读 1110·2019-08-26 13:54
阅读 790·2019-08-26 13:52
阅读 3130·2019-08-26 12:02
阅读 2980·2019-08-26 11:44
阅读 2468·2019-08-26 10:56