Aop 是一个编程思想,最初是一个理论,最后落地成了很多的技术实现。

我们写一个系统,都希望尽量少写点儿重复的东西。而很多时候呢,又不得不写一些重复的东西。比如访问某些方法的权限执行某些方法性能的日志数据库操作的方法进行事务控制。以上提到的,权限的控制,事务控制,性能监控的日志 可以叫一个切面。像一个横切面穿过这一些列需要控制的方法。通过aop编程,实现了对切面业务的统一处理。

以上是我对aop的一个总体概括


aop的原始实现

通过动态代理和反射实现,又称之为JDK动态代理

  • MyInterceptor.java
package demo.aop.jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; /**
* 拦截器
* 1、目标类导入进来
* 2、事务导入进来
* 3、invoke完成
* 1、开启事务
* 2、调用目标对象的方法
* 3、事务的提交
* @author zd
*
*/
public class MyInterceptor implements InvocationHandler{
private Object target;//目标类
private Transaction transaction; public MyInterceptor(Object target, Transaction transaction) {
super();
this.target = target;
this.transaction = transaction;
} public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String methodName = method.getName();
if("savePerson".equals(methodName)||"updatePerson".equals(methodName)
||"deletePerson".equals(methodName)){
this.transaction.beginTransaction();//开启事务
method.invoke(target);//调用目标方法
this.transaction.commit();//事务的提交
}else{
method.invoke(target);
}
return null;
}
}
  • PersonDao.java
package demo.aop.jdkproxy;

public interface PersonDao {
public void savePerson();
public void updatePerson();
}
  • PersonDaoImpl.java
package demo.aop.jdkproxy;

public class PersonDaoImpl implements PersonDao{
public void savePerson() {
System.out.println("save person");
} public void updatePerson() {
// TODO Auto-generated method stub
System.out.println("update person");
}
}
  • Transaction.java
package demo.aop.jdkproxy;

public class Transaction {
public void beginTransaction(){
System.out.println("begin transaction");
}
public void commit(){
System.out.println("commit");
}
}
  • JDKProxyTest
package demo.aop.jdkproxy;

import org.junit.Test;

import java.lang.reflect.Proxy;

/**
* 1、拦截器的invoke方法是在时候执行的?
* 当在客户端,代理对象调用方法的时候,进入到了拦截器的invoke方法
* 2、代理对象的方法体的内容是什么?
* 拦截器的invoke方法的内容就是代理对象的方法的内容
* 3、拦截器中的invoke方法中的参数method是谁在什么时候传递过来的?
* 代理对象调用方法的时候,进入了拦截器中的invoke方法,所以invoke方法中的参数method就是
* 代理对象调用的方法
* @author zd
*
*/
public class JDKProxyTest {
@Test
public void testJDKProxy(){
/**
* 1、创建一个目标对象
* 2、创建一个事务
* 3、创建一个拦截器
* 4、动态产生一个代理对象
*/
Object target = new PersonDaoImpl();
Transaction transaction = new Transaction();
MyInterceptor interceptor = new MyInterceptor(target, transaction);
/**
* 1、目标类的类加载器
* 2、目标类实现的所有的接口
* 3、拦截器
*/
PersonDao personDao = (PersonDao) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), interceptor);
//personDao.savePerson();
personDao.updatePerson();
}
}

运行结果

begin transaction
update person
commit

原始实现部分,想必现在很少会有人再这么写了。但这个对于我们理解Aop的思想很有帮助。

  • 我们可以看到 代理对象 personDao调用的方法updatePerson中没有模拟事务的代码,但最终代理对象却输出了begin transactioncommit

Spring AOP概念核心词

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式来实现。

  • 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。

  • 通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。

  • 切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。

  • 引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。

  • 目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。

  • AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

  • 织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。


上面为官方文档,有的地方还是很难读懂,毕竟是纯概念。下面我用自己的话来翻译一下,如果有不对的地方,请指正

  • 切面 统一处理的业务,比如上文提到的 权限控制,事务处理
  • 连接点 原本被执行的方法,一个执行的方法可能被多个切面横切
  • 通知 切面方法的执行,比如权限控制的具体执行过程(权限控制可以用前置通知@Before)
  • 切入点 切入点的概念通常和连接点概念容易分不清,切入点其实是一个规则,也就是说什么样的情况下(满足什么规则),

    就会去执行链接点的那些方法,这个规则就是切入点,这种规则用切入点表达式去制定
  • 引入(Introduction) 被代理的对象可以引入新接口,通过默认的实现类,让这个被代理的类增强
  • 目标对象 就是被切面执行了的对象
  • AOP代理 代理包括jdk代理和cglib代理,是aop底层实现过程
  • 织入 就是切面中的方法完成加载执行的过程

这里有8个概念,但真正要完成aop的理解,还不得不再引入两个概念。

  • 被代理对象 我们可以看到,上面说到目标对象永远是一个被代理的对象,也是被通知的对象。
  • 代理对象 代理对象呢, 就是最后通知后,生成的对象。

