静态代理和动态代理
什么是代理?
举个例子来说,比如我们想去某个商店买个计算机,我们可以选择自己去商店去购买计算机(自己来做这件事情),也可以找别人代替我们去买计算机(别人代自己做这件事情),此时别人充当的就是代理对象,我们就是目标对象。
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
静态代理
class Target{
public void methodA(){
System.out.println("this is methodA");
}
public void methodB(){
System.out.println("this is methodB");
}
}
如果一个需求为Target类中的每个方法执行前后添加打印日志,我们可以怎么做?
直接修改Target类中每个方法,在方法执行前后添加日志,代码如下:
class Target{
public void methodA(){
System.out.println("方法执行前");
System.out.println("this is methodA");
System.out.println("方法执行后");
}
public void methodB(){
System.out.println("方法执行前");
System.out.println("this is methodB");
System.out.println("方法执行后");
}
}
上述方式可以满足我们的需求,但是具有很多缺点:
- 不符合开闭原则,即对修改关闭、扩展开放,每当有新需求的时候都需要修改源代码
- 当Target类中有成百上千的方法时,需要编写很多代码,而且代码重复性很高
- 当有很多个Target类需要添加日志时,每个类都需要进行修改
下面我们使用静态代理的方式来完成打印日志的需求,首先我们需要把Target类先抽象为接口
//Target接口
interface Target{
void methodA();
void methodB();
}
//Target实现类
class TargetImpl implements Target{
public void methodA(){
System.out.println("this is methodA");
}
public void methodB(){
System.out.println("this is methodB");
}
}
//Target静态代理类
class StaticProxyTarget implements Target{
//代理对象内部维护一个目标对象的引用
private Target target;
//构造方法传入目标对象
public StaticProxyTarget(Target target){
this.target = target;
}
// 调用目标对象的方法并在其前后打印日志
@Override
public void methodA(){
System.out.println("方法执行前");
target.methodA();
System.out.println("方法执行后");
}
@Override
public void methodB(){
System.out.println("方法执行前");
target.methodB();
System.out.println("方法执行后");
}
}
上面就是修改之后的代码,改后我们可以调用方式如下
public class Main{
public static void main(String[] args){
Target target = new StaticProxyTarget(new TargetImpl());
target.methodA();
target.methodB();
}
}
静态代理的方式仅仅解决了上述缺点的第一条
- 不符合开闭原则,即对修改关闭、扩展开放,每当有新需求的时候都需要修改源代码 (√)
- 当Target类中有成百上千的方法时,需要编写很多代码,而且代码重复性很高(×)
- 当有很多个Target类需要添加日志时,每个类都需要进行修改(×)
jdk源码使用静态代理的场景
还记得我们怎么样才能把一个线程不安全的集合转成线程安全的吗?
也就是调用jdk提供给我们的集合工具类Collections中的synchronizedMap()、synchronizedList()、synchronizedCollection()方法
这些方法比如synchronizedMap()也就是返回了一个新的Map实现类(代理类,线程安全),代理类内部有个Map引用,当我们把目标Map传进代理类中,代理类的Map引用就指向了目标Map,我们调用代理类的put,get等方法,其实最终就是调用了目标类的put,get方法。
源码
Map<String,Object> map = new HashMap<>();
Map<String, Object> synchronizedMap = Collections.synchronizedMap(map);
//返回线程安全的代理类
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}
// 代理类部分源码,实现了Map接口
private static class SynchronizedMap<K,V>
implements Map<K,V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L;
// 目标对象
private final Map<K,V> m; // Backing Map
// 锁对象
final Object mutex; // Object on which to synchronize
//构造器,Map引用指向目标对象,锁默认是代理对象
SynchronizedMap(Map<K,V> m) {
this.m = Objects.requireNonNull(m);
mutex = this;
}
SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
}
// 下面代码就是重写了Map中的方法,并用同步代码块保证线程安全,即同一时刻只能有一个线程访问代理Map
public int size() {
synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
}
public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}
}
public void putAll(Map<? extends K, ? extends V> map) {
synchronized (mutex) {m.putAll(map);}
}
public void clear() {
synchronized (mutex) {m.clear();}
}
...
}
其它集合类比如List,Set转成线程安全的方式和上述一样,就是前面介绍的静态代理,就不在一一列举了。
静态代理的问题
静态代理类需要我们手动编写,代理类需要和目标类实现统一接口,代理类内部需要维护一个接口引用,换言之,一个静态代理类只能代理一个目标类,当有很多类需要代理时,依然需要编写很多代理类。那么有没有一个可以为我们生成代理类的方式呢?下面我们介绍的就是jdk动态代理,可以在运行中为我们生成一个代理类。
jdk动态代理
class对象
讲jdk动态代理之前,我们可以先了解一下java的class对象,在java中其实有两种对象
- 实例对象:也就是我们最常使用的,直接new出来的对象
- class对象:class对象是用来描述java类的,当一个类被编译成字节码文件,其字节码被加载进jvm之后,jvm会为这个字节码文件生成一个唯一的class对象,这个class对象就是为了描述这个类运行时的类型信息
如何获取class对象
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
private static final int ANNOTATION= 0x00002000;
private static final int ENUM = 0x00004000;
private static final int SYNTHETIC = 0x00001000;
private static native void registerNatives();
static {
registerNatives();
}
// 私有构造器
private Class(ClassLoader loader) {
classLoader = loader;
}
//后续代码省略
....
}
我们从java Class类源码可以看到该类的构造器为私有的,换句话说,我们不能通过 Class clazz = new Class();直接new的方式获取Class对象
获取Class对象的三种方法
- Class.forName(“com.xxx.Target”) 通过Class.forName的方式获取
- Target.class 直接通过类名加.class方式获取
- target.getClass() 通过实例对象的getClass()方法获取
接口class对象和类class对象的区别
public class Main {
public static void printMethod(Class clazz){
//获取class对象的所有方法
Method[] methods = clazz.getMethods();
for(Method method : methods){
//获取该方法的所有参数
Class<?>[] parameterTypes = method.getParameterTypes();
StringBuilder builder = new StringBuilder();
builder.append(method.getName()).append("(");
for(Class clazz1 : parameterTypes){
builder.append(clazz1.getName()).append(",");
}
if(parameterTypes!=null && parameterTypes.length!=0){
builder.deleteCharAt(builder.length()-1);
}
builder.append(")");
System.out.println(builder);
}
}
public static void printConstructor(Class clazz){
//获取class对象的所有构造器
Constructor[] constructors = clazz.getConstructors();
for(Constructor constructor : constructors){
//获取构造器的所有参数
Class[] parameterTypes = constructor.getParameterTypes();
StringBuilder sb = new StringBuilder(constructor.getName());
sb.append("(");
for(Class clazz1 : parameterTypes){
sb.append(clazz1.getName()).append(",");
}
if(parameterTypes!=null && parameterTypes.length!=0)
sb.deleteCharAt(sb.length()-1);
sb.append(")");
System.out.println(sb);
}
}
public static void main(String[] args) throws Exception {
System.out.println("接口class对象的构造器");
printConstructor(Target.class);
System.out.println("实现类class对象的构造器");
printConstructor(TargetImpl.class);
System.out.println("接口class对象的方法");
printMethod(Target.class);
System.out.println("实现类class对象的方法");
printMethod(TargetImpl.class);
}
}
//输出结果
接口class对象的构造器
实现类class对象的构造器
com.company.test.TargetImpl()
接口class对象的方法
methodA()
methodB()
实现类class对象的方法
methodA()
methodB()
wait()
wait(long,int)
wait(long)
equals(java.lang.Object)
toString()
hashCode()
getClass()
notify()
notifyAll()
从结果中我们可以看出接口和实现类的方法一样(实现类中除了methodA和methodB,其余方法都是继承于Object类),接口中没有构造器,而实现类中有构造器
获取代理class对象
猜想:当接口class对象有构造器之后是不是可以实例化了?我们是不是可以通过某种手段给接口class对象中添加一个构造器。
java提供了给我们这样一个类Proxy,其中有一个getProxyClass()方法,可以使我们获取到一个新的class对象,该class对象不仅具有接口中的方法,还有一个构造器
Class<?> proxyClass = Proxy.getProxyClass(Target.class.getClassLoader(), Target.class);
System.out.println("通过接口产生的新class对象的构造器");
printConstructor(proxyClass);
System.out.println("通过接口产生的新class对象的方法");
printMethod(proxyClass);
//输出结果
通过接口产生的新class对象的构造器
com.company.test.$Proxy0(java.lang.reflect.InvocationHandler)
通过接口产生的新class对象的方法
equals(java.lang.Object)
toString()
hashCode()
methodA()
methodB()
isProxyClass(java.lang.Class)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
wait()
wait(long,int)
wait(long)
getClass()
notify()
notifyAll()
通过代理class对象获取构造器并实例化代理对象
从结果中,我们可以看到新的class对象(代理class对象)中有个参数为InvocationHandler的构造器,下面我们就要通过该构造器创建一个对象,代码如下
Class<?> proxyClass = Proxy.getProxyClass(Target.class.getClassLoader(), Target.class);
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
Target target = (Target) constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
其实InvocationHandler是个借口,其中有个invoke方法,其实每当我们调用代理对象的任一方法时,最终都会走到invoke方法中,下面我来介绍一下该方法的参数
- proxy:代理对象本身,不能调用,会无限递归,最终会stackOverFlow
- method:本次调用的方法,当使用代理对象调用methodA时,该参数就是methodA
- args:调用方法所传入的参数
现在还有一个问题就是,InvocationHandler内部缺少目标对象,当InvocationHandler内部有了目标对象之后,我们在invoke方法中调用目标对象的方法,并在调用方法前后打印日志,是不是就满足了我们的需求。现在我们需要做的就是在InvocationHandler内部维护一个目标对象,具体来说,我们实现该接口,并在实现类中添加一个Target引用,在构造器中传入目标对象,代码如下
//原来的接口新加了一个methodC
interface Target{
void methodA();
void methodB();
int methodC(int a,int b);
}
class MyInvocationHandler implements InvocationHandler{
private Target target;
public MyInvocationHandler(Target target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前");
Object o = method.invoke(target, args);
System.out.println("方法执行后");
return o;
}
}
public class Main {
public static void main(String[] args) throws Exception {
Class<?> proxyClass = Proxy.getProxyClass(Target.class.getClassLoader(), Target.class);
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
Target target = (Target) constructor.newInstance(new MyInvocationHandler(new TargetImpl()));
target.methodA();
target.methodB();
System.out.println(target.methodC(1,2));
}
}
//执行结果
方法执行前
this is methodA
方法执行后
方法执行前
this is methodB
方法执行后
方法执行前
this is methodC
方法执行后
3
上述创建代理对象的过程还是比较复杂的,首先需要先获取到代理class对象,再由代理class对象获取到构造器,最后通过构造器实例化一个实例对象。然而,Proxy类中还有一个newProxyInstance()方法,可以直接获取到代理对象,其实该方法就是对上述过程的一个封装,源码如下
/**
* loader : 类加载器
* interfaces : 接口class数组
* h : InvocationHandler对象
*/
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//获取代理class
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
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;
}
});
}
//通过构造器实例化对象并返回
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
具体使用方式如下
Target target1 = new TargetImpl();
Target target = (Target) Proxy.newProxyInstance(target1.getClass().getClassLoader(),target1.getClass().getInterfaces(),new InvocationHandler(){
- 不符合开闭原则,即对修改关闭、扩展开放,每当有新需求的时候都需要修改源代码 (√)
- 当Target类中有成百上千的方法时,需要编写很多代码,而且代码重复性很高(√)
- 当有很多个Target类需要添加日志时,每个类都需要进行修改(×)
使用jdk动态代理的方式可以解决上述问题中的两个,即符合开闭原则,不需要修改原有代码,还有就是很大程度上减少了代码量,没有了大量无意义重复代码,代码的维护性也因此提高不好。但jdk动态代理还是有缺点的,
- 比如当有100个类需要打印日志时,我们就需要生成100个代理类,也就是100个类需要实现InvocationHandler接口,这也是一个很大的工作量,而且还是存在重复代码,降低系统的维护性
- 目标类需要实现接口,不实现接口就不能使用jdk动态代理;还有就是代理类中只能代理接口中的方法,当目标类中扩展了新的方法时,比如新增了方法D、E、F等,这些新增的方法是不能走代理的,也就是说不能打印日志
针对第一个问题,改写MyInvocationHandler如下
class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
public Object getProxyIntance(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
System.out.println("方法执行前");
Object o = method.invoke(target, args);
System.out.println("方法执行后");
return o;
}
}
Target target =(Target) new MyInvocationHandler(new TargetImpl()).getProxyIntance();
CGLib动态代理
首先对于上述问题中的第二个,目标类需要实现接口和目标类中扩展的方法不走代理的缺点,我们可以采用新的动态代理方式来解决,也就是接下来要接下来要介绍的CGLib动态代理
CGLib不是由java提供的,它是一个开源项目,是一种java字节码的生成工具,通过继承的方式来实现代理
class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz){
// 设置目标类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
// 通过字节码技术动态创建子类实例
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("方法执行前");
// 通过代理类调用父类中的方法
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("方法执行后");
return o1;
}
}
public class Main {
public static void main(String[] args) throws Exception {
CglibProxy proxy = new CglibProxy();
TargetImpl target =(TargetImpl) proxy.getProxy(TargetImpl.class);
target.methodA();
target.methodB();
System.out.println(target.methodC(1, 2));
}
}
CGLib实现原理 https://blog.csdn.net/yhl_jxy/article/details/80633194
CGLib是通过继承的方式来实现代理的,当一个类被final修饰时,该类是不能通过CGLib实现代理的;或是一个类的某些方法被final修饰时,这些方法也是不能走代理的。因为被final修饰的类不能被继承,被final修饰的方法不能重写
参考文档
https://zhuanlan.zhihu.com/p/62534874