AOP 基本概念
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
Spring AOP的源码分析见【Spring】Spring5 AOP源码分析。
AOP 底层原理
AOP 底层使用动态代理 ,动态代理有两种情况:
第一种 有接口情况,使用JDK 动态代理 ;创建接口实现类代理对象,增强类的方法
第二种 没有接口情况,使用CGLIB 动态代理创建子类的代理对象,增强类的方法(该方法不需要实现接口,由CGLIB创建代理对象)
AOP JDK 动态代理
1)使用 JDK 动态代理,使用 Proxy
类里面的方法创建代理对象:
调用 newProxyInstance 方法,方法有三个参数:
1 2 3
| public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
|
- 参数一:类加载器
- 参数二:增强方法所在的类,这个类实现的接口,支持多个接口
- 参数三:实现这个接口
InvocationHandler
,创建代理对象,写增强的部分
2)编写 JDK 动态代理代码
1 2 3 4 5
| public interface UserDao { public int add(int a,int b); public String update(String id); }
|
1 2 3 4 5 6 7 8 9 10 11
| public class UserDaoImpl implements UserDao { @Override public int add(int a, int b) { return a+b; } @Override public String update(String id) { return id; } }
|
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
| public class JDKProxy { public static void main(String[] args) { Class[] interfaces = {UserDao.class}; UserDaoImpl userDao = new UserDaoImpl();
UserDao dao =(UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); int result = dao.add(1, 2); System.out.println("result:"+result); } }
class UserDaoProxy implements InvocationHandler { private Object obj; public UserDaoProxy(Object obj) { this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args)); Object res = method.invoke(obj, args); System.out.println("方法之后执行...."+obj); return res; } }
|
AOP 术语
- 连接点(JointPoint):类里面哪些方法可以被增强,这些方法称为连接点,每一个方法的每一个位置(开始位置,返回位置,异常位置等)都是一个连接点。
- 切入点(PointCut):切面通知执行的“地点”的定义,实际被真正增强的方法称为切入点。
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ….
- 切面(Aspect):横切关注点被模块化的特殊对象。即,它是一个类。
- 通知方法(Advice):切面必须要完成的工作。即,它是类中的一个方法。包含前置通知,后置通知,环绕通知 ,异常通知和最终通知。
- 目标(Target):被通知的对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
AOP CGLIB 动态代理
CGLIB是一个功能强大,高性能的代码生成库(第三方库,可以由Maven导入)。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib
CGLIB 原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
CGLIB 底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
CGLIB缺点:对于final方法,无法进行代理。
Spring 框架一般都是基于 AspectJ 实现 AOP 操作,AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,进行 AOP 操作。基于 AspectJ 实现 AOP 操作的两种方式:
AOP 基于注解开发
采用动态代理的设计模式,在程序运行期间动态地将某段代码切入到指定方法(切入点)指定位置进行运行的编程方式。
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.12.RELEASE</version> </dependency>
|
定义一个业务逻辑类(MathCalculator
)。试图在业务逻辑运行的时候将日志进行打印(方法之前,方法运行结束,方法出现异常等)
1 2 3 4 5
| public class MathCalculator { public int div(int i, int j){ return i/j; } }
|
定义一个日志切面类(LogAspects
),在切面类里需要动态感知MathCalculator.div()
方法运行到什么阶段并执行相应通知方法。通知方法:
- 前置通知(
@Before
):在切入点(PointCut
)运行之前运行
- 后置通知(
@After
):在切入点运行结束之后运行(无论方法是否正常结束)
- 返回通知(
@AfterReturning
):在切入点正常返回之后运行(异常不执行)
- 异常通知(
@AfterThrowing
):在切入点出现异常之后运行
- 环绕通知(
@Around
):动态代理的方式手动推进切入点运行(joinPoint.procced()
),是最底层的通知,其可以实现上述四个通知效果
通知方法的执行顺序:
- 环绕通知(
@Around
)joinPoint.procced()
方法之前的代码
- 前置通知(
@Before
)
- 业务代码
- 返回通知(
@AfterReturning
)/ 若有异常,此时执行异常通知(@AfterThrowing
)
- 后置通知(
@After
)
- 环绕通知(
@Around
)joinPoint.procced()
方法以及其之后的代码
多个切面的情况下,先执行前置通知的后执行返回通知和后置通知,后执行前置通知的先执行返回通知和后置通知。类似方法栈先进后出。执行顺序由切面类的字母顺序排序,也可以通过@Order(1)
设置优先级
切入点表达式写法:
1 2 3 4 5 6 7 8 9
| (1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强 (2)语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) ) (3)例子如下: 例 1:对 com.zhao.dao.BookDao 类里面的 add 进行增强 execution(* com.zhao.dao.BookDao.add(..)) 例 2:对 com.zhao.dao.BookDao 类里面的所有的方法进行增强 execution(* com.zhao.dao.BookDao.* (..)) 例 3:对 com.zhao.dao 包里面所有类,类里面所有方法进行增强 execution(* com.zhao.dao.*.* (..))
|
配置类需要添加@EnableAspectJAutoProxy以开启注解版的AOP自动代理。整个AOP就是从@EnableAspectJAutoProxy注解开始执行的。(Spring中有很多的@EnableXXX
注解,其作用是代替xml文件中的一些配置开启某些功能)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @EnableAspectJAutoProxy @Configuration public class SpringConfigAOP { @Bean public MathCalculator calculator(){ return new MathCalculator(); }
@Bean public LogsAspects logsAspects(){ return new LogsAspects(); } }
|
切面类LogsAspects
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
| @Aspect @Order(1) public class LogsAspects {
@Pointcut("execution(* com.zhao.aop.MathCalculator.*(..))") public void pointCut(){ }
@Before("execution(int com.zhao.aop.MathCalculator.div(int, int))") public void logStart(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); System.out.println("前置通知@Before.... "); }
@After("execution(* com.zhao.aop.MathCalculator.*(..))") public void logEnd(){ System.out.println("后置通知@After...."); }
@AfterReturning(value = "pointCut()", returning = "result") public void logReturn(Object result){ System.out.println("返回通知@AfterReturning.... "); }
@AfterThrowing(value = "pointCut()", throwing = "exception") public void logException(Exception exception){ System.out.println("异常通知@AfterThrowing.... "); }
@Around("pointCut()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("签名: " + proceedingJoinPoint.getSignature()); Object[] args = proceedingJoinPoint.getArgs();
try { System.out.println("【环绕前置通知】.... "); Object result = proceedingJoinPoint.proceed(args); System.out.println("【环绕返回通知】.... "); } catch (Exception exception){ System.out.println("【环绕异常通知】.... "); } finally { System.out.println("【环绕后置通知】.... "); }
return result; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class AOPTest { AnnotationConfigApplicationContext context;
@Test public void testOfAop() { context = new AnnotationConfigApplicationContext(SpringConfigAOP.class); System.out.println("容器创建完成....");
MathCalculator bean = context.getBean(MathCalculator.class); bean.div(1, 1);
context.close(); } }
|
控制台打印:
1 2 3 4 5 6 7 8 9 10
| 签名: int com.zhao.aop.MathCalculator.div(int,int) 【环绕前置通知】.... 前置通知@Before.... div方法执行... 返回通知@AfterReturning.... 后置通知@After.... 【环绕返回通知】.... 【环绕后置通知】...
Process finished with exit code 0
|
AOP 基于xml开发
使用AOP织入,需要导入一个依赖包(和上面的区别?)
1 2 3 4 5 6
| <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
|
方式一:通过 Spring API 实现AOP
第一步:首先编写业务接口和实现类
1 2 3 4 5 6
| public interface UserService { public void add(); public void delete(); public void update(); public void search(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加用户"); } @Override public void delete() { System.out.println("删除用户"); } @Override public void update() { System.out.println("更新用户"); } @Override public void search() { System.out.println("查询用户"); } }
|
第二步:然后编写增强类 :一个前置增强,一个后置增强
1 2 3 4 5 6 7 8 9
| public class Log implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| public class AfterLog implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法," +"返回值:"+returnValue); } }
|
第三步:最后去Spring的文件中注册 , 并实现AOP切入实现 , 注意导入约束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userService" class="com.zhao.service.UserServiceImpl"/> <bean id="log" class="com.zhao.log.Log"/> <bean id="afterLog" class="com.zhao.log.AfterLog"/> <aop:config> <aop:pointcut id="pointcut" expression="execution(* com.zhao.service.UserServiceImpl..*(..))"/> <aop:advisor advice-ref="log" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> </aop:config> </beans>
|
第四步:测试
1 2 3 4 5 6 7 8
| public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) context.getBean("userService"); userService.search(); } }
|
Spring的AOP就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 ,当执行领域业务时,将会把公共业务加进来,实现公共业务的重复利用。
方式二:自定义类来实现AOP
目标业务类不变依旧是userServiceImpl
第一步:编写一个切入类
1 2 3 4 5 6 7 8
| public class DiyPointcut { public void before(){ System.out.println("---------方法执行前---------"); } public void after(){ System.out.println("---------方法执行后---------"); } }
|
第二步:在Spring中配置
1 2 3 4 5 6 7 8 9 10 11 12
|
<bean id="diy" class="com.zhao.config.DiyPointcut"/>
<aop:config> <aop:aspect ref="diy"> <aop:pointcut id="diyPonitcut" expression="execution(* com.zhao.service.UserServiceImpl..*(..))"/> <aop:before pointcut-ref="diyPonitcut" method="before"/> <aop:after pointcut-ref="diyPonitcut" method="after"/> </aop:aspect> </aop:config>
|
第三步:测试
1 2 3 4 5 6 7 8
| public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) context.getBean("userService"); userService.add(); } }
|
Spring AOP 源码分析
Spring AOP的源码分析见【Spring】Spring5 AOP源码分析。