前言
简单回顾下AOP:
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程
切面(Aspect):,给目标类增加的功能即为切面,切面一般都是非业务方法,独立运行,常用的切面的增强/通知(Advice)。
连接点(JoinPoint) :连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
切入点(Pointcut): 切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不 能被增强的。
目标对象(Target): 目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。当某对象被增强,则该类称为目标类,该类对象称为目标对象。
通知(Advice): 通知表示切面的执行时间,Advice 也叫增强。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方 法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。 切入点定义切入的位置,通知定义切入的时间。
切入点表达式格式:execution(访问权限 方法返回值 方法声明(参数) 异常类型)
符号 |
意义 |
* |
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
| excution(public * *(...参数...)) excution(public * set*(...参数...)) excution(* com.java.* *(...参数...)) execution(* com.xyz.service..*.*(..))
execution(* *..service.*.*(..))
execution(* *.service.*.*(..))
execution(* *.ISomeService.*(..))
execution(* *..ISomeService.*(..))
execution(* com.xyz.service.IAccountService.*(..))
execution(* com.xyz.service.IAccountService+.*(..))
execution(* joke(String,int)))
execution(* joke(String,*)))
execution(* joke(String,..)))
execution(* joke(Object))
execution(* joke(Object+)))
|
实战使用
测试直接在开源框架,jeesite上进行
导入依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
|
切面类:
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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| package com.jeesite.modules.test.service;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component public class AopTest {
private static final Logger log = LoggerFactory.getLogger(AopTest.class);
@Pointcut("execution(public * com.jeesite.modules.sys.service.support.LogServiceSupport.insertLog(..))") private void newController1() { }
@After("newController1()") private void afterController(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); log.info("insertLog:{}",Arrays.toString(args)); }
@Pointcut("execution(public * com.jeesite.modules.sys.web.user.UserController.info(..))") private void newController2() { }
@Around(value = "newController2()", argNames = "pjp") private String aroundController(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs(); log.info("info:{}",Arrays.toString(args)); return "modules/sys/user/userSelect"; }
@Pointcut("execution(public String com.jeesite.modules.sys.web.LoginController.switchSkin(..))") public void newController12() { } @Before("newController12() || newController1()") public void beforeController1(JoinPoint joinPoint) { log.info("测试切入"); }
}
|
目标被切方法1(后置切入):
1 2 3 4
| @Transactional(readOnly=false) public void insertLog(Log entity) { dao.insert(entity); }
|
测试图:
目标被切方法2(环绕切入):
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RequiresPermissions("user") @RequestMapping(value = "info") public String info(User user, String op, Model model) { if (StringUtils.isBlank(op)){ op = "base"; } model.addAttribute("op", op); model.addAttribute("user", UserUtils.getUser()); logger.info("userInfo:{}","here"); return "modules/sys/user/userInfo"; }
|
测试图:
批量切入测试(前置切入):
表达式优化
优化前
1 2 3 4 5 6 7 8
| @Before("execution(* aa.*.*(..))") public void begin() { System.out.println("开始事务"); } @After("execution(* aa.*.*(..))") public void close() { System.out.println("关闭事务"); }
|
优化后:
优化后就只需要写一次切面表达式就行了
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Pointcut("execution(* aa.*.*(..))") public void pt() { }
@Before("pt()") public void begin() { System.out.println("开始事务"); } @After("pt()") public void close() { System.out.println("关闭事务"); }
|
后记
实测无法切入 子类重写父类的方法,及 通过this调用的方法