一, 什么是 AOP?
Aspect oritention programming(面向切面编程),AOP 是一种思想, 高度概括的话是 "横向重复, 纵向抽取", 如何理解呢? 举个例子: 访问页面时需要权限认证, 如果每个页面都去实现方法显然是不合适的, 这个时候我们就可以利用切面编程.
每个页面都去实现这个方法就是横向的重复, 我们直接从中切入, 封装一个与主业务无关的权限验证的公共方法, 这样可以减少系统的重复代码, 降低模块之间的耦合度, 简单的示意图如下:
二, 应用场景
AOP 用来封装横切关注点, 具体可以在下面的场景中使用:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试 l
ogging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步 T
ransactions 事务
三,
相关概念
1. 连接点 (Joinpoint) 所谓连接点是指那些可能被拦截到的方法. 例如: 所有可以增加的方法
2. 切点 (Pointcut) 已经被增强的连接点
3. 增强 (Advice) 增强的代码
4. 目标对象 (Target) 目标类, 需要被代理的类
5. 织入 (Weaving) 是指把增强 advice 应用到目标对象 target 来创建新的代理对象 proxy 的过程
6. 代理 (Proxy) 一个类被 AOP 织入增强后, 就产生出了一个结果类, 它是融合了原类和增强逻辑的代理类.
7. 切面 (Aspect) 切入点 + 通知
通知类型: Spring 按照通知 Advice 在目标类方法的连接点位置, 可以分为 5 类
前置通知 (在目标方法执行前实施增强)
后置通知(在目标方法执行后实施增强)
环绕通知(在目标方法执行前后实施增加)
异常抛出通知(在方法跑出异常时通知)
引介通知(在目标类中添加一些新的方法和属性)
四, 实现原理
AOP 的实现关键在于 AOP 框架自动创建的 AOP 代理. AOP 代理主要分为两大类:
静态代理: 使用 AOP 框架提供的命令进行编译, 从而在编译阶段就可以生成 AOP 代理类, 因此也称为编译时增强; 静态代理一 Aspectj 为代表.
动态代理: 在运行时借助于 JDK 动态代理, CGLIB 等在内存中临时生成 AOP 动态代理类, 因此也被称为运行时增强, Spring AOP 用的就是动态代理.
4.1 静态代理
例子: 在增加员工和删除员工时增加事务处理
- // 员工类
- public class Employee {
- private Integer uid;
- public void setUid(Integer uid) {
- this.uid = uid;
- }
- public Integer getUid() {
- return uid;
- }
- private Integer age;
- private String name;
- public Integer getAge() {
- return age;
- }
- public String getName() {
- return name;
- }
- public void setAge(Integer age) {
- this.age = age;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
员工接口:
- // 员工接口
- public interface EmployeeService {
- // 新增方法
- void addEmployee(Employee employee);
- // 删除方法
- void deleteEmployee(Integer uid);
- }
员工实现:
- // 员工方法实现
- public class EmployeeServiceImpl implements EmployeeService {
- @Override
- public void addEmployee(Employee employee) {
- System.out.println("新增员工");
- }
- @Override
- public void deleteEmployee(Integer uid) {
- System.out.println("删除员工");
- }
- }
事务类:
- // 事务类
- public class MyTransaction {
- // 开启事务
- public void before(){
- System.out.println("开启事务");
- }
- // 提交事务
- public void after(){
- System.out.println("提交事务");
- }
- }
代理类:
- // 代理类
- public class ProxyEmployee implements EmployeeService {
- //
- private EmployeeService employeeService;
- private MyTransaction myTransaction;
- public ProxyEmployee(EmployeeService employeeService,MyTransaction myTransaction)
- {
- this.employeeService=employeeService;
- this.myTransaction=myTransaction;
- }
- @Override
- public void addEmployee(Employee employee) {
- myTransaction.before();
- employeeService.addEmployee(employee);
- myTransaction.after();
- }
- @Override
- public void deleteEmployee(Integer uid) {
- myTransaction.before();
- employeeService.deleteEmployee(uid);
- myTransaction.after();
- }
- }
测试:
- @Test
- public void fun1(){
- MyTransaction transaction = new MyTransaction();
- EmployeeService EmployeeService = new EmployeeServiceImpl();
- // 产生静态代理对象
- ProxyEmployee proxy = new ProxyEmployee(EmployeeService, transaction);
- proxy.addEmployee(null);
- proxy.deleteEmployee(0);
- }
结果:
这是静态代理的实现方式, 静态代理有明显的缺点:
1, 代理对象的一个接口只服务于一种类型的对象, 如果要代理的方法很多, 势必要为每一种方法都进行代理, 静态代理在程序规模稍大时就无法胜任了.
2, 如果接口增加一个方法, 比如 EmployeeService 增加修改 updateEmployee()方法, 则除了所有实现类需要实现这个方法外, 所有代理类也需要实现此方法. 增加了代码维护的复杂度.
4.2 动态代理
动态代理就不要自己手动生成代理类了, 我们去掉 ProxyEmployee.java 类, 增加一个 ObjectInterceptor.java 类
- public class ObjectInterceptor implements InvocationHandler {
- // 目标类
- private Object target;
- // 切面类(这里指事务类)
- private MyTransaction transaction;
- // 通过构造器赋值
- public ObjectInterceptor(Object target,MyTransaction transaction){
- this.target = target;
- this.transaction = transaction;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- this.transaction.before();
- method.invoke(target,args);
- this.transaction.after();
- return null;
- }
- }
测试:
- @Test
- public void fun2(){
- // 目标类
- Object target = new EmployeeServiceImpl ();
- // 事务类
- MyTransaction transaction = new MyTransaction();
- ObjectInterceptor proxyObject = new ObjectInterceptor(target, transaction);
- /**
- * 三个参数的含义:
- * 1, 目标类的类加载器
- * 2, 目标类所有实现的接口
- * 3, 拦截器
- */
- EmployeeService employeeService = (EmployeeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
- target.getClass().getInterfaces(), proxyObject);
- employeeService.addEmployee(null);
- employeeService.deleteEmployee(0);
- }
结果:
五, spring 的处理 AOP 的方式
spring 有两种方式实现 AOP 的: 一种是采用声明的方式来实现(基于 xml), 一种是采用注解的方式来实现(基于 AspectJ)
5.1
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">
- <!-- 目标类 -->
- <bean name="employeeService" class="com.yuanqinnan.aop.EmployeeServiceImpl"></bean>
- <bean name="transaction" class="com.yuanqinnan.aop.MyTransaction"></bean>
- <aop:config>
- <aop:aspect ref="transaction">
- <aop:pointcut id="pointcut" expression="execution(* com.yuanqinnan.aop.EmployeeServiceImpl..*(..))"/>
- <!-- 配置前置通知, 注意 method 的值要和 对应切面的类方法名称相同 -->
- <aop:before method="before" pointcut-ref="pointcut"></aop:before>
- <!-- 配置后置通知, 注意 method 的值要和 对应切面的类方法名称相同 -->
- <aop:after-returning method="after" pointcut-ref="pointcut"/>
- </aop:aspect>
- </aop:config>
- </beans>
测试:
- @Test
- public void fun3(){
- ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
- EmployeeService employeeService = (EmployeeService) context.getBean("employeeService");
- employeeService.addEmployee(null);
- }
结果:
补充:
1.aop:pointcut 如果位于 aop:aspect 元素中, 则命名切点只能被当前 aop:aspect 内定义的元素访问到, 为了能被整个 aop:config 元素中定义的所有增强访问, 则必须在 aop:config 下定义切点.
2. 如果在 aop:config 元素下直接定义 aop:pointcut, 必须保证 aop:pointcut 在 aop:aspect 之前定义. aop:config 下还可以定义 aop:advisor, 三者在 aop:config 中的配置有先后顺序的要求: 首先必须是 aop:pointcut, 然后是 aop:advisor, 最后是 aop:aspect. 而在 aop:aspect 中定义的 aop:pointcut 则没有先后顺序的要求, 可以在任何位置定义.
aop:pointcut: 用来定义切入点, 该切入点可以重用;
aop:advisor: 用来定义只有一个通知和一个切入点的切面;
aop:aspect: 用来定义切面, 该切面可以包含多个切入点和通知, 而且标签内部的通知和切入点定义是无序的; 和 advisor 的区别就在此, advisor 只包含一个通知和一个切入点.
3. 在使用 spring 框架配置 AOP 的时候, 不管是通过 xml 配置文件还是注解的方式都需要定义 pointcut"切入点" 例如定义切入点表达式 execution(* com.sample.service.impl...(..))execution()是最常用的切点函数, 其语法如下所示:
整个表达式可以分为五个部分:(1),execution(): 表达式主体.
(2), 第一个号: 表示返回类型, 号表示所有的类型.
(3), 包名: 表示需要拦截的包名, 后面的两个句点表示当前包和当前包的所有子包, com.sample.service.impl 包, 子孙包下所有类的方法.
(4), 第二个号: 表示类名, 号表示所有的类.
(5),(..): 最后这个星号表示方法名, 号表示所有的方法, 后面括弧里面表示方法的参数, 两个句点表示任何参数.
5.2 注解配置
新建注解类:
- @Component
- @Aspect
- public class AopAspectJ {
- /**
- * 必须为 final String 类型的, 注解里要使用的变量只能是静态常量类型的
- */
- public static final String EDP="execution(* com.yuanqinnan.aop.EmployeeServiceImpl..*(..))";
- /**
- * 切面的前置方法 即方法执行前拦截到的方法
- * 在目标方法执行之前的通知
- * @param jp
- */
- @Before(EDP)
- public void doBefore(JoinPoint jp){
- System.out.println("========= 执行前置通知 ==========");
- }
- /**
- * 在方法正常执行通过之后执行的通知叫做返回通知
- * 可以返回到方法的返回值 在注解后加入 returning
- * @param jp
- * @param result
- */
- @AfterReturning(value=EDP,returning="result")
- public void doAfterReturning(JoinPoint jp,String result){
- System.out.println("=========== 执行后置通知 ============");
- }
- /**
- * 最终通知: 目标方法调用之后执行的通知(无论目标方法是否出现异常均执行)
- * @param jp
- */
- @After(value=EDP)
- public void doAfter(JoinPoint jp){
- System.out.println("=========== 执行最终通知 ============");
- }
- /**
- * 环绕通知: 目标方法调用前后执行的通知, 可以在方法调用前后完成自定义的行为.
- * @param pjp
- * @return
- * @throws Throwable
- */
- @Around(EDP)
- public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
- System.out.println("====== 执行环绕通知开始 =========");
- // 调用方法的参数
- Object[] args = pjp.getArgs();
- // 调用的方法名
- String method = pjp.getSignature().getName();
- // 获取目标对象
- Object target = pjp.getTarget();
- // 执行完方法的返回值
- // 调用 proceed()方法, 就会触发切入点方法执行
- Object result=pjp.proceed();
- System.out.println("输出, 方法名:" + method + "; 目标对象:" + target + "; 返回值:" + result);
- System.out.println("====== 执行环绕通知结束 =========");
- return result;
- }
- /**
- * 在目标方法非正常执行完成, 抛出异常的时候会走此方法
- * @param jp
- * @param ex
- */
- @AfterThrowing(value=EDP,throwing="ex")
- public void doAfterThrowing(JoinPoint jp,Exception ex) {
- System.out.println("=========== 执行异常通知 ============");
- }
- }
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.yuanqinnan.aop"></context:component-scan>
- <!-- 声明 spring 对 @AspectJ 的支持 -->
- <aop:aspectj-autoproxy/>
- <!-- 目标类 -->
- <bean name="employeeService" class="com.yuanqinnan.aop.EmployeeServiceImpl"></bean>
- </beans>
测试:
- @Test
- public void fun4(){
- ApplicationContext act = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
- EmployeeService employeeService = (EmployeeService) act.getBean("employeeService");
- employeeService.addEmployee(null);
- }
结果:
pringAOP 的知识就总结到这里
来源: https://www.cnblogs.com/yuanqinnan/p/10507411.html