• 黑马程序员_高新技术之代理


     

     

    一,代理的概念和作用
    1,概述:
    代理的主要类:java.lang.reflect.Proxy
    生活中的代理:比如买电脑,直接在家附近的代理商买比直接去北京总部买要方便的多。
    程序中的代理:为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等

    2,编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。比如下面的代码为X类增加了一个计算方法的系统功能代码:

    class X
    {
    	void sayHello(){
    	System.out.println("hello,itcast");
    	}
    }
    XProxy  /*类X的代理 在方法执行前后增加了时间的计算 计算方法的用时*/
    {
    	void sayHello()	{
    	starttime
    	X.sayHello();
    	endtime
    	}
    }


    3,代理架构图:

    4,AOP:
    系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
                                  安全       事务         日志
    StudentService  ------|----------|------------|-------------
    CourseService   ------|----------|------------|-------------
    MiscService       ------|----------|------------|-------------
    安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务

    用具体的程序代码描述交叉业务:
    method1         method2          method3
    {                      {                       {
    ------------------------------------------------------切面
    ....            ....              ......
    ------------------------------------------------------切面
    }                       }                       }

    交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
    ------------------------------------------------------切面
    func1         func2            func3
    {             {                {
    ....            ....              ......
    }             }                }
    ------------------------------------------------------切面

    使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。

    二,动态代理技术
    1,概述
     要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,太累!JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
     JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。

    2,CGLIB库
    CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。

    3,
    代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
    1.在调用目标方法之前
    2.在调用目标方法之后
    3.在调用目标方法前后
    4.在处理目标方法异常的catch块中
    比如下面的代码:

    Class proxy{
    	void sayHello(){
    		……….//调用目标方法前
    		try{
    			target.sayHello();
    		}catch(Exception e){
    			………..//处理目标方法异常的catch块中
    		}
    		………….//调用目标方法后
    	}
    }
    


    三,分析JVM动态生成的类
    1,Proxy 的静态方法

    // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
    static InvocationHandler getInvocationHandler(Object proxy) 
    
    // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
    static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
    
    // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
    static boolean isProxyClass(Class cl) 
    
    // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
    static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
        InvocationHandler h) 


     

    java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

     2,. InvocationHandler 的核心方法
          

    // 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
    // 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
    Object invoke(Object proxy, Method method, Object[] args) 
    


     

    每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象。
    java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。

    3,代理机制及其特点:
    如何使用 Java 动态代理。具体有如下四步骤:
    通过实现 InvocationHandler 接口创建自己的调用处理器;
    通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
    通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
    通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

    4,动态代理对象创建过程

    // MyInvocationHandler 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
    InvocationHandler handler = new MyInvocationHandler(..); 
    
    // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
    Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 
    
    // 通过反射从生成的类对象获得构造函数对象
    Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 
    
    // 通过构造函数对象创建动态代理类实例
    Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler }); 


     

    实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如下

    简化的动态代理对象创建过程

    // MyInvocationHandler 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    InvocationHandler handler = new MyInvocationHandler(..); 
    
    // 通过 Proxy 直接创建动态代理类实例
    Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
    	 new Class[] { Interface.class }, 
    	 handler ); 
    	


    四, Java 动态代理机制的一些特点。
    首先是动态生成的代理类本身的一些特点。1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.itcast.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.itcast.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;2)类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。4)类继承关系:该类的继承关系如图:

    由图可见,Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。
    接下来让我们了解一下代理类实例的一些特点。每个实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,可能的原因有:一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖;二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。
    接着来了解一下被代理的一组接口有哪些特点。首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。再次,需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。最后,接口的数目不能超过 65535,这是 JVM 设定的限制。
    最后再来了解一下异常处理方面的特点。从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于 Throwable 接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛 Throwable 异常。那么如果在 invoke 方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Java 动态代理类已经为我们设计好了解决方法:它将会抛出 UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

    五,动态代理的工作原理图

    六,让动态生成的类成为目标类的代理
    怎样将目标类传进去?
    直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义。
    为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。
    让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。
    将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接收目标同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。
    将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供?
    把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外界提供的代码!
    为bind方法增加一个Advice参数。

    七。代码演示:
    动态代理测试类:

    public class ProxyTest {
    
    	/**
    	 * @param args
    	 */
    	public static void main(String[] args) throws Exception{
    		// TODO Auto-generated method stub
    		//创建实现了Collection接口的动态类和查看其名称
    		Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
    		System.out.println(clazzProxy1.getName());
    		
    		//编码列出动态类中的所有构造方法和参数签名
    		System.out.println("----------begin constructors list----------");
    		/*$Proxy0()
    		$Proxy0(InvocationHandler,int)*/
    		Constructor[] constructors = clazzProxy1.getConstructors();
    		for(Constructor constructor : constructors){
    			String name = constructor.getName();//获取到某个构造方法的名字
    			//StringBuilder在单线程下效率更高 StringBuffer在多线程下效率高
    			StringBuilder sBuilder = new StringBuilder(name);
    			sBuilder.append('(');
    			//获取构造方法里的参数类型
    			Class[] clazzParams = constructor.getParameterTypes();
    			for(Class clazzParam : clazzParams){
    				sBuilder.append(clazzParam.getName()).append(',');
    			}
    			if(clazzParams!=null && clazzParams.length != 0)
    				//出去参数后面的最后一个多余的逗号
    				sBuilder.deleteCharAt(sBuilder.length()-1);
    			sBuilder.append(')');
    			System.out.println(sBuilder.toString());			
    		}
    		//编码列出动态类中的所有方法和参数签名
    		System.out.println("----------begin methods list----------");
    		/*$Proxy0()
    		$Proxy0(InvocationHandler,int)*/
    		//获取Collection对象上的方法
    		Method[] methods = clazzProxy1.getMethods();
    		for(Method method : methods){
    			String name = method.getName();
    			StringBuilder sBuilder = new StringBuilder(name);
    			sBuilder.append('(');
    			Class[] clazzParams = method.getParameterTypes();
    			for(Class clazzParam : clazzParams){
    				sBuilder.append(clazzParam.getName()).append(',');
    			}
    			if(clazzParams!=null && clazzParams.length != 0)
    				sBuilder.deleteCharAt(sBuilder.length()-1);
    			sBuilder.append(')');
    			System.out.println(sBuilder.toString());			
    		}
    		//创建动态类的实例对象
    		System.out.println("----------begin create instance object----------");
    		//Object obj = clazzProxy1.newInstance();
    		//该对象的构造函数是有参的 所以先通过反射获取有参的构造函数
    		Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
    		//自己创建一个类实现InvocationHandler
    		class MyInvocationHander1 implements InvocationHandler{
    
    			public Object invoke(Object proxy, Method method, Object[] args)
    					throws Throwable {
    				// TODO Auto-generated method stub
    				return null;
    			}
    		
    		}
    		//实例化该对象传入的参数必须是InvocationHandler类型
    		Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHander1());
    		
    		System.out.println(proxy1);//打印结果为null 因为调用了toString invoke返回的为null 所以为null
    		proxy1.clear();//清除集合
    		//proxy1.size();//会报告异常 因为调用size方法 回去调用invoke方法  返回一个null 与size返回的一个整数相冲突
    		
    		//将创建动态类的实例对象的代理改成匿名内部类的形式编写
    		Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
    
    			public Object invoke(Object proxy, Method method, Object[] args)
    					throws Throwable {
    				return null;
    			}
    			
    		});
    		
    		final ArrayList target = new ArrayList();			
    		Collection proxy3 = (Collection)getProxy(target,new MyAdvice());
    		proxy3.add("zxx");//调用add方法会去找Handler的incoke方法
    		proxy3.add("lhm");
    		proxy3.add("bxd");
    		System.out.println(proxy3.size());
    		System.out.println(proxy3.getClass().getName());
    	}
    	
    	//把目标和系统功能作为一个参数传进来 用这个方法去实现目标的代理类
    	private static Object getProxy(final Object target,final Advice advice) {
    		Object proxy3 = Proxy.newProxyInstance(
    				target.getClass().getClassLoader(),//获得目标类的类加载器
    				/*new Class[]{Collection.class},*/
    				target.getClass().getInterfaces(),//目标类的接口
    				
    				new InvocationHandler(){
    				
    					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() + " running time of " + (endTime - beginTime));
    						return retVal;*/
    						
    
    						advice.beforeMethod(method);
    						Object retVal = method.invoke(target, args);
    						advice.afterMethod(method);
    						return retVal;						
    						
    					}
    				}
    				);
    		return proxy3;
    	}
    
    }
    
    


    传入的参数Advice接口:

    import java.lang.reflect.Method;
    
    //要使用的系统功能代码接口
    public interface Advice {
    	void beforeMethod(Method method);
    	void afterMethod(Method method);
    }


    实现Advice接口的MyAdvice类:

    import java.lang.reflect.Method;
    //实现了系统功能接口的类:MyAdvice  也就是我们要切入的代码
    public class MyAdvice implements Advice {
    	long beginTime = 0;
    	public void afterMethod(Method method) {
    		// TODO Auto-generated method stub
    		System.out.println("从传智播客毕业上班啦!");		
    		long endTime = System.currentTimeMillis();
    		System.out.println(method.getName() + " running time of " + (endTime - beginTime));
    
    	}
    
    	public void beforeMethod(Method method) {
    		// TODO Auto-generated method stub
    		System.out.println("到传智播客来学习啦!");
    		beginTime = System.currentTimeMillis();
    	}
    
    }


    八,实现AOP功能的封装与配置
    工厂类BeanFactory:

    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Properties;
    
    import cn.itcast.day3.Advice;
    //负责创建目标类或代理类的实例对象
    public class BeanFactory {
    	Properties props = new Properties();
    	//构造方法要接受一个配置文件
    	public BeanFactory(InputStream ips){
    		try {
    			//从输入流中读取属性列表(键和元素对)。
    			props.load(ips);
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    	/*根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,
    	则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。*/
    	public Object getBean(String name){
    		//用指定的键name在此属性列表中搜索属性
    		String className = props.getProperty(name);
    		Object bean = null;
    		try {
    			//创建这个类的实例对象
    			Class clazz = Class.forName(className);
    			bean = clazz.newInstance();
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		} 
    		//如果是特殊的类ProxyFactoryBean就创建代理 并返回代理
    		if(bean instanceof ProxyFactoryBean){
    			Object proxy = null;
    			ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
    			try {
    				//获取目标和通告
    				Advice advice = (Advice)Class.forName(props.getProperty(name + ".advice")).newInstance();
    				Object target = Class.forName(props.getProperty(name + ".target")).newInstance();
    				//设置目标和通告
    				proxyFactoryBean.setAdvice(advice);
    				proxyFactoryBean.setTarget(target);
    				//通过proxyFactoryBean获取proxy对象
    				proxy = proxyFactoryBean.getProxy();
    			} catch (Exception e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			return proxy;
    		}
    		return bean;
    	}
    }


    配置文件config.properties
    :

    #xxx=java.util.ArrayList
     xxx=cn.itcast.ProxyFactoryBean
     xxx.target=java.util.ArrayList
     xxx.advice=cn.itcast.MyAdvice


    生成动态代理的工厂ProxyFactoryBean

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    import cn.itcast.day3.Advice;
    
    //充当封装生成动态代理的工厂 需要为工厂类提供目标 通知配置参数信息
    public class ProxyFactoryBean {
    
    	private Advice advice;
    	private Object target;
    	
    	public Advice getAdvice() {
    		return advice;
    	}
    
    	public void setAdvice(Advice advice) {
    		this.advice = advice;
    	}
    
    	public Object getTarget() {
    		return target;
    	}
    
    	public void setTarget(Object target) {
    		this.target = target;
    	}
    
    	public Object getProxy() {
    		// TODO Auto-generated method stub
    		Object proxy3 = Proxy.newProxyInstance(
    				target.getClass().getClassLoader(),
    				/*new Class[]{Collection.class},*/
    				target.getClass().getInterfaces(),
    				new InvocationHandler(){
    				
    					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() + " running time of " + (endTime - beginTime));
    						return retVal;*/
    						
    
    						advice.beforeMethod(method);
    						Object retVal = method.invoke(target, args);
    						advice.afterMethod(method);
    						return retVal;						
    						
    					}
    				}
    				);
    		return proxy3;
    	}
    
    }


    验证类:

    import java.io.InputStream;
    import java.util.Collection;
    
    //验证是代理还是目标
    public class AopFrameworkTest {
    
    	/**
    	 * @param args
    	 */
    	public static void main(String[] args) throws Exception {
    		// TODO Auto-generated method stub
    		//将给定名称的资源用一个读取流关联 加在配置文件
    		InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
    		Object bean = new BeanFactory(ips).getBean("xxx");
    		System.out.println(bean.getClass().getName());
    		((Collection)bean).clear();
    	}
    
    }
    


     

  • 相关阅读:
    linux上读取apk信息
    android 的混淆解析
    使用ant自动生成签名的apk
    每天一剂 WebView 良药
    看完让你觉得自己变超聪明的图
    centos7:Kafka集群安装
    centos7:storm集群环境搭建
    Mysql数据库存储数据时间与系统获取时间不一致
    centos7:ssh免密登陆设置
    FLUME安装&环境(二):拉取MySQL数据库数据到Kafka
  • 原文地址:https://www.cnblogs.com/bbsno1/p/3253816.html
Copyright © 2020-2023  润新知