(Aspect Oriented Programming)软件系统定制开发面向切面编程与OOP (Object-Oriented Programming)软件系统定制开发面向对象编程在java软件系统定制开发中都占有非常重要的地位,Java软件系统定制开发是一个面向对象编程的语言,软件系统定制开发面向切面编程通过提供软件系统定制开发对程序结构不同的思维方式对OOP进行补充。对于OOP来说,软件系统定制开发最主要的模块单元是类,对于AOP软件系统定制开发来说是切面(aspect).软件系统定制开发这些切面使关注点模块化,软件系统定制开发例如跨多个类或对象、方法的事务管理。此类关注点通常被称为横切关注点
Spring的关键组件之一是AOP框架。 尽管Spring IOC容器不依赖于AOP,这意味着在不需要时就不需要使用AOP,但AOP是对Spring IOC的补充,可以提供功能强大的中间件解决方案。
如果该类中其他的方法也要输出方法输入参数呢?更进一步,其他类中的其他方法也要输出方法输入参数呢?如果不介意成本或者代码的优雅,当然可以一个方法一个方法像上面那样的添加代码。
但是AOP给我们另外一个更优雅的解决方案,在每个方法执行之前切入相同的逻辑。而这个切面所执行相同的逻辑,以下都会用增强这个词来替代,英文advice。
进行增强的目标有类、对象或者方法,
其实最终还是执行方法之上(Spring AOP并不支持属性的增强)
,所以这些类、对象或者方法都可以称之为连接点(joint point),对应Java类org.aspectj.lang.JoinPoint。另外,究竟是哪些类、对象或者方法需要增强呢?这就需要通过切入点来匹配了。切入点(point cut)简称切点,对应类org.aspectj.lang.reflect.Pointcut。切点用于匹配连接点(二者不是一个概念)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-36RzQABj-1647596207548)(SpringBoot/img/webp.webp)]
1、AOP的操作步骤
1、开启注解
首先开启Aspect(通过加入注解
@EnableAspectJAutoProxy
开启)
2、定义自己的切面类
Aspect
只能标识这个类作为一个切面,而没有作为Spring容器扫描的标识。
代码如下:
@Component@Aspect // 切面public class aop6 { /*要代理的目标类是否实现了指定的接口*/ // 切入点 @Pointcut("@target(com.chengshiyu.springboot9_day.annotation.AnCustomAnnotation)") public void pointcut(){} @Before("pointcut()") // 增强方法 public void before(JoinPoint joinPoint){ // 连接点 JoinPoint System.out.println("before-------------"); // weave 直入 Object[] args = joinPoint.getArgs(); if (args.length < 1){ System.out.println("no args"); return; } if (args.length == 1){ System.out.println(args[0]); return; } StringBuilder builder = new StringBuilder(); for (Object arg : args) { builder.append(arg).append(","); } builder.delete(builder.length()-1,builder.length()); System.out.println(builder.toString()); } @After("pointcut()") public void after(JoinPoint joinPoint){ System.out.println("after----------------"); Object[] args = joinPoint.getArgs(); // 获取的永远是连接点方法里面的值 if (args.length < 1){ System.out.println("no args"); return; } if (args.length == 1){ System.out.println(args[0]); return; } StringBuilder builder = new StringBuilder(); for (Object arg : args) { builder.append(arg).append(","); } builder.delete(builder.length()-1,builder.length()); System.out.println(builder.toString()); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
一定要有@Component @Aspect
首先类com.example.aop.anno.AnCustomAspect上面必须添加org.aspectj.lang.annotation.Aspect注解,标识当前类作为一个切面。
同时要注意加上org.springframework.stereotype.Component注解保证能被Spring容器扫描并注册、管理,因为Spring中切面编程是针对于容器中的bean的。
因为只有给IOC容器管理,我们的spring的一些注解才会有用!!!
3、编写切入点
关于切入点,连接点,增强方法以及通知下面会有讲解,
4、编写增强方法
5、联系连接点
6、进行增强
注意事项:
- 如果要使⽤Spring aop⾯向切⾯编程,调⽤切⼊点⽅法的对象必须通过Spring容器获取
- 如果⼀个类中的⽅法被声明为切⼊点并且织⼊了切点之后,通过Spring容器获取该类对象,实则获取到的是⼀个代理对象
- 如果⼀个类中的⽅法没有被声明为切⼊点,通过Spring容器获取的就是这个类真实创建的对象
2、专业名字
- 切面 Advior
- 切点 poincut
- 连接点 JoinPoint
- 增强方法 Active
- 织入 waver
- 目标 target
- 代理 proxy
- 前置增强 @before
- 后置增强 @after
- 环绕增强 @around
- 抛出增强 @throws
- 引入增强 @Introduction
3、切入点详解
切入点:就是我们的增强方法具体要执行到谁头上
比如切西瓜,如果西瓜是连接点,那么,有很多西瓜,你具体切开哪个西瓜,你才能吃哪个西瓜,同样的道理,很多连接点,你要增强的那个方法就是切入点,其余没有增强的方法,仍然是连接点
比如UserDaoImpl中有add,delete,update等方法,那么如果你要增强add方法,那么,就是这个add就是你的切入点
具体使用:如下
一个通过
@Pointcut
标识的方法,也就是切点,通过切点表达式匹配需要增强的那些类、对象或方法。切点表达式是在org.aspectj.lang.annotation.Pointcut
注解属性中来定义的。
3.1、aop中切入点路径详解
示例:
/*public com.chengshiyu.springboot9.Service.impl.OrderServiceImpl.add(User user,String a ,String b)*/@Pointcut("execution(* com.chengshiyu.springboot9_day.Service..*.*(..))")public void pointcut(){}
- 1
- 2
- 3
- 4
源码:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
- 1
- 2
- 3
modifiers-pattern:匹配方法修饰符 publicor protectedret-type-pattern:匹配方法返回类型 *代表所有类型declaring-type-pattern:匹配类名称 可以包含.或...前者代表单层目录,后者代表任意层级目录。name-pattern:匹配方法名称 *匹配任意方法名称param-pattern:匹配参数名称 如果是()代表没有方法参数,(..)匹配任意个数或类型参数,(*)匹配一个任意类型的参数,(*,String)匹配两个参数,第一个任意类型,第二个参数必须为String类型。throws-pattern:匹配方法异常类型其中returning type pattern、name pattern, param-pattern是必须的,其他的可以不需要。比如execution(* set*(..))就是一个最简单的,ret-type-pattern为*(匹配任意返回类型的方法),name-pattern为set*(匹配方法名称以set开头的方法),param-pattern为…(匹配任意类型或者数量的方法参数),其他的没有定义,完整意思就是:匹配所有方法名以set开头的方法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
3.2、execution
官方解释:
for matching method execution join points, this is the primary pointcut designator you will use when working with Spring AOP
具体代码实现
service层代码
package com.chengshiyu.springboot9_day.Service;import com.chengshiyu.springboot9_day.Entity.User;/** * @author 程世玉 * @create 2022/3/17 17:25 * @PROJECT_NAME Second-SpringBootTest * @Description */public interface OrderService { public User add(User user); public int register(User user);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
package com.chengshiyu.springboot9_day.Service.impl;import com.chengshiyu.springboot9_day.Entity.User;import com.chengshiyu.springboot9_day.Service.OrderService;import org.springframework.stereotype.Service;/** * @author 程世玉 * @create 2022/3/17 17:27 * @PROJECT_NAME Second-SpringBootTest * @Description */@Servicepublic class OrderServiceImpl implements OrderService { @Override public User add(User user) { System.out.println("add方法体,执行了具体的代码逻辑"); user.setUsername("add修改了!!"); return user; } @Override public int register(User user) { System.out.println("register方法体,执行了具体的代码逻辑"); return 0; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
AOP层次
package com.chengshiyu.springboot9_day.Config;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;/** * @author 程世玉 * @create 2022/3/17 17:30 * @PROJECT_NAME Second-SpringBootTest * @Description */@Component@Aspectpublic class aop1 { /*public com.chengshiyu.springboot9.Service.impl.OrderServiceImpl.add(User user,String a ,String b)*/ /* * com.chengshiyu.springboot9_day..*.*(..)*/ @Pointcut("execution(* com.chengshiyu.springboot9_day.Service..*.*(..))") public void pointcut(){} @Before("pointcut()") public void before(JoinPoint joinPoint){ System.out.println("before-------------"); Object[] args = joinPoint.getArgs(); if (args.length < 1){ System.out.println("no args"); return; } if (args.length == 1){ System.out.println(args[0]); return; } StringBuilder builder = new StringBuilder(); for (Object arg : args) { builder.append(arg).append(","); } builder.delete(builder.length()-1,builder.length()); System.out.println(builder.toString()); } @After("pointcut()") public void after(JoinPoint joinPoint){ System.out.println("after----------------"); Object[] args = joinPoint.getArgs(); // 获取的永远是连接点方法里面的值 if (args.length < 1){ System.out.println("no args"); return; } if (args.length == 1){ System.out.println(args[0]); return; } StringBuilder builder = new StringBuilder(); for (Object arg : args) { builder.append(arg).append(","); } builder.delete(builder.length()-1,builder.length()); System.out.println(builder.toString()); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
提前说明一下:
Object[] args = joinPoint.getArgs();
- 1
这一句,getArgs,只能获取到参数里面的东西,不是获取我们返回值,也就是说,即使你用的After后置增强,也获得到的也仅仅是他参数变化之后的值,并不能得到他的返回值
结论:
- ```@Pointcut(“execution(* com.chengshiyu.springboot9_day.Service….(…))”)`注解扫描的就是service下所有的包
- 无论是
@After
还是@Before
注解,方法参数都是JoinPoint
,获取到的args都是参数里面的值,获取到的都是连接点方法里面的参数的值,不会获取到方法return返回的值