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

一、代理的理解
什么是代理?
(1)生活中的代理:
一个武汉人买的联想电脑,他在武汉联想代理商买和跑到北京联想总部买有什么区别?基本上是一样的,都解决了核心问题.但是还有一些区别的.批发进货的成本和运输费的优势可以看出,,从代理那买和直接到北京买的总成本要低.这就可以看出在生活中代理的一些好处.
(2)程序中的代理:
要为已经存在的多个具有相同接口的目标类的各个方法增加依一些系统功能,例如:异常处理,日志,计算方法的时间,事务管理等等.那么这就需要一个与目标相同接口的代理类,代理类的每个方法调用目标类的相 同方法,并在调用方法时加上系统功能的代码.
如果采用工厂模式(实例化对象,用工厂方法代替new操作)和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易. 这就是代理的优势.

先是一个客户端直接调用目标,然后加上代理增加了一些功能同时仍然实现目标类,客户端改为调用代理。核心的内容依然不变,又扩展了相应的功能.客户端也可以随意切换.
二、AOP(面向方面的编程)
安全,事务,日志等功能要贯彻到很多个模块中,他们就是交叉业务
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:

                              安全       事务         日志

StudentService  ------|----------|------------|-------------

CourseService   ------|----------|------------|-------------

MiscService      ------|----------|------------|-------------

用具体的程序代码描述交叉业务:

method1         method2          method3

{                      {                       {

------------------------------------------------------切面

....            ....              ......

------------------------------------------------------切面

}                       }                       }


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

所示:

------------------------------------------------------切面

func1         func2            func3

{             {                {

....            ....              ......

}             }                }

------------------------------------------------------切面

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

三、动态代理技术
要为系统中各个接口增加代理功能,每一个都要写,非常的麻烦.
 但是JVM可以在运行期动态生成出类的字节码,这种动态生成的类就把它用做代理类.
 条件: JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
 如果这个目标类没有接口呢??
 有一个CGLB库可以动态的生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLB库.

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

分析JVM动态生成的类
public class ProxyTest_JVM动态生成类 {
	public static void main(String[] args) throws Exception{
		//调用Proxy这个类,为它指定类加载器,和多个接口
		Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		System.out.println("该类是:"+clazzProxy1.getName());//打印这个类
		//查看这个类的所有构造方法
		System.out.println("------------begin constructors list(构造方法)---------------");
		Constructor [] constructors = clazzProxy1.getConstructors();
		for (Constructor constructor : constructors) {
			String name = constructor.getName();
			//我想按列表的方式打印出这些构造方法
			/*$Proxy0
			 * $Proxy0(InvocationHandler,int)
			 * */
			StringBuilder sBuilder = new StringBuilder(name);//单线程用StringBuilder,效率较高,不需要考虑安全问题
			//打印参数列表
			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);
			}
		}
		
		
		
		//查看这个类的所有方法
		System.out.println("------------begin methods list(方法)---------------");
		Method [] methods = clazzProxy1.getMethods();
		for (Method constructor : methods) {
			String name = constructor.getName();
			//我想按列表的方式打印出这些方法
			StringBuilder sBuilder = new StringBuilder(name);//单线程用StringBuilder,效率较高,不需要考虑安全问题
			//打印方法列表
			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);
			}
		}
		
		System.out.println("------------begin create instance object(创建实例对象)---------------");
		//Object obj = clazzProxy1.newInstance(); 这个无法创建出来的,因为这个对象没有空参数的构造方法
		//那么就先获得这个有参的构造方法(异常先抛出,这样看起来比较直观)
		Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
		//通过这个构造方法来创建实例对象
		//但是这个构造方法的参数InvocationHandler是一个接口,无法new这个对象,无法创建这个对象的参数
		//那么我们写一个类去实现这个接口
		class MyInvocationHandler implements InvocationHandler{
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				
				return 1;
			}
		}
		//这个是Collection实现类对象,那么这个一定是Collection类型
		Collection Proxy1 = (Collection) constructor.newInstance(new MyInvocationHandler());
		//System.out.println(collection);打印结果却是null,说明对象没有创建成功,或者是toString()方法返回了null
		//System.out.println(collection.toString());结果是null,而且没有报空指针异常 ,说明是toString()方法返回null
		
		
		
		//当创建代理对象Proxy1的时候用到MyInvocationHandler这个内部类,这个内部类就使用了一次
		//那么我们可以直接new一个匿名内部类 
		Collection Proxy2 = (Collection) constructor.newInstance(new InvocationHandler(){
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				
				return null;
			}
		});
		
		/**
		 * 重点:
		 * 第三种方式:让jvm创建动态类以及实例对象这两个步骤合二为一
		 * Proxy.newInstance()方法直接一步就创建出代理对象
		 */
		Collection Proxy3 = (Collection)Proxy.newProxyInstance(
				Collection.class.getClassLoader(), //参数1:定义代理类的类加载器,就是接口的类加载器
				new Class[]{Collection.class},//参数2:代理类要实现的接口列表,但不能用可变参数,因为后面还有一个参数
				new InvocationHandler(){//参数3:指派方法调用的调用处理程序
					
					ArrayList  target = new ArrayList();//必须定义在invoke方法之外
					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("running time of "+(beginTime-endTime));//
						return retVal;
					}
				}
				);
		Proxy3.add("123");//每调用一次add,就回去找invoke方法
		Proxy3.add("456");
		Proxy3.add("789");
		System.out.println(Proxy3.size());//打印这个集合对象的大小
		}
}

