点赞的靓仔,你最帅哦!
源码已收录github 查看源码,别忘了star哦!
什么是代理模式
代理模式,一个类代表另外一个类的功能。
在生活中并不缺乏代理模式的例子,比如火车票代售点,代理火车站销售火车票,代售点本身是没有火车班次的,而是代理了火车站的功能;房产中介代理房主销售房屋,中介本身没有房屋,而是代理房主的房屋进行销售。
那么问题来了,同样的功能,为什么需要代理呢?买房需要花费很多时间和精力,可能你不愿意这样,那么你可以用代理;买房可能需要复杂的程序,可能你并不熟悉,那么你需要代理来帮你处理。卖出房屋可能需要一定的宣传,你可能也不会,那么你更需要代理。那么可以看到,代理除了不具备被代理本身的功能之外,它可以在这个功能的基础上做很多事情,那这也就是代理模式的作用:隔离调用者与被调用者,功能增强
静态代理
静态代理是指类的创建编译过程在程序运行之前已经确定。即通过手动编码的方式来进行代理,案例类图结构如下。
在案例中,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();
}
}
类图结构如下。
从代码看到,静态代理和动态代理的代理类和调用有明显的区别,首先,静态代理的代理类需要实现和业务类相同的接口,然后通过注入的形式调用业务类的功能,而动态代理则不同,动态代理则是实现InvocationHandler类,重写invoke方法来实现代理;而调用也有所不同,调用使用JDK提供的Proxy的newProxyInstance方法来创建对象。
动态代理底层原理
动态代理创建的是SellHouse的一个子类,通过Debug来看一下创建的到底是什么对象。
发现生成了一个类$Proxy0@572,那么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
下面是创建类的关键代码
Proxy.ProxyClassFactory.apply
private static final class ProxyClassFactory
implements BiFunction[], Class>>
{
//所有生成类名的前缀
private static final String proxyClassNamePrefix = "$Proxy";
//用于类名的编号
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class> apply(ClassLoader loader, Class>[] interfaces) {
Map, 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() {
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());
}
}
}
看了这么多代码已然晕车,没关系,来画个图。
CgLib动态代理
JDK的动态代理技术使用还是很方便的,但是也有一定的限制,那就是被代理的类需要有一个接口,当被代理类没有接口的情况下,可以选择另外一种动态代理方式——Cglib。
CgLib动态代理底层使用基于ASM的字节码技术,Cglib的代理结构非常简单,即应用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的反编译,我们来查看代理类的代码,由于代码过多,仅截取关键部分。
通过反编译的代码发现,生成的代理类确实是目标类的实现。
另外,使用继承的方式有一个弊端,那就是对于定义了final属性的方法是无法实现代理的。
常用框架的动态代理
- 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/xlgen157387/article/details/82497594
- MyBatis动态代理
相信大家对Mybatis的Mapper接口 + XML的开发模式已经非常熟悉,在开发的时候,我们只需要编写Mapper接口与XML就可以实现DAO操作。 通过本篇文章的学习,相信你已经知道MyBatis的套路,其实就是使用JDK动态代理生成了Mapper接口的实现类,从而根据XML的定义完成DAO操作。