【Java】【代理模式】总结,你值得拥有!

源码已收录github 查看源码,别忘了star哦!

什么是代理模式

代理模式,一个类代表另外一个类的功能。

在生活中并不缺乏代理模式的例子,比如火车票代售点,代理火车站销售火车票,代售点本身是没有火车班次的,而是代理了火车站的功能;房产中介代理房主销售房屋,中介本身没有房屋,而是代理房主的房屋进行销售。

那么问题来了,同样的功能,为什么需要代理呢?买房需要花费很多时间和精力,可能你不愿意这样,那么你可以用代理;买房可能需要复杂的程序,可能你并不熟悉,那么你需要代理来帮你处理。卖出房屋可能需要一定的宣传,你可能也不会,那么你更需要代理。那么可以看到,代理除了不具备被代理本身的功能之外,它可以在这个功能的基础上做很多事情,那这也就是代理模式的作用:隔离调用者与被调用者,功能增强

静态代理

静态代理是指类的创建编译过程在程序运行之前已经确定。即通过手动编码的方式来进行代理,案例类图结构如下。
【Java】【代理模式】总结,你值得拥有!
在案例中,SellHouse为接口,定义销售房屋方法,而HouseMaster则为销售房屋的房主,SellProxy为销售代理,由销售代理对房主的销售进行代理并增强。
具体代码如下:

SellHouse代码

package demo.pattren.proxy.statics;

public interface SellHouse {

void sell();

}

实现类HouseMaster

package demo.pattren.proxy.statics;

//房屋主人

public class HouseMaster implements SellHouse {

@Override

public void sell() {

System.out.println("我是房屋的主人,我买掉房子");

}

}

代理类SellProxy

package demo.pattren.proxy.statics;

//销售代理

public class SellProxy implements SellHouse{

//通过注入被代理对象实现代理功能

private SellHouse sellHouse;

public SellProxy(SellHouse sellHouse){

this.sellHouse = sellHouse;

}

@Override

public void sell() {

before();

//买掉房子

sellHouse.sell();

after();

}

//前置增强

private void before(){

System.out.println("新代理房屋销售");

System.out.println("我先来做一下宣传");

System.out.println("根据实际情况合房主沟通调整一下售价");

}

//后置增强

private void after(){

System.out.println("房子卖掉了,我提取佣金");

}

}

测试类

package demo.pattren.proxy.statics;

public class Test {

public static void main(String[] args) {

SellHouse house = new SellProxy(new HouseMaster());

house.sell();

}

}

动态代理

静态代理的实现是通过程序员编码代码来完成,虽然静态代理使代码有了一定的解耦,但是非常不灵活,如果做代理,那么每个接口都需要编写代理类,造成代码冗余,下面我们来看代理模式的另外一种实现——动态代理。接口类和实现类房主保持不变,而使用另外一种代理。
动态代理类SellDynamicProxy

package demo.pattren.proxy.dynamic;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

//动态代理

public class SellDynamicProxy implements InvocationHandler {

private Object object;

public SellDynamicProxy(Object object){

this.object = object;

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

before();

//买掉房子

Object result = method.invoke(object, args);

after();

return result;

}

//前置增强

private void before(){

System.out.println("新代理房屋销售");

System.out.println("我先来做一下宣传");

System.out.println("根据实际情况合房主沟通调整一下售价");

}

//后置增强

private void after(){

System.out.println("房子卖掉了,我提取佣金");

}

}

动态代理测试类

package demo.pattren.proxy.dynamic;

import demo.pattren.proxy.statics.HouseMaster;

import demo.pattren.proxy.statics.SellHouse;

import java.io.IOException;

import java.lang.reflect.Proxy;

public class Test {

public static void main(String[] args) throws IOException {

SellHouse sellHouse = new HouseMaster();

SellHouse proxy = (SellHouse)Proxy.newProxyInstance(

SellHouse.class.getClassLoader(),

new Class[]{SellHouse.class},

new SellDynamicProxy(sellHouse));

proxy.sell();

}

}

