AOP
一、概述
-
概念
AOP:Aspect Oriented Programming,面向切面编程,是函数式编程的一种衍生范型(反射)。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
-
目的
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
-
通俗理解
在不改变源代码的基础上,增加新的功能。
二、底层原理
三、JDK动态代理底层代码
-
有接口的示例
interface UserDAO { public int add(int a, int b); public int update(int c); } public class UserDAOImpl implements UserDAO{ @Overload public int add(int a, int b) { return a+b; } @Overload public int update(int c) { return c; } }
-
使用Proxy类,创建代理对象
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) // 第一个参数:被代理类的类加载器 // 第二个参数:接口数组,指被增强方法所在的类,这个类实现的一个或多个接口 // 第三个参数:实现InvocationHandler这个接口,传入被代理类的对象,写增强方法
public class JdkProxy { public static void main(String[] args) { /* 第一个参数:就是该类的类加载器 第二个参数:一个Class数组,里面是实现类实现的各个接口的Class(需要创建) 第三个参数:一个InvocationHandler接口的实现类(需要创建) */ // 1.创建第二个参数:创建被代理实现类所实现的所有接口的Class数组 Class[] interfaces = {UserDAO.class}; // 3.创建被代理实现类的对象,用于传入第三个参数中 UserDAOImpl userDAO = new UserDAOImpl(); // 4.通过Proxy.newProxyInstance()方法创建指定实现类的代理对象 UserDAO dao = (UserDAO)Proxy.newProxyInstance( JdkProxy.class.getClassLoader(), interfaces, new UserDAOProxy(userDAO) ); // 5.通过生成的代理对象调用某个方法,该方法就会被增强 int add = dao.add(1, 2); System.out.println("add = " + add); } } // 2.创建第三个参数,实现InvocationHandler接口 class UserDAOProxy implements InvocationHandler { // 2.1 通过有参构造器将被代理类传进去,写成Object类型,是为了通用性,直接写成UserDAOImpl也可以。 private Object obj; public UserDAOProxy(Object obj) { this.obj = obj; } // 2.2 在invoke方法内写增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("执行方法之前"); Object result = method.invoke(obj, args); System.out.println("执行方法之后"); return result; } }
-
更加深入的分析及动态代理:详见博客 Java七十: 反射应用 - - 动态代理
四、AOP术语
-
连接点
类里面哪些方法可以被增强,这些方法就称为连接点
-
切入点
实际被真正增强的方法,就称为切入点
-
通知(增强)
实际增强的逻辑部分称为通知,分为5类
① 前置通知:在切入点(被增强方法)之前执行
② 后置通知:在切入点之后执行,当切入点有异常,后置通知不执行
③ 环绕通知:在切入点前后都可执行,当切入点有异常,环绕后通知不执行,环绕前通知仍然执行
④ 异常通知:当切入点有异常时执行
⑤ 最终通知:无论什么情况,都会被最终执行
-
切面
把通知应用到切入点的过程
五、AOP的操作的准备工作
-
Spring框架中基于AspectJ 实现AOP操作
AapectJ 不是Spring组成部分,是一个独立的AOP框架,一般把AspectJ框架和Spring框架一起使用,进行AOP操作
-
引入依赖
-
切入点表达式
作用:明确对哪个类中的哪个方法进行增强
结构:
execution([权限修饰符][返回类型][类全路径][方法名][参数列表]) 例1,对com.atguigu.dao.BookDao类中的add方法进行增强 execution(* com.atguigu.dao.BookDao.add(..)) 例2,对com.atguigu.dao.BookDao类中的所有方法进行增强 execution(* com.atguigu.dao.BookDao.*(..)) 例3,对com.atguigu.dao包中所有类类中的所有方法进行增强 execution(* com.atguigu.dao.*.*(..))
六、基于AspectJ注解的AOP操作
-
在Spring配置文件中,开启注解扫描
<!-- 开启注解扫描 --> <context:component-scan base-package="com.atguigu.spring5.aopannotation"></context:component-scan>
-
使用注解创建User和UserProxy对象
// 被代理类 @Repository public class User { public void add() { System.out.println("add方法"); } } // 代理类(增强类) @Component // 表示可以生成代理对象 @Aspect public class UserProxy { }
-
在Spring配置文件中开启生成代理对象
<!-- 开启Aspect生成代理对象 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
配置不同类型的通知
// 表示可以被扫描到 @Component // 表示可以生成代理对象 @Aspect public class UserProxy { // 前置通知 @Before(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))") public void before() { System.out.println("before...前置通知"); } // 后置通知 @AfterReturning(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))") public void afterReturning() { System.out.println("afterReturning...后置通知"); } // 最终通知 @After(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))") public void after() { System.out.println("after...最终通知"); } // 异常通知 @AfterThrowing(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))") public void afterThrowing() { System.out.println("afterThrowing...异常通知"); } // 环绕通知 @Around(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("around...环绕之前"); proceedingJoinPoint.proceed(); System.out.println("around...环绕之后"); } }
注:@Aspect配合通知注解内的切点execution表达式,就完成了Proxy.newProxyInstance()及InvocationHandler的功能
-
测试类
public class TestAOP { @Test public void testAOP() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); User user = context.getBean("user",User.class); user.add(); } }
执行结果
-
抽取相同切入点表达式
注意,通知内的value值中的方法一定要带括号
@Component @Aspect public class UserProxy { // 抽取相同切入点表达式 @Pointcut(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))") public void point() { } // 前置通知 @Before(value = "point()") public void before() { System.out.println("before...前置通知"); } @AfterReturning(value = "point()") public void afterReturning() { System.out.println("afterReturning...后置通知"); } }
-
优先级@Order,有多个代理类时,可在这些类上使用@Order来确定执行顺序
// @Order从0开始的整数,数字越小,优先级越高 @Component @Aspect @Order(2) public class UserProxy {}
七、基于AspectJ的xml配置文件的AOP操作
-
新建两个类
// 被增强类 public class Book { public void buy() { System.out.println("buy..."); } } // 代理类 public class BookProxy { public void add() { System.out.println("增强..."); } }
-
对两个类进行AOP配置
<?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="book" class="com.atguigu.spring5.annoxml.Book"></bean> <bean id="bookProxy" class="com.atguigu.spring5.annoxml.BookProxy"></bean> <!-- 配置aop增强 --> <aop:config> <!-- 配置切入点 --> <aop:pointcut id="p" expression="execution(* com.atguigu.spring5.annoxml.Book.buy(..))"/> <!-- 配置切面,即哪个增强方法,其类型和切入点 --> <aop:aspect ref="bookProxy"> <aop:before method="add" pointcut-ref="p"/> </aop:aspect> </aop:config> </beans>