切入点表达式

  • execution

    用于匹配指定类型内的方法执行,匹配的是方法,可以确切到方法
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern (param-pattern)
throws-pattern?) modifiers-pattern 修饰符表达式 :public protect private ,可以不写,表示不限制
ret-type-pattern 返回值表达式 如 String代表返回值为String ,*代表任意返回值都可以 必填字段
declaring-type-pattern 类型,可以由完整包名加类名组成 可以只写包名加.*限定包下的所有类 可以不写,表示不限制
name-pattern 方法名表达式,可以由*统配所有字符 必填字段
param-pattern 参数列表,可以用..来表示所有的方法 必填字段
execution(public * * (..))   //所有public的方法
execution(* set*(..))  //所有set开头的方法
execution( * com.xyz.service.AccountService.* (..) ) //AccountService的所有方法,如果AccountService是接口,指实现了这个接口的所有方法
execution(* com.xyz.service.*.*(..)) //com.xyz.service包下的所有类的所有方法
execution(* com.xyz.service..*.*(..)) //com.xyz.service包及子包下的所有类的所有方法
  • within

    用于匹配指定类型内的方法执行,匹配的是类型内的方法,类型下的所有方法
within (com.xyz.service.*) // com.xyz.service包下面的所有类的所有方法
within (com.xyz.service..*) // com.xyz.service包或子包下面的所有类的所有方法
within (com.xyz.service.impl.UserServiceImpl) // UserServiceImpl类下面所有方法
  • this

    用于匹配当前AOP代理对象类型的执行方法,在前文中引入(Introduction)的代理对象使用,可以注入代理对象

  • target

    用于匹配当前目标对象类型的执行方法,可以注入目标对象,被代理的对象

  • args

    用于匹配当前执行的方法传入的参数为指定类型的执行方法,可以注入连接点(方法)的参数列表

  • @target

  • @args

  • @within

  • @annotation

  • bean

代码实战

5种通知的案例

  • DemoAspect.java

    定义切面,及通知,这里为了测试更多的案例,表达式切到AdviceKindTestController.java

package demo.aop.aspect; import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component; @Aspect
@Slf4j
@Component //必须是个bean
public class DemoAspect { //前置通知
@Before("execution (* demo.aop.controller.AdviceKindTestController.*(..))")
public void auth() {
log.info("前置通知,假装校验了个权限");
} //后置通知
@AfterReturning("execution (* demo.aop.controller.AdviceKindTestController.*(..))")
public Object afterSomething(){
log.info("后置通知,不太清楚运用场景");
return "ok";
} //环绕通知
//如果环绕通知 不返回执行结果 方法不会返回任何结果,导致接口拿不到任何数据
//所以一定把proceed 返回
//ProceedingJoinPoint 是 JoinPoint的子类,仅当环绕通知的时候,可以注入ProceedingJoinPoint的连接点
@Around("execution (* demo.aop.controller.AdviceKindTestController.*(..))")
public Object getMethodTime(ProceedingJoinPoint point) throws Throwable {
log.info("环绕通知,统计方法耗时,方法执行前");
Long beforeMillis = System.currentTimeMillis();
Object proceed = point.proceed();
Long taketimes= System.currentTimeMillis()-beforeMillis;
log.info(String.format("该方法用时%s毫秒",taketimes));
return proceed;
} //异常通知
@AfterThrowing("execution (* demo.aop.controller.AdviceKindTestController.*(..))")
public void throwSomething() {
log.info("异常通知,只有异常了才会通知。具体场景,不是特别了解");
} //最终通知
@After("execution (* demo.aop.controller.AdviceKindTestController.*(..))")
public void closeSomething() {
log.info("最终通知,官网说,可以用来回收某些资源。无论发不发生异常,都会被执行");
} }
  • AdviceKindTestController.java

测试用的接口类

package demo.aop.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
public class AdviceKindTestController { @GetMapping("/advice") //http://localhost:8080/advice
public String test() throws InterruptedException {
Thread.sleep(4);
return "ok";
} @GetMapping("/advice/throwing") //http://localhost:8080/advice/throwing
public String test2(){
int i=1/0;
return "ok";
}
}

访问 http://localhost:8080/advice 后台输出为

2020-10-18 11:24:01.201                 : 环绕通知,统计方法耗时,方法执行前
2020-10-18 11:24:01.201 : 前置通知,假装校验了个权限
2020-10-18 11:24:01.201 : 该方法用时6毫秒
2020-10-18 11:24:01.202 : 最终通知,官网说,可以用来回收某些资源。无论发不发生异常,都会被执行
2020-10-18 11:24:01.202 : 后置通知,不太清楚运用场景

执行顺序

- 环绕通知的前面部分
- 前置通知
- 环绕通知的后面部分
- 最终通知
- 后置通知

访问 http://localhost:8080/advice/throwing 后台输出为

2020-10-18 11:30:34.935                 : 环绕通知,统计方法耗时,方法执行前
2020-10-18 11:30:34.935 : 前置通知,假装校验了个权限
2020-10-18 11:30:34.936 : 最终通知,官网说,可以用来回收某些资源。无论发不发生异常,都会被执行
2020-10-18 11:30:34.936 : 异常通知,只有异常了才会通知。具体场景,不是特别了解
java.lang.ArithmeticException: / by zero //test2()方法抛出了异常

