目录
前言
在Spring框架中,代理是一种用于实现AOP(Aspect-Oriented Programming,面向切面编程)和声明式事务管理的技术。Spring框架通过代理机制在运行时动态地创建代理对象,来实现对目标对象的横切关注点的管理。学习代理前可以先学习一下AOP的知识点:
为什么需要代理呢?
与其说为什么需要代理,还不如说,代理实际上有什么用,到底是什么!
从代码上入手:
第一步:假设我们有一个接口 IService :
public interface IService {
void m1();
void m2();
void m3();
}
第二步,我们有两个实现该接口的类,分别为ServiceA 和ServiceB:
public class ServiceA implements IService {
@Override
public void m1() {
System.out.println("我是ServiceA中的m1方法!");
}
@Override
public void m2() {
System.out.println("我是ServiceA中的m2方法!");
}
@Override
public void m3() {
System.out.println("我是ServiceA中的m3方法!");
}
}
public class ServiceB implements IService {
@Override
public void m1() {
System.out.println("我是ServiceB中的m1方法!");
}
@Override
public void m2() {
System.out.println("我是ServiceB中的m2方法!");
}
@Override
public void m3() {
System.out.println("我是ServiceB中的m3方法!");
}
}
此时此刻,我们可以使用多态的特性,实例化时,对应需要的实体类,并且调用方法!
测试代码:
import org.junit.Test;
public class ProxyTest {
@Test
public void m1() {
IService serviceA = new ServiceA();
IService serviceB = new ServiceB();
serviceA.m1();
serviceA.m2();
serviceA.m3();
serviceB.m1();
serviceB.m2();
serviceB.m3();
}
}
测试结果:
假设,如果你的领导要找你麻烦,执行方法时,需要记录运行的时间!那么我们该怎么办呢?
蠢方法:修改上述每一个方法,虽然有用,但是也太不靠谱了。
因此我们需要一个代理类!ServiceProxy类 使用该代理类实现接口,并且访问Iservice的实现类!
// IService的代理类
public class ServiceProxy implements IService {
//目标对象,被代理的对象
private IService target;
public ServiceProxy(IService target) {
this.target = target;
}
@Override
public void m1() {
long starTime = System.nanoTime();
this.target.m1();
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
}
@Override
public void m2() {
long starTime = System.nanoTime();
this.target.m1();
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
}
@Override
public void m3() {
long starTime = System.nanoTime();
this.target.m1();
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
}
}
在代理类的方法中,我们添加额外的功能,或许你还没有明白该代码的用处!因此给你展示一个测试类使用该代码的例子
@Test
public void serviceProxy() {
IService serviceA = new ServiceProxy(new ServiceA());//@1
IService serviceB = new ServiceProxy(new ServiceB()); //@2
serviceA.m1();
serviceA.m2();
serviceA.m3();
serviceB.m1();
serviceB.m2();
serviceB.m3();
}
我们将创建一个IService接口类型的引用变量,并指向了ServiceProxy类的实例,并且在ServiceProxy类的构造函数里传入了被代理访问的对象,因此我们不需要在原有代码上修改!直接将需要添加的功能放在ServiceProxy类代理中即可。
测试结果:
问题:代理的确很方便,不用修改原代码即可添加新功能!可是这样我岂不是得给每一个接口实现一个代理类吗,那我不得累死!
答:针对以上问题,我们提供了两种方式:JDK动态代理和CGLIB代理
JDK动态代理
JDK为实现代理提供了两种类,
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler
使用两种类虽然可以为接口创建代理类,但不能给具体的类创建代理类,也就是指向下转型。
java.lang.reflect.Proxy
该类是jdk动态代理最重要的类,我们先了解以下其中的静态方法!
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h): 这是创建动态代理对象最常用的方法。它接受三个参数:
loader
:类加载器,用于加载代理类。interfaces
:代理类要实现的接口列表。h
:实现了InvocationHandler
接口的对象,用于处理方法调用。
getProxyClass(ClassLoader loader, Class<?>... interfaces)
: 这个方法返回一个代理类的Class
对象,但不创建实例。通常很少直接使用,而是更常用的是newProxyInstance()
方法。它接受两个参数:
loader
:类加载器,用于加载代理类。interfaces
:代理类要实现的接口列表。
isProxyClass(Class<?> cl)
: 检查指定的Class
对象是否是代理类的类对象。如果是代理类,返回true
,否则返回false
。
getInvocationHandler(Object proxy)
: 返回与指定代理对象关联的InvocationHandler
。如果代理对象不是动态代理对象或者没有关联InvocationHandler
,则返回null
。
方法已经了解过了,接下来是代码阶段
方法一:
1.调用Proxy.getProxyClass方法获取代理类的Class对象
2.使用InvocationHandler接口创建代理类的处理器
3.通过代理类和InvocationHandler创建代理对象
4.上面已经创建好代理对象了,接着我们就可以使用代理对象了
1.接口IService:
public interface IService {
void m1();
void m2();
void m3();
}
注:存在实现类ServiceA和ServiceB
2.创建代理对象:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 创建 ServiceA 实例
ServiceA serviceA = new ServiceA();
// 创建 InvocationHandler 实例
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 使用反射调用 ServiceA 类的对应方法
Method serviceAMethod = ServiceA.class.getMethod(method.getName(), method.getParameterTypes());
return serviceAMethod.invoke(serviceA, args);
} catch (NoSuchMethodException e) {
System.out.println("ServiceA 类中没有对应的方法:" + method.getName());
// 可以执行一些默认逻辑或者抛出异常
return null;
}
}
};
// 获取接口对应的代理类
Class<IService> proxyClass = (Class<IService>) Proxy.getProxyClass(IService.class.getClassLoader(), IService.class);
// 创建代理实例
IService proxyService = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
// 调用代理的方法
proxyService.m1();
proxyService.m2();
proxyService.m3();
}
}
方法二:
1.使用InvocationHandler接口创建代理类的处理器
2.使用Proxy类的静态方法newProxyInstance直接创建代理对象
3.使用代理对象
更简便创建代理对象代码:
@Test
public void m2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 创建 ServiceA 实例
ServiceA serviceA = new ServiceA();
// 1. 创建代理类的处理器
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 使用反射调用 ServiceA 类的对应方法
Method serviceAMethod = ServiceA.class.getMethod(method.getName(), method.getParameterTypes());
return serviceAMethod.invoke(serviceA, args);
} catch (NoSuchMethodException e) {
System.out.println("ServiceA 类中没有对应的方法:" + method.getName());
// 可以执行一些默认逻辑或者抛出异常
return null;
}
}
};
// 2. 创建代理实例
IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(), new Class[]{IService.class}, invocationHandler);
// 3. 调用代理的方法
proxyService.m1();
proxyService.m2();
proxyService.m3();
}
注:如果不太明白这两段代码,建议学习一下Java的反射!
通用代理对象实现
通过两个方法,我们可以实现 一个简便通用的代理对象类!
我们需要自定义一个类实现InvocationHandler接口。
创建 InvocationHandler接口
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class CostTimeInvocationHandler implements InvocationHandler {
private Object target;
public CostTimeInvocationHandler(Object target) {
this.target = target;
}
// 该重写方法自动调用 属性:代理对象本身 方法 方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long starTime = System.nanoTime();
Object result = method.invoke(this.target, args);
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
return result;
}
/**
* 用来创建targetInterface接口的代理对象
*
* @param target 需要被代理的对象
* @param targetInterface 被代理的接口
* @param <T>
* @return
*/
public static <T> T createProxy(Object target, Class<T> targetInterface) {
if (!targetInterface.isInterface()) {
throw new IllegalStateException("targetInterface必须是接口类型!");
} else if (!targetInterface.isAssignableFrom(target.getClass())) {
throw new IllegalStateException("target必须是targetInterface接口的实现类!");
}
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CostTimeInvocationHandler(target));
}
}
上面主要是createProxy方法用来创建代理对象,2个参数:
target:目标对象,需要实现targetInterface接口
targetInterface:需要创建代理的接口
invoke方法中通过method.invoke(this.target, args)调用目标方法,然后统计方法的耗时。
测试代码:
@Test
public void costTimeProxy() {
IService serviceA = CostTimeInvocationHandler.createProxy(new ServiceA(), IService.class);
IService serviceB = CostTimeInvocationHandler.createProxy(new ServiceB(), IService.class);
serviceA.m1();
serviceA.m2();
serviceA.m3();
serviceB.m1();
serviceB.m2();
serviceB.m3();
}
测试结果:
而如果我们还有其他接口和实现类也想统计耗时功能,我们则不需要创建新的代理类,案例如下:
1.创建一个 IUserService 接口
public interface IUserService {
/**
* 插入用户信息
* @param name
*/
void insert(String name);
}
2.IUserService接口实现类:
public class UserService implements IUserService {
@Override
public void insert(String name) {
System.out.println(String.format("用户[name:%s]插入成功!", name));
}
}
测试代码:
@Test
public void userService() {
IUserService userService = CostTimeInvocationHandler.createProxy(new UserService(), IUserService.class);
userService.insert("路人甲Java");
}
注:
-
jdk中的Proxy只能为接口生成代理类,如果你想给某个类创建代理类,那么Proxy是无能为力的,此时需要我们用到下面要说的cglib了。
-
Proxy类中提供的几个常用的静态方法大家需要掌握
-
通过Proxy创建代理对象,当调用代理对象任意方法时候,会被InvocationHandler接口中的invoke方法进行处理,这个接口内容是关键
CGLIB代理
1.什么是CGLIB呢?
CGLIB(Code Generation Library)是一个强大的代码生成库,它可以在运行时生成字节码,用于创建类的动态代理对象。与Java标准库中的动态代理相比,CGLIB更加灵活,因为它可以代理没有实现接口的类。
CGLIB通过继承目标类来创建代理对象,然后重写目标类中的方法,实现代理逻辑。这种方式与Java标准库中的动态代理使用代理接口不同,因此CGLIB可以代理那些没有实现接口的类。
使用CGLIB创建代理对象的过程相对复杂,因为它涉及到字节码的生成和类加载等底层操作。但是,它提供了更大的灵活性,可以代理更多类型的类,并且性能相对较高,因为它不需要通过反射来调用目标方法。
CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式足够的了解
2.与JDK代理有什么区别呢?
CGLIB代理 | JDK代理 | |
接口代理支持 | 可以代理没有实现接口的类 | 代理实现了接口的类,因为它是基于接口的 |
性能 | 通过继承目标类来创建代理对象,因此在创建代理对象时会涉及到类的继承和方法重写,可能会导致创建代理对象的性能略低于JDK动态代理。 | 利用了Java标准库中的反射机制,相对而言创建代理对象的性能可能更高一些。 |
依赖 | 需要额外的库依赖 | Java标准库的一部分,因此无需引入额外的依赖 |
兼容性 | JDK动态代理是Java标准库的一部分,因此在Java应用程序中使用广泛,且具有良好的兼容性。 | 不是Java标准库的一部分,因此在一些特定的环境中可能需要注意兼容性和依赖管理。 |
虽然CGLIB提供了更多的灵活性和功能,但JDK动态代理仍然是Java开发中常用的一种代理方式,特别是在需要代理接口实现的情况下。选择使用哪种代理方式取决于具体的需求和场景。
话语一大堆,不如实际代码来的可观!得先建立Spring项目
3.项目建立
1.引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
POM文件中引入依赖,下载需要等待时间
2.代码示例:
public class SampleClass {
public void test(){
System.out.println("hello world");
}
public static void main(String[] args) {
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
// 设置父类(目标类)
enhancer.setSuperclass(SampleClass.class);
// 设置回调处理器
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before method run...");
//调用代理对象的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("after method run...");
return result;
}
});
// 创建代理对象
SampleClass sample = (SampleClass) enhancer.create();
// 调用代理对象的方法
sample.test();
}
}
在mian函数中,我们通过一个Enhancer和一个MethodInterceptor来实现对方法的拦截
4.CGLIB常用API
Enhancer类
CGLIB中最常用的一个类,Enhancer既能够代理普通的class,也能够代理接口
Enhancer可创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,
代码示例:
先创建一个普通类:
public class Demo {
public String test(){
return "我是普通方法";
}
public final String test2(){
return "我是final方法";
}
}
以这个类为基础,测试:
@Test
void testCGLIB(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Demo.class);
enhancer.setCallback(new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "我爱cglib";
}
});
Demo demo = (Demo) enhancer.create();
System.out.println(demo.test());
System.out.println(demo.test2());
}
注:test方法被拦截,test2方法没有被拦截
解释:使用 setCallback()
方法设置了一个 FixedValue
类型的,回调重写了 loadObject()
方法。在这个方法中,我们指定了固定的返回值 "我爱cglib",
也就是指代理对象方法被调用的时候,只会返回一个 "我爱cglib" ,但是无法拦截代理对象的final方法。
但是假设我们想要特定方法进行拦截,怎么操作呢?
普通类:
public class Demo {
public String test(){
return "我是普通方法1";
}
public String test2(){
return "我是普通方法2";
}
}
测试类方法:
@Test
public void testCallbackFilter() throws Exception{
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Demo.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (!method.getName().equals("test2")) {
// 放行 非test()2 方法,直接调用原始方法
System.out.println("Intercepted method: " + method.getName() + " 可以运行");
return proxy.invokeSuper(obj, args);
} else {
// 阻拦test2方法
System.out.println("Intercepted method: " + method.getName() + " - 不可以运行");
// 这里可以根据需要返回一个默认值或者抛出异常
return null; // 返回 null,表示阻拦方法调用
}
}
});
Demo d = (Demo) enhancer.create();
d.test();
d.test3();
}
例如我们想要阻拦test2方法,则如此!
其实还有很多其他类可以使用,但是我们主要了解最常用的 Enhancer类
其他类:
ImmutableBean类
不可变的Bean,ImmutableBean允许创建一个原来对象的包装类,这个包装类是不可变的,任何改变底层对象的包装类操作都会抛出IllegalStateException。但是我们可以通过直接操作底层对象来改变包装类对象。
Bean generator类
cglib提供的一个操作bean的工具,使用它能够在运行时动态的创建一个bean。
Bean Copier类
cglib提供的能够从一个bean复制到另一个bean中,而且其还提供了一个转换器,用来在转换的时候对bean的属性进行操作。
BulkBean类
相比于BeanCopier,BulkBean将copy的动作拆分为getPropertyValues和setPropertyValues两个方法,允许自定义处理属性
注:CGLIB的大部分类是直接对Java字节码进行操作!
标签:IService,Spring,void,System,模式,代理,方法,public From: https://blog.csdn.net/m0_74097410/article/details/137289908