实现步骤:
(1)创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
(2)编码列出动态类中的所有构造方法和参数签名
(3)编码列出动态类中的所有方法和参数签名
(4)创建动态类的实例对象
(5)用反射获得构造方法
(6)编写一个最简单的InvocationHandler类
(7)调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
(8)打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。
原因:每次调用对象方法都会调用InvocationHandler对象中的invoke方法,而当invoke方法返回null时,没有返回值的结果是null,带返回值的无法将invoke方法返回null这个值转换成自己的返回值,所以会报错!!
调用调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求

总结思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息?
三个方面:
(1)生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
(2)产生的类字节码必须有个一个关联的类加载器对象;
(3)生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
简便方法:用Proxy.newInstance方法直接一步就创建出代理对象。

分析动态生成的类的内部代码

(1)动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接收InvocationHandler参数的构造方法

(2)构造方法接收一个InvocationHandler对象,接收对象了要干什么呢?内部的代码会是怎么样呢?
为了记住这个对象,以便在其他的时候在应用它。内部代码如下:
$Proxy0 implements Collection
{
	InvocationHandler handler;
	public $Proxy0(InvocationHandler handler)
	{
		this.handler = handler;
	}
}

(3) 实现 Collection 接口的动态类中的各个方法的代码又是怎样的呢?

$Proxy0 implements Collection
{
	InvocationHandler handler;
	public $Proxy0(InvocationHandler handler)
	{
		this.handler = handler;
	}
	//生成的Collection接口中的方法的运行原理,它会调用InvocationHandler这个对象的invoke方法
	int size()
	{
		return handler.invoke(this,this.getClass().getMethod("size"),null);
	}
	void clear(){
		handler.invoke(this,this.getClass().getMethod("clear"),null);
	}
	boolean add(Object obj){
		handler.invoke(this,this.getClass().getMethod("add"),obj);
	}
}

InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?图解说明如下:








如图所示:1.客户端调用代理;
  2.代理的构造方法接收一个InvocationHandler对象;
   3.然后客户断掉用代理的各个方法;
   4.代理的各个方法会把调用请求转发给通过构造方法传进来的invocationHandler对象;
   5.这个invocationHandler对象,又会把请求分发给目标类相应的方法;在invoke()中封装,这个invoke()方法中可以加入相应的功能,同时还通过Method调用目标相应的方法;
   上图中加入log.()这个日志功能,它代表一个对象,也就是把系统功能封装成了对象,执行这个对象就等于执行了切面的代码,这就是面向方面的编程.

 四、编写可生成代理和插入通告的通用方法