类图结构如下。
【Java】【代理模式】总结,你值得拥有!
从代码看到,静态代理和动态代理的代理类和调用有明显的区别,首先,静态代理的代理类需要实现和业务类相同的接口,然后通过注入的形式调用业务类的功能,而动态代理则不同,动态代理则是实现InvocationHandler类,重写invoke方法来实现代理;而调用也有所不同,调用使用JDK提供的ProxynewProxyInstance方法来创建对象。

动态代理底层原理

动态代理创建的是SellHouse的一个子类,通过Debug来看一下创建的到底是什么对象。
【Java】【代理模式】总结,你值得拥有!
发现生成了一个类[email protected],那么JDK是如何去创建这样一个并不存在的Java类呢?我们通过代码入口Proxy.newProxyInstance来一探究竟。

Proxy.newProxyInstance

@CallerSensitive

public static Object newProxyInstance(ClassLoader loader,

Class<?>[] interfaces,

InvocationHandler h)

throws IllegalArgumentException

{

//关键代码

Class<?> cl = getProxyClass0(loader, intfs);

}

Proxy.getProxyClass0

private static Class<?> getProxyClass0(ClassLoader loader,

Class<?>... interfaces) {

//接口数量不能大于65535

if (interfaces.length > 65535) {

throw new IllegalArgumentException("interface limit exceeded");

}

//从缓存获取

return proxyClassCache.get(loader, interfaces);

}

WeakCache.get

public V get(K key, P parameter) {

Objects.requireNonNull(parameter);

expungeStaleEntries();

Object cacheKey = CacheKey.valueOf(key, refQueue);

ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);

//缓存为空则新建

if (valuesMap == null) {

ConcurrentMap<Object, Supplier<V>> oldValuesMap

= map.putIfAbsent(cacheKey,

valuesMap = new ConcurrentHashMap<>());

if (oldValuesMap != null) {

valuesMap = oldValuesMap;

}

}

//新建并放入缓存

Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

}

下面是创建类的关键代码

Proxy.ProxyClassFactory.apply

private static final class ProxyClassFactory

implements BiFunction<ClassLoader, Class<?>[], Class<?>>

{

//所有生成类名的前缀

private static final String proxyClassNamePrefix = "$Proxy";

//用于类名的编号

private static final AtomicLong nextUniqueNumber = new AtomicLong();

@Override

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);

for (Class<?> intf : interfaces) {

Class<?> interfaceClass = null;

try {

interfaceClass = Class.forName(intf.getName(), false, loader);

} catch (ClassNotFoundException e) {

}

if (interfaceClass != intf) {

throw new IllegalArgumentException(

intf + " is not visible from class loader");

}

if (!interfaceClass.isInterface()) {

throw new IllegalArgumentException(

interfaceClass.getName() + " is not an interface");

}

if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {

throw new IllegalArgumentException(

"repeated interface: " + interfaceClass.getName());

}

}

String proxyPkg = null; // package to define proxy class in

int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

for (Class<?> intf : interfaces) {

int flags = intf.getModifiers();

if (!Modifier.isPublic(flags)) {

accessFlags = Modifier.FINAL;

String name = intf.getName();

int n = name.lastIndexOf('.');

String pkg = ((n == -1) ? "" : name.substring(0, n + 1));

if (proxyPkg == null) {

proxyPkg = pkg;

} else if (!pkg.equals(proxyPkg)) {

throw new IllegalArgumentException(

"non-public interfaces from different packages");

}

}

}

if (proxyPkg == null) {

//如果package为空,则使用proxy包

proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";

}

long num = nextUniqueNumber.getAndIncrement();

String proxyName = proxyPkg + proxyClassNamePrefix + num;

//生成字节码数组

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(

proxyName, interfaces, accessFlags);

