Aop:
即:约定编程。其核心切面(Interceptor)。包含了5种通知(advice)。
它们分别是:前置(befor),环绕(around),后置(after),事后返回(AfterReturning),异常(AfterThrowing)。
环绕通知,被称作最强大的通知,也意味着难以控制(取代原有方法,也提供原有方法的能力)。
人们对它的理解就是,它很强大,也很危险,不要用。
为啥呢?写一个简单的例子,你就会知道 ,它是有bug的(猜测这个bug大约从4.3直到现在一直存在)。
1,引入:starter-web 和 starter-aop 包
2,定义一个最简单的pojo,里面两个最简单的字段,username note。
3,写一个最简单的接口:
public interface UserService {
public void printUser(User user);
}
4,然后实现它,如何实现看心情。
5,写一个最简单的切面
@Aspect
public class MySimpleAspect {
//定义切点。指向包路径。
@Pointcut("execution(* com.example.demobenaware.service.impl.UserServiceImpl.printUser(..))")
public void pointCut() {
}
//前置通知 1
@Before("pointCut()")
public void before() {
System.out.println("before ......"+new Date(System.currentTimeMillis()));
}
//环绕通知 2
@Around("pointCut()")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("around before......");
jp.proceed();
System.out.println("around after......");
}
//后置通知 3
@After("pointCut()")
public void after() {
System.out.println("after ......"+new Date(System.currentTimeMillis()));
}
//事后返回通知 4
@AfterReturning("pointCut()")
public void afterReturning() {
System.out.println("afterReturning ......");
}
//异常通知
@AfterThrowing("pointCut()")
public void afterThrowing() {
System.out.println("afterThrowing ......");
}
}
6,把这个切面交给ioc容器。(放在boot自带的@SpringBootApplication类里面,省事了@Configuration,@ComponentScan都是配置好的 )
// 切面类注册给spring
@Bean(name = "mySimpleAspect")
public MySimpleAspect initMySimpleAspect() {
return new MySimpleAspect();
}
7,写个最简单的控制器。怎样写都行。记得调用要拦截的方法 userService.printUser(user);
切面的执行顺序(非异常)应该为:
前置,环绕,后置,事后返回。
而真实的执行顺序是:
环绕(proceed()方法前),前置,环绕(proceed()方法后),后置,事后返回。
对于先执行环绕通知,我是懵了(备注:如果不使用环绕通知,任何版本都没问题)。
Spring是java史上最伟大的框架,Aop更是核心所在。
即:约定编程。其核心切面(Interceptor)。包含了5种通知(advice)。
它们分别是:前置(befor),环绕(around),后置(after),事后返回(AfterReturning),异常(AfterThrowing)。
环绕,最强大的通知,难以控制(取代原有方法,也提供原有方法的能力)。
它很强大,也很危险。
它是有bug的(直到现在)。
1,引入:web 和 aop 包
2,pojo。
3,接口:
public void printUser();
4,实现它。
5,切面
@Aspect
public class Aspect {
//定义切点。指向包路径。
@Pointcut("execution(* 包路径到方法(..))")
public void pointCut() {
}
//前置通知 1 @Before("pointCut()")
//环绕通知 2
@Around("pointCut()")
public void around(ProceedingJoinPoint jp){
("a");
jp.proceed();
("b");
}
//后置通知 3 @After("pointCut()")
//事后返回通知 @AfterReturning("pointCut()") 4
//异常通知 @AfterThrowing("pointCut()")
}
6,切面给ioc容器。(放在boot自带的主类里面,省事了)
@Bean(name = "aspect")
7,控制器。记得调用要拦截的 你的方法 ;
切面的执行顺序(非异常)应该为:
前置,环绕,后置,事后返回。
而真实顺序是:
环绕(proceed()前),前置,环绕(proceed()后),后置,事后返回。
懵了(备注:如果不使用环绕通知,没问题)。
约定编程 代理
- 切点 通知 连接点 引入 织入
- 你需要记住约定的流程是什么,然后完成对应的任务,却不需要知道 底层设计者 是怎么将 约定的内容 织入对应的流程中的。
public interface HelloService {
public void sayHello(String name);
}
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String name) {
if (StringUtils.isEmpty(name)){
throw new RuntimeException("参数为空!");
}
System.out.println(name.trim()+"你好!!!");
}
}
-
定义拦截器接口
public interface Interceptor { //事前方法 public boolean before(); //事后方法 public void after(); //是否返回方法。事件没有发生异常执行 public void afterReturning();//无异常执行 //事后异常方法,当事件发生异常后执行 public void afterThrowing();//有异常就执行 //是否使用around(下面的)方法取代原有方法 boolean useAround(); /** * 取代原有事件方法 * @param invocation -- 回调参数,可以通过它的proceed方法,回调原有事件 * @return 原有事件返回对象 * @throws InvocationTargetException * @throws IllegalAccessException */ public Object around(Invocation invocation) throws InvocationTargetException, IllegalAccessException; }
后面会给出约定,将这些发方法织入 流程中
- Invocation定义
@Data
public class Invocation {
private Object[] params;//参数
private Method method;//方法
private Object target;//目标
//全参 构造赋值
public Invocation(Object target, Method method, Object[] params) {
this.target = target;
this.method = method;
this.params = params;
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, params);//方法执行 参数是: 要执行的目标,要传递的参数
}//会以反射的形式 去调用 原有的方法
}
-
开发自己的拦截器
public class MyInterceptor implements Interceptor { @Override public boolean before() { System.out.println("before ......"); return true; } @Override public void after() { System.out.println("after ......"); } @Override public boolean useAround() { return true; } @Override public Object around(Invocation invocation) throws InvocationTargetException, IllegalAccessException { System.out.println("around before ......");//增强代码 Object obj = invocation.proceed();//执行原来的方法 System.out.println("around after ......");//增强代码 return obj; } @Override public void afterReturning() {//事后,没发生于异常执行 System.out.println("afterReturning......"); } @Override public void afterThrowing() {//事后,发生了异常执行 System.out.println("afterThrowing 。。。。。。"); } }
- 开始约定了:
-
约定是Spring Aop的本质
-
提供一个ProxyBean类,有一个静态方法 给他人使用
-
public static Object getProxyBean(Object target, Interceptor interceptor) {}
-
target 存在接口。interceptor 是上面定义的接口
-
返回一个对象记作:proxy,使用target对象进行强制转换
HelloService helloService = new HelloServiceImpl();//定义一个类 HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor()); //用这个类得到一个代理, proxy.sayHello("zhangsan");//使用代理调用方法 System.out.println("\n###############name is null!!#############\n"); proxy.sayHello(null);
-
生成代理对象的时候会走一遍流程:
before ......拦截器的before方法 around before ......拦截器的around的方法,invocation.proceed()前,下面执行原方法 around after ......拦截器的around的方法,invocation.proceed()后 after ......拦截器的after方法 afterReturning......原方法执行完毕的执行
-
用这个代理调用方法的时候也会走一遍流程
before ......拦截器的before方法 around before ......拦截器的around的方法,invocation.proceed()前,下面执行原方法 zhangsan你好!!!原方法执行了 around after ......拦截器的around的方法,invocation.proceed()后 after ......拦截器的after方法 afterReturning......原方法执行完毕的执行 cao,下面怎么又执行了一遍。好像每一个方法,都会执行下面的。哪怕sout.("hello word") before ......拦截器的before方法 around before ......拦截器的around的方法,invocation.proceed()前,下面执行原方法 around after ......拦截器的around的方法,invocation.proceed()后 after ......拦截器的after方法 afterReturning......原方法执行完毕的执行
-
异常的执行了流程
before ......拦截器的before方法 around before ......拦截器的around的方法,invocation.proceed()前,下面执行原方法 after ......拦截器的after方法 afterThrowing 。。。。。。原方法异常了的执行 //又有一个方法执行了 before ......拦截器的before方法 around before ......拦截器的around的方法,invocation.proceed()前,下面执行原方法 around after ......拦截器的around的方法,invocation.proceed()后 after ......拦截器的after方法 afterReturning......原方法执行完毕的执行
我们已经把服务和 拦截器的方法织入约定的流程中了。
-
-
ProxyBean 代理生成类
如何将 服务类 和 拦截方法 织入对应的流程。是ProxyBean的功能。
动态代理:你需要采访一个儿童时候,需要他的父母同意。在一些问题上父母替他回答。
对于另一些问题,父母觉得不太适合这个小孩。会拒绝掉。
这时父母就是这名儿童的代理了。
通过代理可以增强或者控制对儿童这个真是对象(target)的访问。
ProxyBean源码在下面
- 约定(拦截器接口的方法)
- 会先执行before方法
- 如果useAround 返回true(返回false执行target对象的方法)。则执行around方法(不执行target对象的方法)
- around 参数invocation对象存在 存在一个 proceed方法,可调用target方法
- 无论怎么样都会执行after方法。 发生异常执行afterThrowing,不发生异常执行 afterReturning方法
-
jdk提供了类proxy的静态方法newProxyInstance 生成代理对象
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { }
- ClassLoader 类加载器
- interfaces绑定的接口,把代理对象绑定到那些接口下
- InvocationHandler绑定代理对象逻辑实现
-
InvocationHandler 解析
public Object invoke(Object proxy, Method method, Object[] args) {} 目标对象,方法,参数
-
ProxyBean源码
public class ProxyBean implements InvocationHandler {//implements InvocationHandler
private Object target = null;
private Interceptor interceptor = null;
/**
* 绑定代理对象
* @param target 被代理对象
* @param interceptor 拦截器
* @return 代理对象
*/
public static Object getProxyBean(Object target, Interceptor interceptor) {
ProxyBean proxyBean = new ProxyBean(); // new ProxyBean(); 保存目标对象 和拦截器
// 保存被代理对象
proxyBean.target = target;
// 保存拦截器
proxyBean.interceptor = interceptor;
// 生成代理对象 getProxyBean 生成代理对象。
Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
proxyBean);//类加载器,接口, 一个 implements InvocationHandler绑定代理对象
// 返回代理对象
return proxy;
}
/**
*生成了一个代理对象,这个代理对象挂在target实现 的 接口之下
*所以你 可以用 target对象 实现的接口 对这个代理对象 实现 强制装换
*并且这个将这个代理对象的逻辑挂在 ProxyBean实例之下
* 完成了目标对象(target) 和 代理对象(Proxy) 的绑定
* 最后将代理对象返回给调用者
* HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());
* 当我们使用 代理 调用方法时,就会进入ProxyBean的invoke方法里
* 这就是我们通过一定的规则完成约定编成的原因。
*/
/**
* 处理代理对象方法逻辑
* @param proxy 代理对象
* @param method 当前方法
* @param args 运行参数
* @return 方法调用结果
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
//异常标识
boolean exceptionFlag = false;
//创建Invocation对象
Invocation invocation = new Invocation(target, method, args);
Object retObj = null;
try {
if (this.interceptor.before()) {//先执行before,如果before为true
retObj = this.interceptor.around(invocation);//执行around方法
} else {
retObj = method.invoke(target, args);//如果before为false,还执行原来的方法
}
} catch (Exception ex) {
//产生异常
exceptionFlag = true;//执行异常了
}
this.interceptor.after();//执行after方法
if (exceptionFlag) {//如果异常了,就执行afterThrowing
this.interceptor.afterThrowing();
} else {//否则执行afterReturning方法
this.interceptor.afterReturning();
return retObj;
}
return null;
}
只要提供一定的约定规则,按照约定编程后
就可以把自己开发的代码织入约定的流程中
jdbc经典代码
Connection conn = null;
int result = 0;
try {
//先注册驱动
Class.forName("com.mysql.jdbc.Driver");
//在通过驱动管理器,获取数据事务连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
//非自动提交事务
conn.setAutoCommit(false);
//执行层
PreparedStatement ps = null;
try {
//预设sql
ps = conn.prepareStatement("insert into user(user_name,note) values (?,?)");
//参数指定
ps.setString(1, "张三");
ps.setString(2, "study");
//执行
result = ps.executeUpdate();
} finally {
//怎样都关闭连接
ps.close();
}
//提交事务
conn.commit();
} catch (Exception e) {
try {
//回滚事务
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
//释放连接池
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
if (result == 0) {
} else {
}
存在一个默认的过程
- 打开数据库连接,然后对其进行设置
- 这行SQL语句
- 如果没有异常,就提交
- 如果发生异常,则回滚
- 关闭数据库事务连接
这个默认流程 可以通过 Aop来实现,你只需要编写SQL这一步,然后织入流程中
AOP概念
- Aop 也是一种约定流程的编程
- @AspectJ注解
-
为什么要用aop
数据库事务的管控,当我们要保存一个用户时,连同它的角色信息一并保存到数据库中。
要么一起成功,要么一起失败。(oop无能为力)
@Transactional //实现了 数据库的打开和关闭 ,事务的回滚 和 提交
public int inserUser(User user){
return userDao.insertUser(user);
}
-
spring Aop 可以处理 OOP实现的业务逻辑
-
Spring Aop是一种基于方法的aop
SpringAop 流程约定:
-
连接点 join point 。 HelloServiceImpl sayHello方法。是具体被拦截的对象。
-
public void sayHello(String name) { if (StringUtils.isEmpty(name)){ throw new RuntimeException("参数为空! 异常方法执行了"); } System.out.println(name.trim()+"你好!!!原方法执行了"); }
-
切面 aspect 。 MyInterceptor 。包含前置通知 before ,环绕通知around,后置通知after,事后返回通知 afterReturning ,和 异常通知 afterThrowiing
@Override public void after() { System.out.println("after ......拦截器的after方法"); } @Override public boolean useAround() { return true; } @Override public Object around(Invocation invocation) throws InvocationTargetException, IllegalAccessException { System.out.println("around before ......拦截器的around的方法,invocation.proceed()前,下面执行原方法"); Object obj = invocation.proceed(); System.out.println("around after ......拦截器的around的方法,invocation.proceed()后"); return obj; }
-
切点 point cut 。有时候不单是单个方法,也可能是多个类的不同方法。通过正则 和 指示器的规则 去定义
-
通知 advice,就是切面中的那些通知
-
目标对象 target 。即:被代理对象,HelloServiceImpl 实例
-
引入 introduction。引入新的类和其他方法,增强现有的bean功能
-
织入 weaving。通过动态代理技术,为原服务生成代理对象。
aop 开发详细
确定连接点
public interface UserService {
public void printUser(User user);
}
@Service
public class UserServiceImpl implements UserService {
@Override
public void printUser(User user) {
if (user == null) {
throw new RuntimeException("检查用户参数是否为空......");
}
System.out.print("id =" + user.getId());
System.out.print("\tusername =" + user.getUsername());
System.out.println("\tnote =" + user.getNote());
}
}
开发切面
1.最简单的切面
@Aspect
public class MySimpleAspect {
//定义切点
@Pointcut("execution(* com.springboot.chapter4.aspect.service.impl.UserServiceImpl.printUser(..))")
public void pointCut() {
}
//前置通知 1
@Before("pointCut()")
public void before() {
System.out.println("before ......"+new Date(System.currentTimeMillis()));
}
//环绕通知 2
@Around("pointCut()")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("around before......");
jp.proceed();
System.out.println("around after......");
}
//后置通知 3
@After("pointCut()")
public void after() {
System.out.println("after ......"+new Date(System.currentTimeMillis()));
}
//事后返回通知 4
@AfterReturning("pointCut()")
public void afterReturning() {
System.out.println("afterReturning ......");
}
//异常通知
@AfterThrowing("pointCut()")
public void afterThrowing() {
System.out.println("afterThrowing ......");
}
}
- 毕竟不是所有的功能都是需要启用Aop的,spring通过这个正则去匹配,去确定对应的方法,是否启用切面编程。
“execution( * com.springboot.chapter4.aspect.service.impl.UserServiceImpl.printUser(…) )”
execution 执行的时候,拦截里面的正则匹配的方法
*任意返回类型的方法
com.****.pringUser 指定目标对象的方法
(…) 任意参数进行匹配
AspectJ关于 Spring Aop切点的指示器
项目类型 | 描述 |
---|---|
arg() | 限定连接点方法参数 |
@args | 连接点方法参数上的注解进行限定 |
execution() | 连接点的执行方法 |
this() | 限制连接点匹配Aop代理Bean引用为指定的类型 |
target | 目标(被代理)对象 |
@target() | 限制目标对下你给的配置了指定的注解 |
within | 限制连接点匹配指定的注解 |
@within() | 限制连接点带有匹配注解类型 |
@annotation() | 限制带有指定注解的连接点 |
@Pointcut("execution(* com.springboot.chapter4.*.*.*.*.print(..)) && bean('userServiceImpl')") bean没用,并且报错。前面的可用
public void pointCut() {
}
execution(* com.springboot.chapter4.*.*.*.*.printUser(..)) && bean('userServiceImpl')
com.springboot.chapter4.aspect.service.impl.UserServiceImpl.printUser
&& 并且
bean(‘userServiceImpl’) 代表对Spring Bean名称的限定
测试Aop
pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId> //必要依赖
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
</dependencies>
控制器
//定义控制器
@Controller
// 定义类请求路径
@RequestMapping("/user")
public class UserController {
// 注入用户服务
@Autowired
private UserService userService = null;
// 定义请求
@RequestMapping("/print")
// 返回json
@ResponseBody
public User printUser(Long id, String userName, String note) {
User user = new User();
user.setId(id);
user.setUsername(userName);
user.setNote(note);
userService.printUser(user);
return user;
}
}
配置
@SpringBootApplication(scanBasePackages = { "com.springboot.chapter4.aspect" })
public class Chapter4Application {
// 启动切面
public static void main(String[] args) {
SpringApplication.run(Chapter4Application.class, args);
}
// 定义切面
@Bean(name = "myAspect")
public MyAspect initMyAspect() {
return new MyAspect();
}
}
userService 可以看出是个 jdk 动态代理对象
http://localhost:8080/user/print/?id=111¬e=哈哈哈
before ......Sun May 03 18:27:10 CST 2020 之前
id =111 username =null note =哈哈哈 方法体
after ......Sun May 03 18:27:25 CST 2020 之后
afterReturning ...... 事后返回
环绕通知
-
最强大的通知,也意味着难以控制
-
场景:你需要 大幅度修改原有 目标对象的服务逻辑时。否则:尽可能用其他通知。
-
是一个取代 原有目标对象 方法的通知。也提供了回调原方法的能力。
切面类: //环绕通知 2 @Around("pointCut()") public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("around before......"); jp.proceed();//回到原有的放 System.out.println("around after......"); }
around before...... 这个顺序是错的,它很强大,却很危险
before ......Sun May 03 21:43:56 CST 2020 。这个在应该放在 第一位
id =111 username =null note =哈哈哈
around after......
after ......Sun May 03 21:44:06 CST 2020
afterReturning ......
spring 4.3.9 xml测试,没问题
引入
-
引入用户检测的接口
public interface UserValidator { public boolean validate(User user); } //检测用户信息是否为空,如果为空 则不再打印 public class UserValidatorImpl implements UserValidator { @Override public boolean validate(User user) { System.out.println("引入新的接口:"+ UserValidator.class.getSimpleName()); return user != null && user.getUsername() != null;//大概需要这样校验 } }
-
使用
@Aspect public class MyAspect { @DeclareParents(value= "com.springboot.chapter4.aspect.service.impl.UserServiceImpl+", defaultImpl=UserValidatorImpl.class) public UserValidator userValidator; } @DeclareParents(value= "com.UserServiceImpl+", defaultImpl=UserValidatorImpl.class)
- @DeclareParents 引入新的类来增强服务
- value XX + 要增强的对象
- defaultImpl 引入增强的类
-
action 测试引入的验证器
// 注入用户服务 @Autowired private UserService userService = null; // 定义请求 @RequestMapping("/vp") // 返回json @ResponseBody public User validateAndPrint(Long id, String userName, String note) { User user = new User(); user.setId(id); user.setUsername(userName); user.setNote(note); // 强制转换 UserValidator userValidator = (UserValidator) userService; // 验证用户是否为空 if (userValidator.validate(user)) { userService.printUser(user); } return user; }
-
原理
Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
proxyBean);
//类加载器,接口, 一个 implements InvocationHandler绑定代理对象
源码:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
- 第二个参数是一个对象数据。生成代理对象 会把 UserService 和 UserValidator 两个参数传递进去
- 让代理对象挂到这两个接口下,
获取通知的参数
-
传递参数给通知
-
只需要在切点处 加入对应 的正则式 就可以了。
-
非环绕通知 还可以 使用 一个连接点 JoinPoint 类型的参数
-
环绕通知 还是 那个可执行 原方法的对象 ProceedingJoinPoint jp;jp.proceed();
@Before("pointCut() && args(user)") public void beforeParam(JoinPoint point, User user) { Object[] args = point.getArgs(); System.out.println("before ......"); } //前置通知 1 @Before("pointCut() && args(user)") public void before(JoinPoint point, User user) { Object[] args = point.getArgs(); System.out.println("所有的参数:"+args.toString()); System.out.println("before ......"+new Date(System.currentTimeMillis())); }
织入
-
是一个生成动态代理对象 并且将切面 和 目标对象方法 编织 成为约定流程 的过程
-
动态代理,jdk,cglib,javassist,asm,
-
jdk 要求 ,被代理的目标对象 必须拥有接口。cglib都可以
-
当你需要使用Aop的类拥有接口时,它会以Jdk动态代理运行。否则cglib。
-
注意最新版的:5.2.5 我看到的,都是cglib
-
最重要的还是这一句话
Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
proxyBean);
//类加载器,接口, 一个 implements InvocationHandler绑定代理对象
多个切面
@Aspect
public class MyAspect1 implements Ordered {
@Override
public int getOrder() {
return 1; //执行顺序为1
}
@Pointcut("execution(* com.example.demobenaware.service.impl.UserServiceImpl.manyAspects(..))")
public void manyAspects() {
}
@Before("manyAspects()")
public void before() {
System.out.println("MyAspect1 before ......");
}
@After("manyAspects()")
public void after() {
System.out.println("MyAspect1 after ......");
}
@After("manyAspects()")
public void afterReturning() {
System.out.println("MyAspect1 afterReturning ......");
}
}
@Aspect
@Order(2) //执行顺序为2,任意选择一种继承 或者注解都行
public class MyAspect2 implements Ordered {
@Override
public int getOrder() {
return 2;
}
}
// 定义切面
@Bean(name = "myAspect2")
public MyAspect2 initMyAspect2() {
return new MyAspect2();
}
// 定义切面
@Bean(name = "myAspect1")
public MyAspect1 initMyAspect1() {
return new MyAspect1();
}
- http://localhost:8080/user/manyAspects
从外 到里执行,在从里到外 执行
MyAspect1 before ......
MyAspect2 before ......
测试多个切面顺序
MyAspect2 after ......
MyAspect2 afterReturning ......
MyAspect1 after ......
MyAspect1 afterReturning ......