• 深入理解JDK动态代理机制


    一、现实生活中的代理?

            在现实生活中,我们常见的有服务器代理商、联想PC代理商、百事可乐、火车票、机票等代理商,为什么会有这些个代理商呢?设想以买火车票为场景,如果我要买一张从广州去长沙的火车票,就必须去火车站排队购票,如果排队的人比较多的话,非常的耽误时间。但有了火车票代理商之后,我就可以直接去找个离我最近的代理商买票,因为这样的代理商不止一个二个,遍布全市各地。 所以代理商的出现不但减轻了火车站售票员的工作压力,同时也为市民购票提供了许多方便。只是代理商会收5块的手续费。从这个示例中可以发现和买票有关的一些名词:火车站、售票、代理商?解释这些名词在程序中代理的含义:火车站:称为目标,售票:目标的最终行为,代理商:和火车站具有同样售票行为的代理商,不过代理商在售票前和售票后会做一些操作,比如查询余票、售票后收取手续费等操作。

    二、JDK中的代理

    1、特征:
          1)、jdk中代理的一个很重要的特征:代理类和目标类都拥有相同的接口,所以它们都拥有相同的行为。代理类的对象本身并不真正提供服务,而是调用目标类对象的相关方法,来提供特定的服务。如:火车票代理商自己并不提供火车票销售的服务,而是调用火车站的售票服务。为顾客提供查询余票信息、火车的运营时间、火车票的销售等服务,这些服务都是来自火车站(目标类)。代理类主要负责为目标类预处理消息(如:身份证信息是否正确)、过滤消息(如:该顾客是否为通缉犯,是否公安监视的人),并把这些消息转发给目标类,以及事后处理消息(收取顾客的手续费)等。
          2)、必须实现一个或多个接口

    2、代理种类:

    1)、静态代理,示例:以销售火车票为例

    a、定义一个火车站售票的接口
    package proxy;
    
    public interface Ticket {
    
    	public void ticket();
    }
    
    b、售票接口的实现类
    package proxy;
    
    public class TicketImpl implements Ticket {
    
    	@Override
    	public void ticket() {
    		System.out.println("成功售出一张火车票!");
    	}
    }
    
    c、火车票销售的代理类,与目标类实现了相同的接口
    package proxy;
    
    /**
     * 火车票销售代理类
     */
    public class TicketImplProxy implements Ticket {
    	
    	private TicketImpl ticketImpl;	//目标类
    	
    	public TicketImplProxy(TicketImpl ticketImpl) {
    		this.ticketImpl = ticketImpl;
    	}
    
    	@Override
    	public void ticket() {
    		System.out.println("售票前验证顾客的身份信息…………");
    		ticketImpl.ticket();
    		System.out.println("售票后收取顾客的手续费…………");
    	}
    
    }
    d、测试类:
    package proxy;
    
    public class StaticProxyTest {
    
    	public static void main(String[] args) throws Exception {
    		TicketImpl ticketImpl = new TicketImpl();	//要代理的目标
    		TicketImplProxy staticProxy = new TicketImplProxy(ticketImpl);
    		staticProxy.ticket();
    	}
    }

    运行结果:


    静态代理的缺陷:
        一个代理类只能代理一个目标类,如果在一个系统中想代理多个目标类的话,就得写多个代理类,这将是一件很繁琐的工作。所以在JDK1.3之后,引出了动态代理的概念,动态代理可以在程序运行的时候,通过反射机制动态的生成一个类的字节码文件,并实现目标类相同的接口。

    2)、动态代理
    1)、介绍:
        jdk中的动态代理,主要用到了java.lang.reflect包中的两个类:Proxy类InvocationHandler接口,Proxy类用于生成代理类,InvocationHandler接口用于调用目标类的方法之前或之后,做一些处理。比如:记录日志、事务处理、效率测试等,均在该接口的invoke方法中实现。传说中AOP思想的原理就是从这里开始扩展的。



    2)、动态代理机制深入分析(以ArrayList为代理目标类为例):
    1、使用Proxy类的getProxyClass方法,获得ArrayList代理类的字节码文件
    Class clazzProxy = Proxy.getProxyClass(ArrayList.class.getClassLoader(), ArrayList.class.getInterfaces());
    
    2、打印代理类的结构信息
    获得这个Class对象的字节码文件之后,我们打印出来看看这个类的名字叫什么?
    System.out.println("代理类名称:" + clazzProxy.getName());
    代理类名称:$Proxy0
    通过JDK反射机制,获代理类的结构,比如:它的父类是谁?实现了哪些接口?有哪些个构造方法?有哪些个成员方法?等。。。
    下面将通反射机制,获取这个代理类的组织结构信息:
    System.out.println("代理类" + clazzProxy.getName() + "的父类:" + clazzProxy.getSuperclass().getName());
    代理类$Proxy0的父类:java.lang.reflect.Proxy
    Class[] clazzInterfaces = clazzProxy.getInterfaces();
    StringBuilder sbInterfaces = new StringBuilder();
    for (Class clazzInterface : clazzInterfaces) {
    	sbInterfaces.append(clazzInterface.getName()).append(",");
    }
    sbInterfaces.deleteCharAt(sbInterfaces.length()-1);
    System.out.println("代理类" + clazzProxy.getName() + "所实现的接口:" + sbInterfaces);
    代理类$Proxy0所实现的接口:java.util.List,java.util.RandomAccess,java.lang.Cloneable,java.io.Serializable
    System.out.println("代理类" + clazzProxy.getName() + "的访问修饰符:" + clazzProxy.getModifiers());
    代理类$Proxy0的访问修饰符:17
    由访问修饰符的值可得知代理类的修饰符为:代理类的访问修饰符为:public final,由clazzProxy.getModifiers();得知为17,其中public代表1,final代表16,相加得17。参考java.lang.reflect.Modifier
    System.out.println("\n-------------打印代理类" + clazzProxy.getName() + "的构造方法列表-------------");
    Constructor[] constructors = clazzProxy.getConstructors();
    for (Constructor constructor : constructors) {
    System.out.println("访问修饰符:" + constructor.getModifiers());
    String name = constructor.getName();
    	int modifiers = constructor.getModifiers();
    	StringBuilder sb = new StringBuilder(name);
    	sb.append('(');
    	Class[] clazzParameters = constructor.getParameterTypes();
    	for (Class clazzParameter : clazzParameters) {
    		sb.append(clazzParameter.getName()).append(",");
    	}
    	if (clazzParameters != null && clazzParameters.length != 0) {
    		sb.deleteCharAt(sb.length()-1);
    	}
    	sb.append(')');
    	System.out.println(sb.toString());
    }
    -------------打印代理类$Proxy0的构造方法列表-------------
    访问修饰符:1
    $Proxy0(java.lang.reflect.InvocationHandler)
    System.out.println("\n-------------打印代理类" + clazzProxy.getName() + "的方法列表-------------");
    Method[] methods = clazzProxy.getMethods();
    for (Method method : methods) {
    	String name = method.getName();
    	StringBuilder sb = new StringBuilder(name);
    	sb.append('(');
    	Class[] clazzParameters = method.getParameterTypes();
    	for (Class clazzParameter : clazzParameters) {
    		sb.append(clazzParameter.getName()).append(",");
    	}
    	if (clazzParameters != null && clazzParameters.length != 0) {
    		sb.deleteCharAt(sb.length()-1);
    	}
    	sb.append(')');
    	System.out.println(sb.toString());
    }
    
    打印结果:
    -------------打印代理类$Proxy0的方法列表-------------
    add(java.lang.Object)
    add(int,java.lang.Object)
    get(int)
    equals(java.lang.Object)
    toString()
    hashCode()
    indexOf(java.lang.Object)
    clear()
    contains(java.lang.Object)
    isEmpty()
    lastIndexOf(java.lang.Object)
    addAll(java.util.Collection)
    addAll(int,java.util.Collection)
    iterator()
    size()
    toArray()
    toArray([Ljava.lang.Object;)
    remove(java.lang.Object)
    remove(int)
    set(int,java.lang.Object)
    containsAll(java.util.Collection)
    removeAll(java.util.Collection)
    retainAll(java.util.Collection)
    subList(int,int)
    listIterator()
    listIterator(int)
    isProxyClass(java.lang.Class)
    getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
    getInvocationHandler(java.lang.Object)
    newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
    wait(long)
    wait()
    wait(long,int)
    getClass()
    notify()
    notifyAll()
    这些方法分别来自List接口、Proxy和Object类,因为代理类会自动实现目标类相同的接口
    //由上述信息可推断出代理类的文件结构(程序中只实现打印代理类的声明部份)
    StringBuilder sbProxyClassStruct = new StringBuilder("public final class ");
    sbProxyClassStruct.append(clazzProxy.getName())
    		  .append(" extends ").append(clazzProxy.getSuperclass().getName())
    		  .append(" implements ").append(sbInterfaces).append(" { } ");
    System.out.println("代理类的结构:" + sbProxyClassStruct);
    程序中只打印了代理类的声明部份,结果:
    代理类声明部份的结构:public final class $Proxy0 extends java.lang.reflect.Proxy implements java.util.List,java.util.RandomAccess,java.lang.Cloneable,java.io.Serializable { } 

    由上述信息可推断出代理类的文件结构:
    package proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.List;
    import java.util.ListIterator;
    
    public final class $Proxy0 extends java.lang.reflect.Proxy implements java.util.List,
    		java.util.RandomAccess, java.lang.Cloneable, java.io.Serializable {
    
    	public $Proxy0(InvocationHandler h) {
    		super(h);
    		// TODO Auto-generated constructor stub
    	}
    
    	@Override
    	public boolean add(Object e) {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public void add(int index, Object element) {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public boolean addAll(Collection c) {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public boolean addAll(int index, Collection c) {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public void clear() {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public boolean contains(Object o) {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public boolean containsAll(Collection c) {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public Object get(int index) {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public int indexOf(Object o) {
    		// TODO Auto-generated method stub
    		return 0;
    	}
    
    	@Override
    	public boolean isEmpty() {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public Iterator iterator() {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public int lastIndexOf(Object o) {
    		// TODO Auto-generated method stub
    		return 0;
    	}
    
    	@Override
    	public ListIterator listIterator() {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public ListIterator listIterator(int index) {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public boolean remove(Object o) {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public Object remove(int index) {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public boolean removeAll(Collection c) {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public boolean retainAll(Collection c) {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public Object set(int index, Object element) {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public int size() {
    		// TODO Auto-generated method stub
    		return 0;
    	}
    
    	@Override
    	public List subList(int fromIndex, int toIndex) {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public Object[] toArray() {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public Object[] toArray(Object[] a) {
    		// TODO Auto-generated method stub
    		return null;
    	}
    }
    由上述信息得知该代理类只有一个带InvocationHandler参数的构造方法,现在我们来生成这个代理类的实例对象:
    //方式1,创建一个内部类,并实现InvocationHandler接口
    Constructor proxy1 = clazzProxy.getConstructor(InvocationHandler.class);
    System.out.println("------------创建代理类实例,方式1--------------------");
    class MyInvocationHandler implements InvocationHandler {
    	ArrayList target = new ArrayList();
    	@Override
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		String name = method.getName();
    		System.out.println(name + "方法调用前...");
    		Object retVal = method.invoke(target, args);
    		System.out.println(name + "方法调用后的返回结果为:" + retVal);
    		System.out.println(name + "方法调用后...\n");
    		return retVal;
    	}
    			
    }
    List list1 = (List)proxy1.newInstance(new MyInvocationHandler());
    list1.add("zhangsan");
    list1.add("lisi");
    System.out.println(list1.size());
    调用List接口的add方法和size方法后的输出结果:
    ---------------------创建代理类$Proxy0的实例对象----------------------
    ------------创建代理类实例,方式1--------------------
    add方法调用前...
    add方法调用后的返回结果为:true
    add方法调用后...

    add方法调用前...
    add方法调用后的返回结果为:true
    add方法调用后...

    size方法调用前...
    size方法调用后的返回结果为:2
    size方法调用后...
    2
    //方式2
    Constructor prox2 = clazzProxy.getConstructor(InvocationHandler.class);
    List list2 = (List)prox2.newInstance(new InvocationHandler() {
    	List target = new ArrayList();
    	@Override
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		Object retVal = method.invoke(target, args);
    		return retVal;
    	}}
    );
    //方式3(匿名类),将创建代理类和创建代理类实例对象的步聚合二为一
    System.out.println("------------创建代理类实例,方式3--------------------");
    List list3 = (List)Proxy.newProxyInstance(
    	ArrayList.class.getClassLoader(), 
    	ArrayList.class.getInterfaces(),
    		new InvocationHandler() {
    			ArrayList target = new ArrayList();
    			@Override
    			public Object invoke(Object proxy, Method method,
    					Object[] args) throws Throwable {
    				//测试方法的运行效率
    				long beginTime = System.currentTimeMillis();
    				Object retVal = method.invoke(target, args);
    				long endTime = System.currentTimeMillis();
    				System.out.println(method.getName() + " 方法运行时长为:" + (endTime - beginTime));
    				return retVal;
    			}}
    		);
    		list3.add("hy");
    		list3.add("yangxin");
    		System.out.println(list3.size());
    输出结果:
    ------------创建代理类实例,方式3--------------------
    add 方法运行时长为:0
    add 方法运行时长为:0
    size 方法运行时长为:0
    2

    *通过以上代码的分析,可将生成代理类的代码,抽取成一个生成代理的通用方法,并将功能代码用一个对象封装起来(如:测试方法的运行时间,方法运行前或运行后需要做的事情)
    /**
     * 获得代理对象
     * @param target 目标(被代理的对象)
     * @param advice 目标对象中的方法被调用前要执行的功能
     * @return 目标的代理对象
    */
    private static Object getProxy(final Object target,final Advice advice) {
    	return Proxy.newProxyInstance(
    			target.getClass().getClassLoader(), 
    			target.getClass().getInterfaces(),
    			new InvocationHandler() {
    				@Override
    				public Object invoke(Object proxy, Method method,
    						Object[] args) throws Throwable {
    					Object retVal = null;
    					try {
    						advice.doBefore(target, method, args);//方法执行前
    						retVal = method.invoke(target, args);
    						advice.doAfter(target, method, args, retVal);//方法执行后
    					} catch (Exception e) {
    						advice.doThrow(target, method, args, e);//方法抛出异常
    					} finally {
    						advice.doFinally(target, method, args);//方法最终执行代码(用于释放数据资源、关闭IO流等操作)
    					}
    					return retVal;
    					}}
    				);
    	}
    封装功能的对象:
    package proxy;
    
    import java.lang.reflect.Method;
    
    /**
     * aop接口,提供方法运行前、方法运行后、方法运行中产生Exception、方法最终运行代码
     *
     */
    public interface Advice {
    	
    	/**
    	 * 方法运行前
    	 * @param target 被代理的目标对象
    	 * @param method 被调用的方法
    	 * @param args 方法的参数
    	 */
    	public void doBefore(Object target, Method method, Object[] args);
    	
    	/**
    	 * 方法运行后
    	 * @param target 被代理的目标对象
    	 * @param method 被调用的方法对象
    	 * @param args 方法的参数
    	 * @param retVal 方法的返回值
    	 */
    	public void doAfter(Object target, Method method, Object[] args, Object retVal);
    	
    	/**
    	 * 方法运行时产生的异常
    	 * @param target 被代理的目标对象
    	 * @param method 被调用的方法
    	 * @param args 方法参数
    	 * @param e 运行时的异常对象
    	 */
    	public void doThrow(Object target, Method method, Object[] args, Exception e);
    	
    	/**
    	 * 最终要执行的功能(如释放数据库连接的资源、关闭IO流等)
    	 * @param target 被代理的目标对象
    	 * @param method 被调用的方法
    	 * @param args 方法参数
    	 */
    	public void doFinally(Object target, Method method, Object[] args);
    }
    
    再建一个日志功能的实现类LogAdvice,用于测试:
    package proxy;
    
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    /**
     * 日志功能切入类
     * @author 杨信
     *
     */
    public class LogAdvice implements Advice {
    
    	long beginTime = System.currentTimeMillis();
    	
    	@Override
    	public void doBefore(Object target, Method method, Object[] args) {
    		System.out.println(target.getClass().getSimpleName() +
    				"." + method.getName() + "方法被调用,参数值:" + Arrays.toString(args));
    
    	}
    	
    	@Override
    	public void doAfter(Object target, Method method, Object[] args, Object retVal) {
    		long endTime = System.currentTimeMillis();
    		System.out.println(target.getClass().getSimpleName() +
    				"." + method.getName() + "方法运行结束,返回值:" + retVal + ",耗时" + (endTime - beginTime) + "毫秒。");
    
    	}
    
    	@Override
    	public void doThrow(Object target, Method method, Object[] args,
    			Exception e) {
    		System.out.println("调用" + target.getClass().getSimpleName() +
    				"." + method.getName() + "方法发生异常,异常消息:");
    		e.printStackTrace();
    	}
    
    	@Override
    	public void doFinally(Object target, Method method, Object[] args) {
    		System.out.println("doFinally...");
    		
    	}
    
    }
    
    测试生成代理的通用方法:
    ArrayList target = new ArrayList();
    List list4 = (List)getProxy(target,new LogAdvice());
    list4.add("张三");
    list4.add("李四");
    list4.add("王五");
    System.out.println(list4.size());
    list4.get(3);	//演示异常advice
    输出结果:
    ArrayList.add方法被调用,参数值:[张三]
    ArrayList.add方法运行结束,返回值:true,耗时0毫秒。
    doFinally...
    ArrayList.add方法被调用,参数值:[李四]
    ArrayList.add方法运行结束,返回值:true,耗时0毫秒。
    doFinally...
    ArrayList.add方法被调用,参数值:[王五]
    ArrayList.add方法运行结束,返回值:true,耗时0毫秒。
    doFinally...
    ArrayList.size方法被调用,参数值:null
    ArrayList.size方法运行结束,返回值:3,耗时0毫秒。
    doFinally...
    3
    ArrayList.get方法被调用,参数值:[3]
    调用ArrayList.get方法发生异常,异常消息:
    java.lang.reflect.InvocationTargetException
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    	at java.lang.reflect.Method.invoke(Method.java:597)
    	at proxy.DynamicProxyTest$3.invoke(DynamicProxyTest.java:163)
    	at $Proxy0.get(Unknown Source)
    	at proxy.DynamicProxyTest.main(DynamicProxyTest.java:143)
    Caused by: java.lang.IndexOutOfBoundsException: Index: 3, Size: 3
    	at java.util.ArrayList.RangeCheck(ArrayList.java:547)
    	at java.util.ArrayList.get(ArrayList.java:322)
    	... 7 more
    doFinally...
    完毕,通过动态代理机制,实现了传说中的AOP思想。
  • 相关阅读:
    java对象转json对象
    cas-client登录后报INVALID_PROXY_CALLBACK
    tomcat启动一闪而过,调试tomcat
    获取url中的参数
    cas 退出后跳转指定页面
    cas增加验证码
    spring security+cas(cas proxy配置)
    oracle 导入导出指定表
    Marshaller根据对象生成xml文件
    webpack学习笔记
  • 原文地址:https://www.cnblogs.com/xyang0917/p/4172535.html
Copyright © 2020-2023  润新知