执行顺序如下,我们可以看到因为接口出现了异常,所以后置通知并没执行,环绕通知的后面部分也没执行,但最终通知异常通知被执行

- 环绕通知的前面部分
- 前置通知
- 最终通知
- 异常通知

下文预告

  • 切入点表达式详解
  • 通知优先级
  • 通知中引用切点的参数,目标对象,代理对象、切点对象及其方法调用
  • @DeclareParents 实现引入
  • @ControllerAdvice 实现统一错误处理

本文完整代码参考 https://gitee.com/haimama/java-study/tree/master/spring-aop

spring aop翻译文档http://shouce.jb51.net/spring/aop.html

Spring Aop 详解一的更多相关文章

  1. 【转载】Spring AOP详解 、 JDK动态代理、CGLib动态代理

    Spring AOP详解 . JDK动态代理.CGLib动态代理  原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html AOP是Aspec ...

  2. Spring AOP详解(转载)所需要的包

    上一篇文章中,<Spring Aop详解(转载)>里的代码都可以运行,只是包比较多,中间缺少了几个相应的包,根据报错,几经百度搜索,终于补全了所有包. 截图如下: 在主测试类里面,有人怀疑 ...

  3. Spring AOP详解及简单应用

    Spring AOP详解   一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...

  4. 转:Spring AOP详解

    转:Spring AOP详解 一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...

  5. Spring Aop 详解二

    这是Spring Aop的第二篇,案例代码很详解,可以查看https://gitee.com/haimama/java-study/tree/master/spring-aop-demo. 阅读前,建 ...

  6. [Spring学习笔记 5 ] Spring AOP 详解1

    知识点回顾:一.IOC容器---DI依赖注入:setter注入(属性注入)/构造子注入/字段注入(注解 )/接口注入 out Spring IOC容器的使用: A.完全使用XML文件来配置容器所要管理 ...

  7. Spring系列(四):Spring AOP详解

    一.AOP是什么 AOP(面向切面编程),可以说是一种编程思想,其中的Spring AOP和AspectJ都是现实了这种编程思想.相对OOP(面向过程编程)来说,提供了另外一种编程方式,对于OOP过程 ...

  8. Spring AOP详解

    一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址:http://www.cnbl ...

  9. Spring AOP详解 、 JDK动态代理、CGLib动态代理

    AOP是Aspect Oriented Programing的简称,面向切面编程.AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理以及日志记录.AOP将这些分散在各个业务逻辑中的代码 ...

随机推荐

  1. HashSet保证元素唯一原理以及HashMap扩容机制

    一.HashSet保证元素唯一原理: 依赖于hashCode()和equals()方法1.唯一原理: 1.1 当HashSet集合要存储元素的时候,会调用该元素的hashCode()方法计算哈希值 1 ...

  2. Linux:nginx基础

    一..简单介绍 nginx时开源的www服务器,只能解析静态的网页(html,js,css等),具有静态小文件高并发特性. 可做web服务器.负载均衡.web cache(web缓存) 优点: 1.高 ...

  3. MySQL 8 安装教程(个人版)+创建用户

    Mysql 8的安装教程 解压到指定目录如:D:\WinInstall\mysql-8.0.19-winx64这时候你需要在根目录下创建两个文件,分别是data文件夹和my.ini文件,然后使用编辑器 ...

  4. pytest(2):使用pycharm运行pytest

    pycharm运行 1.在pycharm里创建测试文件test_demo.py # Author xuejie zeng # encoding utf-8 # content of test_demo ...

  5. Sql Server中使用特定字符分割字符串

    在T-SQL中我们经常批量操作时都会对字符串进行拆分,可是SQL Server中却没有自带Split函数,所以要自己来实现了.这里将字符串分割以table形式输出 语法如下: SET ANSI_NUL ...

  6. 原来写插件还可以选MEF

    MEF是微软提供的一个轻量级的ICO容器,可以轻易的解除程序集的依赖关系,最近想写个类似插件试的软件所以搜索了一下,终于淘到宝了. 下面我们看看MEF是如何解耦的 新建一个控制台项目两个类库 Ites ...

  7. Spring基于XML的IOC环境搭建及入门

    一.使用Maven构建Java项目 * 项目目录结构 1. 在sun.service包下创建UserDao接口和接口实现类: UserDao接口: package sun.service; /** * ...

  8. Linux安装指定版Git以及卸载

     来自于:https://www.cnblogs.com/rstyro/articles/10817855.html 安装Git 在linux中,安装Git 一般一条命令即可,如下: Debian/U ...

  9. Python记录日志模块推荐-loguru!

      作者:小张学Python 本文链接: https://mp.weixin.qq.com/s/dkNkEohPl6H2VopUrpxxZg 转载请注明来源!! 前言 在做项目的时候一直在用Pytho ...

  10. 关于KeePass实现mstsc远程桌面(rdp协议)的自动登录

    本文的Keepass版本:KeePass Password Safe Version 2.45 首先介绍一下Keepass,引用官网的解释如下: KeePass is a free open sour ...