我们把的目标系统功能封装起来,当代理需要什么样的系统功能和相应的目标就传给InvocationHandler对象的invoke()方法,这样大大提高了程序的扩展和灵活性。
1.将代理方法单独重构,接收目标和系统功能的对象
final ArrayList  target = new ArrayList();
Collection Proxy3 = (Collection) getProxy(target,new MyAdvice());//代理系统功能变为一个对象
2.把系统功能变为一个接口,通过相应的类去实现接口
public interface Advice_封装系统功能的接口 {
	public void beforeMethod(Method method);
	public void afterMethod(Method method);
}

public class MyAdvice implements Advice_封装系统功能的接口 {
	long beginTime = 0;
	public void afterMethod(Method method) {
		System.out.println("从传智播客毕业上班了!");
		long endTime = System.currentTimeMillis();
		System.out.println(method.getName()+"running time of "+(endTime-beginTime));//
	}

	public void beforeMethod(Method method) {
		System.out.println("到传智播客学习了!");
		 beginTime = System.currentTimeMillis();
	}
}

3.在代理方法中接收目标和系统功能接口
这个方法就变成封装的固定方法,只要用代理,把实现的目标类和实现扩展的系统功能接口的对象传过来

//这个方法就变为给我一个目标,给我一个系统功能,我就会生成一个代理
	private static Object getProxy(final Object target,final Advice_封装系统功能的接口 advice) { //返回值和参数改为Object,设为通用的方法
		Object  Proxy3 = (Object)Proxy.newProxyInstance(
				target.getClass().getClassLoader(), //参数1:定义代理类的类加载器,就是接口的类加载器
				/*new Class[]{Collection.class*/
				//你给我什么目标我就实现什么接口
				target.getClass().getInterfaces(),//参数2:代理类要实现的接口列表,但不能用可变参数,因为后面还有一个参数
				new InvocationHandler(){//参数3:指派方法调用的调用处理程序
					
					//ArrayList  target = new ArrayList();//必须定义在invoke方法之外
					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("running time of "+(beginTime-endTime));//
						return retVal;*/
						
						advice.beforeMethod(method);
						Object retVal= method.invoke(target, args);
						advice.afterMethod(method);
						return retVal;
					}
				}
				);
		return Proxy3;
	}

五、实现类似spring的可配置的AOP框架
工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。
BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:

  #xxx=java.util.ArrayList

  xxx=cn.itcast.ProxyFactoryBean

  xxx.target=java.util.ArrayList

  xxx.advice=cn.itcast.MyAdvice

lProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?
(1)目标
(2)通知
编写客户端应用:
(1)编写实现Advice接口的类和在配置文件中进行配置
(2)调用BeanFactory获取对象

(1)创建BeanFactory工厂类负责创建目标类或代理类的实例对象
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();
        }
    }
    public Object getBean(String name){
        String className = props.getProperty(name);
        Object bean = null;
        try {
            Class clazz = Class.forName(className);
            bean = clazz.newInstance();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        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();
                System.out.println(Class.forName(props.getProperty(name+".target")).getName());
                proxyFactoryBean.setAdvice(advice);
                proxyFactoryBean.setTarget(target);
                proxy = proxyFactoryBean.getProxy();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return proxy;
        }
        return bean;
    }
}

(2)创建lProxyFacotryBean封装生成动态代理的工厂
public class ProxyFactoryBean {
    
    private Advice advice;
    private Object target;
    
    public Object getProxy() {
        final Object target = getTarget();
        final Advice advice = getAdvice();
        Object proxy = Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new InvocationHandler(){
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    advice.beforeMethod(method);
                    Object retVal = method.invoke(target, args);
                    advice.afterMethod(method);
                    return retVal;
                }
            }
        );
        return proxy;
    }
    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;
    }
}

(3) 创建一个测试框架类
public class AopFrameworkTest{
		public static void main(String[] args)throws Execption{
		// TODO Auto-generated method stub
		InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
		BeanFactory beanFactory = new BeanFactory(ips);
		Object obj = beanFactory.getBean("xxx");
		System.out.println(obj.getClass().getName());
		System.out.println(obj.toString());		
	}
}
( 4)创建一个配置 文件config.properties
#xxx=java.util.ArrayList
xxx=com.itcast.day2.aopframework.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=com.itcast.day2.MyAdvice




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值