Spring AOP (Aspect-Oriented Programming) 是 Spring 框架的一个重要模块,它是一种编程范式,旨在增强现有代码的功能而不修改原始代码。理解 Spring AOP 需要了解以下几个关键概念:
综上所述,Spring AOP 提供了一种通过将横切关注点与业务逻辑分离的方式,将通用的功能(例如日志、事务等)模块化,并将它们应用于应用程序的不同部分,以提高代码的重用性、可维护性和可扩展性。
代理模式是一种结构型设计模式, 让你能够提供对象的替代品或其占位符, 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。
问题
为什么要控制对于某个对象的访问呢? 举个例子: 有这样一个消耗大量系统资源的巨型对象, 你只是偶尔需要使用它, 并非总是需要。
数据库查询有可能会非常缓慢。
你可以实现延迟初始化: 在实际有需要时再创建该对象。 对象的所有客户端都要执行延迟初始代码。 不幸的是, 这很可能会带来很多重复代码。
在理想情况下, 我们希望将代码直接放入对象的类中, 但这并非总是能实现: 比如类可能是第三方封闭库的一部分。
解决方案
代理模式建议新建一个与原服务对象接口相同的代理类, 然后更新应用以将代理对象传递给所有原始对象客户端。 代理类接收到客户端请求后会创建实际的服务对象, 并将所有工作委派给它。
代理将自己伪装成数据库对象, 可在客户端或实际数据库对象不知情的情况下处理延迟初始化和缓存查询结果的工作。
案例:
package com.shu.aop.demo01;
/**
* @description: 视频类,用于存储视频信息,包括视频id,视频标题,视频数据等信息
* @author: shu
* @createDate: 2023/7/22 17:02
* @version: 1.0
*/
public class Video {
public String id;
public String title;
public String data;
Video(String id, String title) {
this.id = id;
this.title = title;
this.data = "Random video.";
}
}
我们指定获取视频的两个接口,一个获取热门视频,一个通过视频ID获取视频
package com.shu.aop.demo01;
import java.util.HashMap;
/**
* @description:
* @author: shu
* @createDate: 2023/7/22 17:02
* @version: 1.0
*/
public interface ThirdPartyYouTubeLib {
/**
* 返回热门视频
* @return
*/
HashMap<String, Video> popularVideos();
/**
* 根据视频id获取视频
* @param videoId
* @return
*/
Video getVideo(String videoId);
}
编写实现类
package com.shu.aop.demo01;
import java.util.HashMap;
/**
* @description:
* @author: shu
* @createDate: 2023/7/22 17:03
* @version: 1.0
*/
public class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib {
/**
* 返回热门视频
* @return
*/
@Override
public HashMap<String, Video> popularVideos() {
return getRandomVideos();
}
/**
* 根据视频id获取视频
* @param videoId
* @return
*/
@Override
public Video getVideo(String videoId) {
return getSomeVideo(videoId);
}
private HashMap<String, Video> getRandomVideos() {
System.out.print("Downloading populars... ");
HashMap<String, Video> hmap = new HashMap<String, Video>();
hmap.put("catzzzzzzzzz", new Video("sadgahasgdas", "Catzzzz.avi"));
hmap.put("mkafksangasj", new Video("mkafksangasj", "Dog play with ball.mp4"));
hmap.put("dancesvideoo", new Video("asdfas3ffasd", "Dancing video.mpq"));
hmap.put("dlsdk5jfslaf", new Video("dlsdk5jfslaf", "Barcelona vs RealM.mov"));
hmap.put("3sdfgsd1j333", new Video("3sdfgsd1j333", "Programing lesson#1.avi"));
System.out.print("Done!" + "\n");
return hmap;
}
private Video getSomeVideo(String videoId) {
System.out.print("Downloading video... ");
Video video = new Video(videoId, "Some video title");
System.out.print("Done!" + "\n");
return video;
}
}
现在我们需要通过编写代理类来时实现它,这就需要要求我们编写的代理类隐藏原始类,同时应该保持与原始类一样的方法,当然这就可以通过实现来实现
package com.shu.aop.demo01;
import java.util.HashMap;
/**
* @description:
* @author: shu
* @createDate: 2023/7/22 17:06
* @version: 1.0
*/
public class YouTubeCacheProxy implements ThirdPartyYouTubeLib{
private ThirdPartyYouTubeLib youtubeService;
private HashMap<String, Video> cachePopular = new HashMap<String, Video>();
private HashMap<String, Video> cacheAll = new HashMap<String, Video>();
public YouTubeCacheProxy() {
this.youtubeService = new ThirdPartyYouTubeClass();
}
/**
* 返回热门视频
*
* @return
*/
@Override
public HashMap<String, Video> popularVideos() {
if (cachePopular.isEmpty()) {
cachePopular = youtubeService.popularVideos();
} else {
System.out.println("Retrieved list from cache.");
}
return cachePopular;
}
/**
* 根据视频id获取视频
*
* @param videoId
* @return
*/
@Override
public Video getVideo(String videoId) {
Video video = cacheAll.get(videoId);
if (video == null) {
video = youtubeService.getVideo(videoId);
cacheAll.put(videoId, video);
} else {
System.out.println("Retrieved video '" + videoId + "' from cache.");
}
return video;
}
}
编写下载器,根据传入不同的实现来渲染视频
package com.shu.aop.demo01;
import java.util.HashMap;
/**
* @description:
* @author: shu
* @createDate: 2023/7/22 17:07
* @version: 1.0
*/
public class YouTubeDownloader {
private ThirdPartyYouTubeLib api;
public YouTubeDownloader(ThirdPartyYouTubeLib api) {
this.api = api;
}
/**
* 渲染视频页面
* @param videoId
*/
public void renderVideoPage(String videoId) {
Video video = api.getVideo(videoId);
System.out.println("\n-------------------------------");
System.out.println("Video page (imagine fancy HTML)");
System.out.println("ID: " + video.id);
System.out.println("Title: " + video.title);
System.out.println("Video: " + video.data);
System.out.println("-------------------------------\n");
}
/**
* 渲染热门视频
*/
public void renderPopularVideos() {
HashMap<String, Video> list = api.popularVideos();
System.out.println("\n-------------------------------");
System.out.println("Most popular videos on YouTube (imagine fancy HTML)");
for (Video video : list.values()) {
System.out.println("ID: " + video.id + " / Title: " + video.title);
}
System.out.println("-------------------------------\n");
}
}
测试
package com.shu.aop.demo01;
/**
* @description:
* @author: shu
* @createDate: 2023/7/22 17:08
* @version: 1.0
*/
public class ProxyTest {
public static void main(String[] args) {
YouTubeDownloader naiveDownloader = new YouTubeDownloader(new ThirdPartyYouTubeClass());
YouTubeDownloader smartDownloader = new YouTubeDownloader(new YouTubeCacheProxy());
long naive = test(naiveDownloader);
long smart = test(smartDownloader);
System.out.print("Time saved by caching proxy: " + (naive - smart) + "ms");
}
private static long test(YouTubeDownloader downloader) {
long startTime = System.currentTimeMillis();
// User behavior in our app:
downloader.renderPopularVideos();
downloader.renderVideoPage("catzzzzzzzzz");
downloader.renderPopularVideos();
downloader.renderVideoPage("dancesvideoo");
// Users might visit the same page quite often.
downloader.renderVideoPage("catzzzzzzzzz");
downloader.renderVideoPage("someothervid");
long estimatedTime = System.currentTimeMillis() - startTime;
System.out.print("Time elapsed: " + estimatedTime + "ms\n");
return estimatedTime;
}
}
记住重要的一点,为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。
在我们的现实生活中其实有很多这样的案例,不如商品经销商与厂家的关系,出租房的人与中介。
package com.shu.agency;
/**
* @author shu
* @version 1.0
* @date 2022/7/20 16:00
* @describe 代理类和委托代理类都要实现的接口
*/
public interface Sell {
/**
* 销售产品
*/
void market();
/**
* 生成产品
*/
void add();
}
package com.shu.agency;
/**
* @author shu
* @version 1.0
* @date 2022/7/20 16:03
* @describe 生产厂家
*/
public class Production implements Sell{
@Override
public void market() {
System.out.println("生产厂家销售产品了哦!!!");
}
@Override
public void add() {
System.out.println("生产厂家销售产品了哦!!!");
}
}
package com.shu.agency;
/**
* @author shu
* @version 1.0
* @date 2022/7/20 16:05
* @describe 商家代销产品
*/
public class Merchant implements Sell{
Sell production;
public Merchant(Production production) {
this.production = production;
}
@Override
public void market() {
production.market();
}
@Override
public void add() {
production.add();
}
}
/**
* 调用处理程序
*/
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args);
}
定义中介类
package com.shu.agency;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author shu
* @version 1.0
* @date 2022/7/20 19:33
* @describe
*/
public class DynamicProxy implements InvocationHandler {
//obj为委托类对象;
private Object obj;
public DynamicProxy(Object obj) {
this.obj = obj;
}
/**
* 通过反射来执行真正的方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(obj, args);
return result;
}
}
测试
package com.shu.agency;
import java.lang.reflect.Proxy;
/**
* @author shu
* @version 1.0
* @date 2022/7/20 19:35
* @describe
*/
public class ProxyTest {
public static void main(String[] args) {
//创建中介类实例
DynamicProxy inter = new DynamicProxy(new Production());
//加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
//获取代理类实例sell
Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter));
sell.market();
sell.add();
}
}
缺点
Proxy.newProxyInstance(ClassLoader loader, Class>[] interfaces,InvocationHandler h) ClassLoader loader:表示类加载器,用于加载生成的代理类。可以通过目标类的 ClassLoader 实例或者其他合适的类加载器来指定。 Class>[] interfaces:表示代理类要实现的接口列表。这是一个数组,指定了代理类要实现的接口,通过代理对象调用接口中的方法时,实际上会被转发给 InvocationHandler 的 invoke() 方法处理。
InvocationHandler h:表示代理对象的调用处理程序,也是一个实现了 InvocationHandler 接口的对象。InvocationHandler 接口中只有一个方法 invoke(),当代理对象的方法被调用时,会被传递到 invoke() 方法中进行处理。 动态代理的作用是允许我们在调用方法之前或之后执行一些额外的操作,比如记录日志、实现事务管理等。通过 newProxyInstance() 方法创建的代理对象会自动关联到指定的 InvocationHandler,并将方法调用委托给 InvocationHandler 来处理。
在 Java 中,ClassLoader(类加载器)是一个关键的组件,它负责将 Java 类字节码从磁盘或其他来源加载到 Java 虚拟机(JVM)中,并将其转换为可以在运行时使用的类对象。ClassLoader 实现了 Java 虚拟机的动态加载机制,使得 Java 程序可以在运行时根据需要加载类,而不是在编译时就将所有类加载到内存中。
Java 中的ClassLoader是一个抽象类(java.lang.ClassLoader),它有几个重要的子类,如下:
除了上述三种类加载器,Java 还提供了一些其他特殊用途的类加载器,如URLClassLoader用于从特定URL加载类,以及自定义ClassLoader用于实现特定的类加载行为。
类加载器的工作原理是实现一个双亲委派模型。当一个类加载器加载一个类时,它首先会尝试将类委派给父类加载器进行加载。如果父类加载器找不到该类,则该类加载器会尝试加载该类。这样的层级结构使得类加载器之间形成了一个类似于树的结构,保证了类的加载顺序和类的隔离性。
类加载器在 Java 应用程序的运行中起着重要的作用,允许动态加载类,实现热部署和动态扩展等功能。
package com.shu.aop;
/**
* @description:
* @author: shu
* @createDate: 2023/7/22 17:34
* @version: 1.0
*/
public class ClassLoaderTest {
public static void main(String[] args) {
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getParent());
// sun.misc.Launcher$AppClassLoader@18b4aac2
// sun.misc.Launcher$ExtClassLoader@1b6d3586
// null
}
}
当然也很明显,这个类的作用就是来加载类,但是注意动态代理执行的时候是借用其他的类的类加载器
动态代理生成的代理类通常是在运行时通过字节码生成技术生成的,因此在创建代理对象时,需要一个 ClassLoader 来加载这个新生成的代理类。这里会使用调用线程的当前类加载器(即调用者的类加载器)来加载这个代理类。
通常情况下,动态代理的实现不会使用 Bootstrap ClassLoader(启动类加载器)来加载代理类,因为这样的代理类对于 JVM 来说并不是一个内置类。相反,它会使用调用者的类加载器和父类加载器来加载代理类,这样就确保了代理类的可见性和正确性。
invoke() 在 InvocationHandler 接口中的 invoke() 方法中,有三个参数:
Object proxy:代理对象本身。在 invoke() 方法中,可以使用 proxy 参数来调用代理对象的其他方法,或者在特定情况下使用代理对象本身。
Method method:被调用的方法对象。method 参数表示当前正在调用的方法,通过它可以获取方法的名称、参数等信息。
Object[] args:方法的参数数组。args 参数是一个对象数组,表示传递给方法的参数。通过这个数组,可以获取方法调用时传递的具体参数值。
package com.shu.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @description:
* @author: shu
* @createDate: 2023/7/22 17:47
* @version: 1.0
*/
public class TestInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
依赖
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>2.2.2version>
dependency>
编写代理类
package com.shu.aop.demo03;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
/**
* @description: cglib动态代理类,实现MethodInterceptor接口
* @author: shu
* @createDate: 2023/7/22 17:58
* @version: 1.0
*/
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("这里是对目标类进行增强!!!");
//注意这里的方法调用,不是用反射哦!!!
Object object = methodProxy.invokeSuper(obj, args);
return object;
}
}
测试
package com.shu.aop.demo03;
import com.shu.aop.demo02.Production;
import com.shu.aop.demo02.Sell;
import net.sf.cglib.proxy.Enhancer;
/**
* @description:
* @author: shu
* @createDate: 2023/7/22 18:00
* @version: 1.0
*/
public class CglibTest {
public static void main(String[] args) {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer = new Enhancer();
//设置目标类的字节码文件
enhancer.setSuperclass(Production.class);
//设置回调函数
enhancer.setCallback(new MyMethodInterceptor());
//这里的creat方法就是正式创建代理类
Production production = (Production) enhancer.create();
production.market();
production.add();
}
}
JDK动态代理具体实现原理: 通过实现InvocationHandler接口创建自己的调用处理器;
通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。
CGLib动态代理具体实现原理: 利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
两者对比: JDK动态代理是面向接口的。
CGLib动态代理是通过字节码底层继承要代理类来实现,因此如果被代理类被final关键字所修饰,会失败。在创建代理这一块没有JDK动态代理快,但是运行速度比JDK动态代理要快。
使用注意: 如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spring默认采用JDK动态代理实现机制);
如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。
JDK和CGLib效率区别 创建效率: CGLib由于创建多个.class文件所以创建效率肯定要慢于JDK动态代理
调用效率:CGLib是要优于JDK的,因为JDK使用的反射,而CGLib直接调用
为什么Java反射性能慢效率低 我们都知道 Java 代码是需要编译才能在虚拟机里运行的,但其实 Java 的编译期是一段不确定的操作过程。因为它可能是一个前端编译器(如 Javac)把 _.java 文件编译成 _.class 文件的过程;也可能是程序运行期的即时编译器(JIT 编译器,Just In Time Compiler)把字节码文件编译成机器码的过程;还可能是静态提前编译器(AOT 编译器,Ahead Of Time Compiler)直接把 *.java 文件编译成本地机器码的过程。
其中即时编译器(JIT)在运行期的优化过程对于程序运行来说更重要,Java虚拟机在编译阶段的代码优化就在这里进行,由于反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化。因此,反射操作的性能要比非反射操作慢,因此应该避免在对性能敏感的应用程序中频繁使用Java反射来创建对象。
JDK和CGLib动态代理性能对比 关于两者之间的性能的话,网上有人对于不通版本的jdk进行测试,经过多次试验,测试结果大致是这样的,在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了,但是JDK动态代理和CGLIB动态代理的适用场景还是不一样的哈!