静态代理和动态代理

静态代理和动态代理

什么是代理?

举个例子来说,比如我们想去某个商店买个计算机,我们可以选择自己去商店去购买计算机(自己来做这件事情),也可以找别人代替我们去买计算机(别人代自己做这件事情),此时别人充当的就是代理对象,我们就是目标对象。

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

静态代理

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("方法执行后");
    }
}

上述方式可以满足我们的需求,但是具有很多缺点:

  1. 不符合开闭原则,即对修改关闭、扩展开放,每当有新需求的时候都需要修改源代码
  2. 当Target类中有成百上千的方法时,需要编写很多代码,而且代码重复性很高
  3. 当有很多个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();
    }
}

静态代理的方式仅仅解决了上述缺点的第一条

  1. 不符合开闭原则,即对修改关闭、扩展开放,每当有新需求的时候都需要修改源代码 (√)
  2. 当Target类中有成百上千的方法时,需要编写很多代码,而且代码重复性很高(×)
  3. 当有很多个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对象的三种方法
  1. Class.forName(“com.xxx.Target”) 通过Class.forName的方式获取
  2. Target.class 直接通过类名加.class方式获取
  3. 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(){
  1. 不符合开闭原则,即对修改关闭、扩展开放,每当有新需求的时候都需要修改源代码 (√)
  2. 当Target类中有成百上千的方法时,需要编写很多代码,而且代码重复性很高(√)
  3. 当有很多个Target类需要添加日志时,每个类都需要进行修改(×)

使用jdk动态代理的方式可以解决上述问题中的两个,即符合开闭原则,不需要修改原有代码,还有就是很大程度上减少了代码量,没有了大量无意义重复代码,代码的维护性也因此提高不好。但jdk动态代理还是有缺点的,

  1. 比如当有100个类需要打印日志时,我们就需要生成100个代理类,也就是100个类需要实现InvocationHandler接口,这也是一个很大的工作量,而且还是存在重复代码,降低系统的维护性
  2. 目标类需要实现接口,不实现接口就不能使用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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值