Java代理
代理模式
代理模式的意义
代理(Proxy)模式是一种结构型设计模式,提供了对目标对象另外的访问方式: 即通过代理对象访问目标对象。
这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作, 即扩展目标对象的功能。
这里使用到编程中的一个思想: 不要随意去修改别人已经写好的代码或者方法,如果需要修改, 可以通过代理的方式来扩展该方法。
代理模式的形式
代理模式大致有三种角色:
- 真实类(Real Subject):,也就是被代理类、委托类。用来真正完成业务服务功能;
- 代理类(Proxy):将自身的请求用 Real Subject 对应的功能来实现, 代理类对象并不真正的去实现其业务功能;
- 主题(Subject):定义 RealSubject 和 Proxy 角色都应该实现的接口。
Java代理实现
- 静态代理:需要代理类和目标类都实现接口的方法,从而达到代理增强其功能。
-
动态代理(JDK代理,接口代理):需要代理类实现某个接口,
使用
Proxy.newProxyInstance
方法生成代理类,并实现InvocationHandler
中的invoke()
方法,实现增强功能。 -
Cglib动态代理(在内存中动态的创建目标对象的子类):无需代理类实现接口,
使用Cblib中的Enhancer来生成代理对象子类,
并实现
MethodInterceptor
中的intercept
方法,在此方法中可以实现增强功能。
静态代理
静态代理需要先定义接口,被代理对象与代理对象一起实现相同的接口, 然后通过调用相同的方法来调用目标对象的方法。
可以看见,代理类无非是在调用委托类方法的前后增加了一些操作。 委托类的不同,也就导致代理类的不同。
例子
某公司生产电视机,在当地销售需要找到一个代理销售商。 那么客户需要购买电视机的时候,就直接通过代理商购买就可以。
电视机:
public class TV { private String name; // 名称 private String address; // 生产地 private Boolean functional; // 能否正常工作 public TV(String name, String address, Boolean functional) { super(); this.name = name; this.address = address; this.functional = functional; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public Boolean getFunctional() { return functional; } public void setFunctional(Boolean functional) { this.functional = functional; } @Override public String toString() { return "TV [name=" + name + ", address=" + address + ", functional=" + functional + "]"; } }
创建公司接口:
public interface TVCompany { public TV produceTV(); // 生产电视机 }
公司的工厂生产电视机:
public class TVFactoryXiaomi implements TVCompany { @Override public TV produceTV() { System.out.println("TV factory produce TV..."); return new TV("小米电视机", "合肥", true); } }
代理商去下单拿货(静态代理类):
public class TVProxy implements TVCompany{ private TVCompany tvCompany; public TVProxy() { } @Override public TV produceTV() { System.out.println("TV proxy get order .... "); System.out.println("TV proxy start produce .... "); if (Objects.isNull(tvCompany)) { System.out.println("machine proxy find factory .... "); tvCompany = new TVFactoryXiaomi(); } return tvCompany.produceTV(); } }
消费者通过代理商拿货(代理类的使用):
public class TVConsumer { public static void main(String[] args) { TVProxy tvProxy = new TVProxy(); TV tv = tvProxy.produceTV(); System.out.println(tv); } }
输出结果:
TV proxy get order .... TV proxy start produce .... machine proxy find factory .... TV factory produce TV... TV [name=小米电视机, address=合肥, functional=true]
小结
- 优点:静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。
- 缺点:静态代理实现了目标对象的所有方法,一旦目标接口增加方法, 代理对象和目标对象都要进行相应的修改,增加维护成本。
如何解决静态代理中的缺点呢?答案是可以使用动态代理方式
动态代理
动态代理具有如下特点:
- JDK动态代理对象不需要实现接口,只有目标对象需要实现接口。
- 实现基于接口的动态代理需要利用JDK中的API,在JVM内存中动态的构建Proxy对象。
-
需要使用到
java.lang.reflect.Proxy
,和其newProxyInstance
方法, 但是该方法需要接收三个参数。
@CallerSensitive public static Object newProxyInstance( ClassLoader loader, Class<?>[] interfaces, invocationHandler h) throws IllegalArgumentException
注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
-
ClassLoader
:指定当前目标对象使用类加载器,获取加载器的方法是固定的。 -
Class<?>[]
:目标对象实现的接口的类型,使用泛型方式确认类型。 -
InvocationHandler
:事件处理,执行目标对象的方法时,会触发事件处理器的方法, 会把当前执行目标对象的方法作为参数传入。
例子
有一天公司增加了业务,出售的商品越来越多,售后也需要更上。 但是公司发现原来的代理商,还要再培训才能完成全部的业务, 于是就找了另外的动态代理商B 。
代理商B 承诺无缝对接公司所有的业务,不管新增什么业务, 均不需要额外的培训即可完成。
公司增加了维修业务:
public interface TVCompany { public TV produceTV(); // 生产电视机 public TV repair(TV tv); // 维修电视机 }
工厂也得把维修业务搞起来:
public class TVFactory implements TVCompany { @Override public TV produceTV() { System.out.println("TV factory produce TV..."); return new TV("小米电视机","合肥"); } @Override public TV repair(TV tv) { tv.setFunctional(true); System.out.println("tv is repair finished..."); return tv; } }
B代理商全面代理公司所有的业务。使用Proxy.newProxyInstance
方法生成代理对象,
实现InvocationHandler
中的invoke
方法,
在invoke
方法中通过反射调用代理类的方法,并提供增强方法。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class TVProxyFactory { private Object target; public TVProxyFactory(Object o) { this.target = o; } public Object getProxy() { ClassLoader loader = this.getClass().getClassLoader(); Class<?>[] interfaces = target.getClass().getInterfaces(); InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("TV proxy find factory for tv.... "); Object invoke = method.invoke(target, args); return invoke; } }; return Proxy.newProxyInstance(loader, interfaces, handler); } }
购买、维修这两个业务 B代理就可以直接搞定了。后面公司再增加业务, 不用再修改B代理的代码:
public class TVConsumer { public static void main(String[] args) { TVCompany target = new TVFactoryXiaomi(); TVCompany tvCompany = (TVCompany) new TVProxyFactory(target).getProxy(); TV tv = tvCompany.produceTV(); tvCompany.repair(tv); } }
输出结果:
TV proxy find factory for tv.... TV factory produce TV... TV proxy find factory for tv.... tv is repair finished... Process finished with exit code 0
小结
- 代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。
-
动态代理的方式中,所有的函数调用最终都会经过
invoke
函数的转发, 因此我们就可以在这里做一些自己想做的操作, 比如日志系统、事务、拦截器、权限控制等。
JDK动态代理有一个最致命的问题是它只能代理实现了某个接口的实现类, 并且代理类也只能代理接口中实现的方法,要是实现类中有自己私有的方法, 而接口中没有的话,该方法不能进行代理调用。
怎么解决这个问题呢?我们可以用 CGLIB 动态代理机制。
Cglib代理
静态代理和JDK代理都需要某个对象实现一个接口,有时候代理对象只是一个单独对象, 此时可以使用Cglib代理。
Cglib代理可以称为子类代理,是在内存中构建一个子类对象, 从而实现对目标对象功能的扩展。
Cglib通过Enhancer来生成代理类,通过实现MethodInterceptor
接口,
并实现其中的intercept
方法,在此方法中可以添加增强方法,
并可以利用反射Method
或者MethodProxy
继承类来调用原方法。
例子
C代理商不仅想代理公司,而且还想代理多个工厂的产品。
看到B代理商承接了公司(接口)的多种业务,那么此时C代理商又从中发现新的商机, B只能代理某个公司的产品,而我不仅想要代理公司产品,而且对接不同的工厂, 拿货渠道更广,赚钱更爽快。于是Cglib就用上了。
代码示例:
import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class TVProxyCglib implements MethodInterceptor { // 给目标对象创建一个代理对象 public Object getProxyInstance(// @SuppressWarnings("rawtypes") Class clazz) // { Enhancer enhancer = new Enhancer(); // 1.工具类 enhancer.setSuperclass(clazz); // 2.设置父类 enhancer.setCallback(this); // 3.设置回调函数 return enhancer.create(); // 4.创建子类(代理对象) } @Override public Object intercept(// Object o, Method method, Object[] objects, MethodProxy methodProxy// ) throws Throwable // { System.out.println("TVProxyFactory enhancement....."); Object object = methodProxy.invokeSuper(o, objects); return object; } }
新代理的B工厂
public class TVFactoryHuawei { public TV produceHuaweiTV() { System.out.println("华为TV factory produce TV..."); return new TV("华为电视机", "合肥", true); } public TV repairHuaweiTV(TV tv) { tv.setFunctional(true); System.out.println("华为电视 is repair finished..."); return tv; } }
C代理可以直接和公司合作,也可以和工厂打交道。并且可以代理任何工厂的产品。
public class TVConsumer { public static void main(String[] args) { TVProxyCglib tvProxy = new TVProxyCglib(); // ==================================== TVCompany tvCompany = (TVCompany) // tvProxy.getProxyInstance(TVFactoryXiaomi.class); TV xiaomiTv = tvCompany.produceTV(); tvCompany.repair(xiaomiTv); System.out.println("=============================="); TVFactoryHuawei tvFactoryB = (TVFactoryHuawei) // tvProxy.getProxyInstance(TVFactoryHuawei.class); TV huaweiTV = tvFactoryB.produceHuaweiTV(); tvFactoryB.repairHuaweiTV(huaweiTV); } }
输出结果:
TVProxyFactory enhancement..... TV factory produce TV... TVProxyFactory enhancement..... tv is repair finished... ============================== TVProxyFactory enhancement..... 华为TV factory produce TV... TVProxyFactory enhancement..... 华为电视 is repair finished...
Spring中AOP使用代理
Spring中AOP的实现有JDK和Cglib两种,在org.springframework.aop.framework
包中
有接口AopProxy
的三个实现:
-
JdkDynamicAopProxy
:如果目标对象需要实现接口,则使用JDK代理。 -
CglibAopProxy
:如果目标对象不需要实现接口,就用这个Cglib代理。 -
ObjenesisCglibAopProxy
JDK 9的反射限制
JDK9以上很多库都有这种非法反射访问的警告,比如protostuff和Cglib都会报警:
Illegal reflective access by net.sf.cglib.core.ReflectUtils
解决方法两个:
- JDK降级到JDK8
- 添加JVM参数
JDK9以上模块不能使用反射去访问非公有的成员/成员方法以及构造方法,
除非模块标识为opens
去允许反射访问。
旧JDK制作的库(JDK8及以下)运行在JDK9上会自动被标识为未命名模块,
为了处理该警告,JDK9以上提出了一个新的JVM参数:--illegal-access
。
该参数有四个可选值:
-
permit
:默认值,允许通过反射访问,因此会提示像上面一样的警告, 这个是首次非法访问警告,后续不警告 -
warn
:每次非法访问都会警告 -
debug
:在warn的基础上加入了类似e.printStackTrace()
的功能 -
deny
:禁止所有的非法访问除了使用特别的命令行参数排除的模块, 比如使用--add-opens
排除某些模块使其能够通过非法反射访问
因此解决的办法很简单:
-
首先将
--illegal-access
设置为debug
,找到哪些代码里有非法访问。 -
然后将
--illegal-access
设置为deny
, 并把有非法访问的模块加到--add-opens
里即可。
例如对于以下报错,可以看到来源在CGlib里的
net.sf.cglib.core.ReflectUtils$1.run(ReflectUtils.java:61)
:
WARNING: Illegal reflective access by net.sf.cglib.core.ReflectUtils$1 (file:/D:/Users/qwshan/.m2/repository/cglib/cglib/3.2.12/cglib-3.2.12.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) at net.sf.cglib.core.ReflectUtils$1.run(ReflectUtils.java:61) at java.base/java.security.AccessController.doPrivileged(Native Method) at net.sf.cglib.core.ReflectUtils.<clinit>(ReflectUtils.java:52) at net.sf.cglib.core.KeyFactory$Generator.generateClass(KeyFactory.java:243) at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:332) at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:96) at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:94) at net.sf.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61) at net.sf.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:119) at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294) at net.sf.cglib.core.KeyFactory$Generator.create(KeyFactory.java:221) at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:174) at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:153) at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:73) at jade.proxy.TVProxyCglib.getProxyInstance(TVProxyCglib.java:15) at jade.proxy.TVConsumer.main(TVConsumer.java:11)
打开源代码可以看到因为这句调用了defineClass.setAccessible(true)
:
Class loader = Class.forName("java.lang.ClassLoader"); // JVM crash w/o this Method defineClass = loader.getDeclaredMethod("defineClass", new Class[]{ String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class }); defineClass.setAccessible(true); return defineClass;
这里是非法访问了JDK基本模块的代码Medhod.setAccessible(boolean)
,
Mehtod
类属于包java.lang.reflect
,java.lang.reflect
又属于java.base
模块
(JDK9新增了模块概念)
所以要把java.base/java.lang.reflect
添加到JVM参数--add-opens
中去:
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED
如果还是有错,可以扩大范围到上一级的包java.lang
:
--add-opens=java.base/java.lang=ALL-UNNAMED
如果没有错误,可以把--illegal-access
设置为deny
。
如果是用Maven运行,参数也是加上:
--illegal-access=deny --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
如果是Gradle,添加在运行参数里是没有用的,要加在build.gradle
里:
test { useJUnitPlatform() jvmArgs('--illegal-access=deny') jvmArgs('--add-opens', 'java.base/java.lang=ALL-UNNAMED') }