try {

//通过类加载器生成代理类

return defineClass0(loader, proxyName,

proxyClassFile, 0, proxyClassFile.length);

} catch (ClassFormatError e) {

throw new IllegalArgumentException(e.toString());

}

}

}

现在,代理类创建完成,接下来就是通过类创建对象了。
Proxy.newProxyInstance方法接收三个参数,第一个是类加载器,第二个是代理的接口,第三个是一个
InvocationHandler,这个参数是干什么的的? 我们再回到SellDynamicProxy类, 其实现了InvocationHandler接口,而传入的对象正是SellDynamicProxy类型。而InvocationHandler与生成的代理类也是有关系的,通过源码来一探究竟。
Proxy.newProxyInstance

 @CallerSensitive

public static Object newProxyInstance(ClassLoader loader,

Class<?>[] interfaces,

InvocationHandler h)

throws IllegalArgumentException

{

//创建class代理类对象

Class<?> cl = getProxyClass0(loader, intfs);

try {

if (sm != null) {

checkNewProxyPermission(Reflection.getCallerClass(), cl);

}

final Constructor<?> cons = cl.getConstructor(constructorParams);

final InvocationHandler ih = h;

if (!Modifier.isPublic(cl.getModifiers())) {

AccessController.doPrivileged(new PrivilegedAction<Void>() {

public Void run() {

cons.setAccessible(true);

return null;

}

});

}

//通过反射创建对象,并且InvocationHandler是构造函数的一个参数

return cons.newInstance(new Object[]{h});

//...more code

}

现在整个结构基本清楚了,首先通过Java反射与Proxy代理动态的在内存中创建class对象,并通过反射创建对象。而创建对象使用了InvocationHandler作为参数,由此可以证明动态生成的代理类有一个InvocationHandler的有参构造器。
下面通过文件流的方式,讲class字节码写入硬盘。

//生成class字节码,在源码中能够看到也有使用这个方法

byte[] $Proxy0s = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{SellHouse.class});

//将字节码写入硬盘

FileOutputStream fileOutputStream = new FileOutputStream("D://$Proxy0.class");

fileOutputStream.write($Proxy0s);

fileOutputStream.close();

然后通过反编译(如果是IDEA,直接将文件拖动到IDEA即可查看),查看生成的源码。
如果上面的代码看得比较晃眼,那么也不用去看。但是一定要清楚这样一个方法做了什么事情。

import demo.pattren.proxy.statics.SellHouse;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements SellHouse {

private static Method m1;

private static Method m2;

private static Method m3;

private static Method m0;

public $Proxy0(InvocationHandler var1) throws {

//调用Proxy的方法,为Proxy的h属性赋值

super(var1);

}

//省略代码

public final boolean equals(Object var1) {}

//省略代码

public final String toString(){}

//重写sell方法

public final void sell() throws {

try {

//调用SellDynamicProxy的sell方法,m3就是SellHouse定义的sell方法,在静态代码块中查看

super.h.invoke(this, m3, (Object[])null);

} catch (RuntimeException | Error var2) {

throw var2;

} catch (Throwable var3) {

throw new UndeclaredThrowableException(var3);

}

}

//省略代码

public final int hashCode(){}

static {

try {

m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));

m2 = Class.forName("java.lang.Object").getMethod("toString");

m3 = Class.forName("demo.pattren.proxy.statics.SellHouse").getMethod("sell");

m0 = Class.forName("java.lang.Object").getMethod("hashCode");

} catch (NoSuchMethodException var2) {

throw new NoSuchMethodError(var2.getMessage());

} catch (ClassNotFoundException var3) {

throw new NoClassDefFoundError(var3.getMessage());

}

}

}

看了这么多代码已然晕车,没关系,来画个图。
【Java】【代理模式】总结,你值得拥有!

CgLib动态代理

