代理模式
代理模式是常见的设计模式之一,Java我们通常通过new一个对象然后调用其对应的方法来访问我们需要的服务。代理模式则是通过创建代理类(proxy)的方式来访问服务,代理类通常会持有一个委托类对象,代理类不会自己实现真正服务,而是通过调用委托类对象的相关方法,来提供服务,所以其实我们调用的还是委托类的服务,但是中间隔了一个代理类。这么做是有好处的,我们可以在访问服务之前或者之后加一下我们需要的操作。例如Spring的面向切面编程,我们可以在切入点之前执行一些操作,切入点之后执行一些操作。这个切入点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。
根据代理类的创建时间又可以分为:
静态代理
动态代理
两者的区别:
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
但是不管是静态代理还是动态代理,他们都有一个共同点就是:都是通过接口进行代理。委托方通过接口去寻找代理方,而代理方也是通过去实现接口来完成代理对象的创建。并且代理方都会在方法中通过引用去调用委托方。
静态代理
某个对象提供一个代理,代理角色固定,以控制对这个对象的访问。 代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。
静态代理的特点
目标角色固定
在应用程序执行前就得到目标角色
代理对象会增强目标对象的行为
有可能存在多个代理 引起"类爆炸"(缺点)
针对静态代理使用以下的例子:
委托方为People类
代理接口为ProxyInterface
静态代理类为StaticProxy
接下来我们直接看代码:
ProxyInterface代理接口:
public interface ProxyInterface {
String sing();
}
1
2
3
委托方People:
public class People implements ProxyInterface {
private String name;
public People(String name) {
this.name = name;
}
public String sing(){
System.out.println(name + "正在唱歌");
return name+"表示感谢";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
静态代理方StaticProxy
public class StaticProxy implements ProxyInterface{
//委托方引用
private People people;
public StaticProxy(People people) {
this.people = people;
}
public String sing() {
System.out.println("这里是代理对People的增强");
return people.sing();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
然后我们测试一下:
public class ProxyTest {
public static void main(String[] args) {
StaticProxy zsProxy = new StaticProxy(new People("张三"));
System.out.println(zsProxy.sing());
}
}
1
2
3
4
5
6
结果:
我们可以知道:
静态代理就是程序员在编写代码的时候就已经把代理类的源码写好了,编译后就会生成.class文件。
动态代理
相比于静态代理,动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由Java反射机制动态产生。它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象,无需程序员手动编写它的源代码。
动态代理的特点
目标对象不固定
在应用程序执行时动态创建目标对象
代理对象会增强目标对象的行为
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。然后通过反射在invoke方法中执行代理类的方法。在代理过程中,在执行代理类的方法前或者后可以执行自己的操作,这就是spring AOP的原理。
针对动态代理我们使用如下的例子:
代理接口ProxyInterface
委托方People
代理方使用Proxy.newProxyInstance方法动态生成
InvocationHandler回调处理程序的实现类DynamicProxy
其中代理接口ProxyInterface和委托方People的代码与前面一样,DynamicProxy的代码如下:
public class DynamicProxy implements InvocationHandler {
private final T target;
public DynamicProxy(T target) {
this.target = target;
}
/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用method时传入的实参
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("sing")) {
System.out.println("这里是对sing方法动态代理的增强");
}
return method.invoke(target,args);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
测试代码:
public class ProxyTest {
public static void main(String[] args) {
// StaticProxy zsProxy = new StaticProxy(new People("张三"));
// System.out.println(zsProxy.sing());
People zs = new People("张三");
ProxyInterface proxyInterface = (ProxyInterface) Proxy.newProxyInstance(
ProxyTest.class.getClassLoader(),
new Class[]{ProxyInterface.class},
new DynamicProxy<People>(zs)
);
System.out.println(proxyInterface.sing());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
理解:
我们的InvocationHandler中涉及代理对象的引用和invoke方法,我们使用newProxyInstance创建代理对象的时候,传入代理接口就是为了对接口里面的方法进行拦截,然后全部替换成执行invoke方法。
结果:
CGLIB动态代理
CGLIB 通过动态生成一个需要被代理类的子类(即被代理类作为父类),该子类重写被代理类的所有不是 final 修饰的方法,并在子类中采用方法拦截的技术拦截父类所有的方法调用,进而织入横切逻辑。此外,因为 CGLIB 采用整型变量建立了方法索引,从而可以快速的查找对应的方法代理,这比使用 JDK 动态代理更快(使用 Java 反射技术创建代理类的实例)。
在底层实现上,CGLIB 使用字节码处理框架 ASM,该框架用于转换字节码并生成新的类。但是不鼓励直接使用 ASM,因为它要求对于 JVM 的内部结构包括 class 文件的格式和 JVM 指令都很熟悉,若一旦出现错误,会导致 JVM 崩溃级别的异常。
虽然说 CGLIB 与 JDK 动态代理相比,具有更好的优势,当并不是说使用 CGLIB 会更好,这里先总结下 CGLIB 和 JDK 动态代理之间的区别:
JDK 动态代理只能对接口进行代理,不能对普通的类进行代理,这是因为 - JDK 动态代理生成的代理类,其父类是 Proxy,且 Java 不支持类的多继承。
CGLIB 能够代理接口和普通的类,但是被代理的类不能被 final 修饰,且接口中的方法不能使用 final 修饰。
JDK 动态代理使用 Java 反射技术进行操作,在生成类上更高效。
CGLIB 使用 ASM 框架直接对字节码进行修改,使用了 FastClass 的特性。在某些情况下,类的方法执行会比较高效。
我们接下来看一个使用的例子:
首先我们导入相关依赖包:
cglib
cglib
2.2.2
1
2
3
4
5
我们的委托方还是前面的Person类对象张三,代码如下:
public class CglibTry {
public static void main(String[] args) {
final People zs = new People("张三");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(People.class);
enhancer.setCallback(new MethodInterceptor() {
/**
* @param o: 代理对象
* @param method: 被代理方法
* @param params: 方法入参
* @param methodProxy: CGLIB方法
**/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (method.getName().equals("sing")) {
System.out.println("这里是cglib对sing方法的动态增强");
}
return method.invoke(zs,objects);
}
});
People cglibProxy = (People) enhancer.create();
System.out.println(cglibProxy.sing());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
结果:
当然cglib也可以代理接口,我们尝试一下:
public class CglibProxyInterfaceTry {
public static void main(String[] args) {
final People zs = new People("张三");
Enhancer enhancer = new Enhancer();
enhancer.setInterfaces(new Class[]{ProxyInterface.class});
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (method.getName().equals("sing")) {
System.out.println("这里是cglib对sing方法的动态增强");
}
return method.invoke(zs,objects);
}
});
ProxyInterface proxyInterface = (ProxyInterface) enhancer.create();
System.out.println(proxyInterface.sing());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
区别在于:
最后使用enhancer.create()创建的对象是代理接口类型的,而不是委托方的类型
接口代理使用的是setInterfaces方法而不是setSuperclass方法(当然这个不绝对,我们可以看看下面的代码)
public class CglibProxyInterfaceTry {
public static void main(String[] args) {
// final People zs = new People("张三");
// Enhancer enhancer = new Enhancer();
// enhancer.setInterfaces(new Class[]{ProxyInterface.class});
// enhancer.setCallback(new MethodInterceptor() {
// public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// if (method.getName().equals("sing")) {
// System.out.println("这里是cglib对sing方法的动态增强");
// }
// return method.invoke(zs,objects);
// }
// });
// ProxyInterface proxyInterface = (ProxyInterface) enhancer.create();
// System.out.println(proxyInterface.sing());
final People zs = new People("张三");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ProxyInterface.class);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (method.getName().equals("sing")) {
System.out.println("这里是cglib对sing方法的动态增强");
}
return method.invoke(zs,objects);
}
});
ProxyInterface proxyInterface = (ProxyInterface) enhancer.create();
System.out.println(proxyInterface.sing());
}
}
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
也就是说setSuperclass代理接口也是可以的。
JDK动态代理源码分析
我们直接来看newProxyInstance方法:
此处得到安全管理器非重点,继续往下看,我们可以看到一个getProxyClass0方法:
这个方法非常重要,就是通过它来生成的代理类。
拿到这个代理类之后,通过这个代理类的构造方法并传入InvocationHandler对象来得到一个代理对象。
接着我们来看看代理类的产生方法getProxyClass0:
我们可以看到这个地方使用了缓存。如果由实现给定接口的给定加载器定义的代理类存在,则只返回缓存副本;否则,它将通过ProxyClassFactory创建代理类
————————————————
版权声明:本文为CSDN博主「十八岁讨厌编程」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zyb18507175502/article/details/129353385