AOP 面向切面编程
1 什么是AOP
- 面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,降低耦合性,提高复用性,同时加快开发效率
- 通俗描述:不通过修改源代码的方式,在主干功能上添加新的功能
2 AOP 底层原理
(1)AOP底层使用动态代理
1 有两种情况动态代理
-
第一种 有接口的情况,使用 JDK动态代理
创建接口实现类的代理对象
-
第二种 没有接口的情况,使用 CGLIB动态代理
创建的当前类子类的代理对象
2 JDK动态代理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mjV3cq18-1650027365333)(C:\Users\30386\Desktop\SPring boot\img\JDK 动态代理.png)]
-
什么是代理?
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理
-
动态代理步骤:
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2.创建被代理的类以及接口
3.通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
4.通过代理调用方法 -
使用
-
1 需要动态代理的接口:
public interface Subject{ public String SayHello(String name); public String SayGoodBye(); }
-
2 需要代理的实际对象
public class RealSubject implements Subject{ public String SayHello(String name){ return "hello " + name; } public String SayGoodBye(){ return " good bye "; } }
-
3 调用处理器实现类
/** * 调用处理器实现类 * 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象 */ public class InvocationHandlerImpl implements InvocationHandler{ //这个就是我们要代理的真实对象 private Object subject; /** * 构造方法,给我们要代理的真实对象赋初值 * @param subject */ public InvocationHandlerImpl(Object subject){ this.subject = subject; } /** * 该方法负责集中处理动态代理类上的所有方法调用。 * 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行 * * @param proxy 代理类实例 * @param method 被调用的方法对象 * @param args 调用参数 * @return * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ //在代理真实对象前我们可以添加一些自己的操作 System.out.println("在调用之前,我要干点啥呢?"); System.out.println("Method:" + method); //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用 Object returnValue = method.invoke(subject, args); //在代理真实对象后我们也可以添加一些自己的操作 System.out.println("在调用之后,我要干点啥呢?"); return returnValue; } }
-
4 测试
public class DynamicProxyDemonstration{ public static void main(String[] args){ //代理的真实对象 Subject realSubject = new RealSubject(); /** * InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发 * 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用. * 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法 */ InvocationHandler handler = new InvocationHandlerImpl(realSubject); //getClass() -- 获取对象所在类 //getClassLoader() -- 获取类装载器 //getInterfaces() -- 获取类继承的所有接口 ClassLoader loader = realSubject.getClass().getClassLoader(); Class[] interfaces = realSubject.getClass().getInterfaces(); // 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler); System.out.println("动态代理对象的类型:"+subject.getClass().getName()); String hello = subject.SayHello("guxinhu"); System.out.println(hello); //String goodbye = subject.SayGoodBye(); //System.out.println(goodbye); } }
-
3 CGLIB动态代理
-
什么是cglib
CGLIB是一个强大的高性能的代码生成包。
1>它广泛的被许多AOP的框架使用,例如:Spring AOP和dynaop,为他们提供方法的interception(拦截); 2>hibernate使用CGLIB来代理单端single-ended(多对一和一对一)关联(对集合的延迟抓取,是采用其他机制实现的); 3>EasyMock和jMock是通过使用模仿(moke)对象来测试java代码的包。 它们都通过使用CGLIB来为那些没有接口的类创建模仿(moke)对象。
-
底层
-
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM(Java字节码操控框架),来转换字节码并生成新的类。
-
除了CGLIB包,脚本语言例如 Groovy和BeanShell,也是使用ASM来生成java的字节码。
-
当不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
-
所以cglib包要依赖于asm包,需要一起导入。
-
Spring AOP和Hibernate同时使用JDK的动态代理和CGLIB包。Spring AOP,如果不强制使用CGLIB包,默认情况是使用JDK的动态代理来代理接口。
-
下图为cglib与一些框架和语言的关系 CGLIB Library and ASM Bytecode Framework
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jaLJnmiP-1650027365335)(C:\Users\30386\Desktop\SPring boot\img\cglib.gif)]
-
AOP (术语)
-
连接点
可以被增强的方法
-
切入点
实际被真正增强的方法
-
通知(增强)
- 实际增强的逻辑部分被称为通知
- 通知类型
- 前置类型 @Before
- 后置类型 @AfterReturning (返回值之前执行)
- 环绕类型 @Around
- 异常类型 @After
- 最终类型 @After(一定执行的)
-
切面
是动作:把通知应用到切入点的过程
AOP操作
-
Spring 框架一般都是基于 AspectJ 实现 AOP 操作
AspectJ 不是Spring组成部分,独立的AOP框架,AspectJ和Spring一起使用,进行AOP操作
-
基于 AspectJ实现AOP操作
- xml
- 注解(主要)
-
切入点表达式
-
作用:知道对谁增强
-
语法结构
execution([权限修饰符] [返回类型] [方法名称] ([参数列表]))
-
-
AspectJ(注解)
-
1 原始类
-
2 增强类
-
3 通知配置
-
开启注解扫描
-
使用注解创建对象
-
增强类上添加 @Aspect
-
开启生成代理对象
<!-- xml 形式 --> <aop:aspcet.j-autoproxy></aop:aspcet.j-autoproxy>
@Configuration @ComponentScan(basePackages = {"com.xin"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class SpringConfig { }
-
-
4 配置不同类型的通知
- 在增强类的里面,在做为通知方法上面添加通知类型,使用切面点表达式
-
细节部分
- 相同切入点抽取
// 创建相同切入点抽取 @Poincut(value="切入点表达式") public void add(){ } // 使用相同切入点抽取 @Before(value="add()") public void before(){ System.out.println("before+++"); }
2. 增强类的优先级
在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高
-
-
AspectJ(xml)
-
原始类,增强类
-
xml 中创建两类对象
-
xml 配置切入点
<!-- 创建对象 --> <bean id="book" class="原始类"></bean> <bean id="bookProxy" class="增强类"></bean> <!-- 配置aop增强 --> <aop:config> <!-- 切入点 --> <aop:pointcut id="p" expression="切入点表达式"></aop:pointcut> <!-- 配置切面 --> <aop:aspect ref="bookProxy"> <!-- 增强作用在具体方法上 --> <aop:before method="增强类中方法名" pointcut-ref="p"></aop:before> </aop:aspect> </aop:config>
-