JDK的动态代理技术使用还是很方便的,但是也有一定的限制,那就是被代理的类需要有一个接口,当被代理类没有接口的情况下,可以选择另外一种动态代理方式——Cglib。

CgLib动态代理底层使用基于ASM的字节码技术,Cglib的代理结构非常简单,即应用Java集成机制实现代理效果。

【Java】【代理模式】总结,你值得拥有!

目标类使用上一个案例的HouseMaster,下面编写拦截器类CgLibInteceptor

package demo.pattren.proxy.cglib;

import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CgLibInteceptor implements MethodInterceptor {

//被代理的目标对象

private Object target;

public Object getInstance(final Object target) {

this.target = target;

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(this.target.getClass());

enhancer.setCallback(this);

return enhancer.create();

}

@Override

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

before();

//买掉房子

Object result = method.invoke(target, objects);

after();

return result;

}

//前置增强

private void before() {

System.out.println("新代理房屋销售");

System.out.println("我先来做一下宣传");

System.out.println("根据实际情况合房主沟通调整一下售价");

}

//后置增强

private void after() {

System.out.println("房子卖掉了,我提取佣金");

}

}

然后编写测试类:

package demo.pattren.proxy.cglib;

import demo.pattren.proxy.statics.HouseMaster;

import org.springframework.cglib.core.DebuggingClassWriter;

import java.io.IOException;

public class CgLibTest {

static{

//将cglib生产的class写入本地,然后再使用idea反编译查看,写入static即保证在运行期执行

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\ideaWorkspace\\demo2\\src\\main\\java\\demo\\pattren\\proxy\\cglib");

}

public static void main(String[] args) throws IOException {

HouseMaster sellHouse = new HouseMaster();

CgLibInteceptor cgLibProxy = new CgLibInteceptor();

HouseMaster h = (HouseMaster)cgLibProxy.getInstance(sellHouse);

h.sell();

}

}

与预期一致,通过Inteceptor类成功实现了sell()方法的前后增强功能。但是前面也提到了Cglib是通过集成来实现代理的,在测试类中,我们加入了一段static静态代码块,目的是将Cglib在运行过程中生成的代理类写入硬盘。
通过Idea的反编译,我们来查看代理类的代码,由于代码过多,仅截取关键部分。
【Java】【代理模式】总结,你值得拥有!
通过反编译的代码发现,生成的代理类确实是目标类的实现。
【Java】【代理模式】总结,你值得拥有!

另外,使用继承的方式有一个弊端,那就是对于定义了final属性的方法是无法实现代理的。

常用框架的动态代理

  1. SpringAop

Spring的面向切面编程极大的简化了我们的日常开发工作,而Spring的Aop则是应用了动态代理技术,并且支持JDK动态代理和Cglib代理两种方式,这个问题也经常出现于面试题中。

问:请解释Spring动态代理?
答:Spring动态代理有两种方式,当目标类是接口时,默认使用JDK动态代理,而没有接口的情况下使用Cglib代理。
问:他们的区别?
答:JDK动态代理使用Proxy与InvocationHandler实现,其使用反射机制生成被代理的实现类,要求目标类有实现接口。Cglib使用ASM字节码技术生成被代理类的子类,是继承关系。在JDK低版本时,JDK动态代理的生成效率较高,而运行效率较低,而Cglib的生成效率低但是运行效率高,但随着JDK的优化,在1.8时运行差距已经非常小。 参考 : https://blog.csdn.net/xlgen15...

  1. MyBatis动态代理

相信大家对Mybatis的Mapper接口 + XML的开发模式已经非常熟悉,在开发的时候,我们只需要编写Mapper接口与XML就可以实现DAO操作。 通过本篇文章的学习,相信你已经知道MyBatis的套路,其实就是使用JDK动态代理生成了Mapper接口的实现类,从而根据XML的定义完成DAO操作。

以上是 【Java】【代理模式】总结,你值得拥有! 的全部内容, 来源链接: utcz.com/a/96897.html

回到顶部