文章目录
- Spring的AOP
- 一、 Spring对AOP的实现包括以下3种方式
- **什么是AspectJ?**
- 二、使用Spring的AOP
- 1、准备工作
- 2、尝试写一个简单的AOP demo
- 3、代码如下:
- spring.xml
- 业务类
- 切面类
- 测试类
- 4、复习切面表达式
- 1)所有方法
- 2)指定路径下某个包及其子包的所有方法
- 3)限定public的,某个包及其子包的,带login开头的,且参数是Stirng 的 方法
- 5、5种不同的通知如何写?
- 思考1,5种通知都存在时,执行的顺序是什么样的?如果抛出异常,执行的顺序是什么样的?
- 思考2:切面的先后顺序(如果一个业务存在多个切面,如何排序?如何执行
- 测试2:同数字或者都无Order注解的情况,是如何执行的
- 测试2:不同优先级的切面先后执行顺序
- 6、写法优化(切点优化pointcut)
- 7、拓展用法,JointPoint
- 8、全注解开发优化
- 修改后的配置类
- 测试类
- 9、基于XML开发的AOP
Spring的AOP
一、 Spring对AOP的实现包括以下3种方式
- 第一种方式: Spring框架结合AspectJ框架实现的AOP,基于注解方式。(需要重点掌握, 核心用法
- 第二种方式: Spring框架结合AspectJ框架实现的AOP,基于XML方式第三种方式:
- Spring框架自己实现的AOP,基于XML配置方式。(少用不做介绍)
什么是AspectJ?
什么是AspectJ?
(Edlipse组织的一个支持AOP的架。Aspect/架是独立于Spring架之外的一个框架,Spring框架用了AspectJ)
Aspedtu项目起源于洛阿你托 (Palo Ato 研究中心(写为PARC) ,该中由Xeox建团资,reor kczales导,以1997年开力于Aspec开发,1998年第-次发布外部用户,2001年发布1.0 release为了Aspe技术和发展,PAR(在200年3月将Aspec/项目给了dipse织,因为Aspec的发展和受关注度大大超出了PARC的预期,他们已经力继维持它的发展。
二、使用Spring的AOP
1、准备工作
要使用Spring的AOP,需要导入以下依赖
- spring context 核心依赖 包含 spring-aop
- spring-aspects 依赖
并在resource目录下新建配置文件
2、尝试写一个简单的AOP demo
思路整理
-
写一个业务类模拟实际业务
-
定义一个切面类 (切面=通知+切点),需要添加
@Aspect
直接,告诉spring这是一个切面类 -
将上述的两个类,纳入Spring容器中 (注解 + 添加扫描配置信息)
-
在切面类中定义具体的通知
- 通知就是增强,就是具体要编写的增强代码
- 这里通知Advice以方法的形式出现,因为方法中可以写代码
-
在该方法上加入
@Before
注解,表示这是一个 前置通知- 这个注解注解写 ——> 切点表达式
-
在spring配置中,开启aspecj的自动代理
<aop:aspectj-autoproxy />
- 开启自动dialing后,Spring容器会自动扫描该类上是否带有@Aspect注解,如果有,则生成代理对象。
- 这里标签可以添加一个属性
proxy-target-class
- 为true时表示强制使用CGLIB
- 反之,则根据接口情况使用JDK动态代理或者CGLIB
-
测试
3、代码如下:
spring.xml
<?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:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--组件扫描-->
<context:component-scan base-package="com.zhc.aop"/>
<!--开启aspectj的自动代理-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
业务类
package com.zhc.aop.service;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserService {
public void login(){
System.out.println("系统正在登陆。。。");
}
}
切面类
package com.zhc.aop.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component("logAspect")
@Aspect // 声明这是一个切面
public class LogAspect {
@Before("execution(* com.zhc.aop.service..*(..))")
public void beforePoint(){
System.out.println("前置通知");
}
}
测试类
import com.zhc.aop.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
@Test
public void TestProxy(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.login();
}
}
测试结果
4、复习切面表达式
切面表达式在这章中比较重要,迅速复习一遍即可,要记得几个常用的表达式
1)所有方法
权限修饰符略,异常略,全限定名略
execution(* *(..))
2)指定路径下某个包及其子包的所有方法
excution(* com.zhc..*(..))
3)限定public的,某个包及其子包的,带login开头的,且参数是Stirng 的 方法
excution(public * com.zhc..login*(String))
5、5种不同的通知如何写?
- 前置 —— @Before
- 后置—— @AfterReturning
- 环绕——@Around
- 异常——@AfterThrowing
- 最终——@After
注意 ,后置通知的注解并不是@After哦,别搞混 了。
写法其实差不太多,需要注意是的环绕通知的写法。
思考1,5种通知都存在时,执行的顺序是什么样的?如果抛出异常,执行的顺序是什么样的?
引用老杜的案例:无异常的执行顺序如下
一旦捕获异常,后置通知和后环绕就被抛弃了!
- 后环绕也有特殊之处——如果是try…catch内捕获的话,后环绕也会执行
思考2:切面的先后顺序(如果一个业务存在多个切面,如何排序?如何执行
在实际业务中,往往存在多个切面,如何做好排序呢?
其实Spring的开发者已经想到了这种情况,解决方法也很简单——
在切面类上加入注解@Order(int num)
,Order注解的数字越小,优先级越高!
- 这里老杜没有提到的细节,比如同数字或者都无Order注解的情况,以及不同优先级的切面先后执行顺序是怎么样的?(比如环绕通知的先后执行顺序是什么样的?
测试2:同数字或者都无Order注解的情况,是如何执行的
结论:不是随机的,而是按照某个顺序进行执行,规律不明但是固定
测试2:不同优先级的切面先后执行顺序
测试设置 log的order为0,auth的order为1,测试结果如下:
结论:不同切面的执行顺序的:
- 优先级高的前置/前环绕通知 会先执行
- 优先级高的后置/后环绕通知 会后执行
6、写法优化(切点优化pointcut)
写的切面多了,发现切面表达式重复写了很多,这里老杜介绍了 如何优化这个问题,具体实现如下
- 在切面类中定义一个方法(任意名称)
- 在该方法上加入
@Pointcut
注解,并在注解内写入 切面表达式 - 在其他的切面方法上使用注解,就可以直接传入该方法名即可
这个简化的写法也可以跨类使用,只需要写上全限定类名(包名+类名)即可
优化后的切面类
package com.zhc.aop.aspects;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component("logAspect")
@Aspect // 声明这是一个切面
public class LogAspect {
// 优化写法
@Pointcut("execution(* com.zhc.aop.service.UserService.*(..))")
public void pointcut(){
}
@Before("pointcut()") // 前置通知
public void beforePoint(JoinPoint joinPoint){
System.out.println("前置通知");
// System.out.println(joinPoint); // 打印结果:execution(void com.zhc.aop.service.UserService.loginA())
}
@AfterReturning("pointcut()")
public void afterPoint(){
System.out.println("后置通知");
}
@Around("pointcut()")
public void aroundPoint(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前。。。");
joinPoint.proceed();
System.out.println("环绕后。。。");
}
@AfterThrowing("pointcut()")
public void throwPoint(){
System.out.println("异常通知");
}
@After("pointcut()")
public void finallyPoint(){
System.out.println("最终通知");
}
}
7、拓展用法,JointPoint
前面的环绕通知有用到了一个 ProcessJoinPoint ,其实在编写其他类型的通知类时,也可以通过JoinPoint来获取目标方法
-
比如直接尝试打印该joinpoint对象——可以看出来打印的结果就是一个切面表达式:
打印结果:execution(void com.zhc.aop.service.UserService.loginA())
8、全注解开发优化
可以优化掉配置文件spring.xml
声明一个配置类,配置思路如下
- 声明一个配置类 加上@Configuration
- 声明组件扫描
- 声明aspectj的自动扫描
- 修改对应的测试/调用代码
修改后的配置类
package com.zhc.aop.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.zhc.aop")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig {
}
测试类
@Test
public void TestProxyByAnno(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.loginA();
}
9、基于XML开发的AOP
略,作为了解。主要是就是xml文件的书写和阅读,截图老杜写好的xml文件
依然推荐大家去看动力节点的视频,更有助于理解