黑马程序员——浅谈java中的高新技术(三)

---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------


代理

 

一、概念及作用

        1、生活中的代理:就是常说的代理商,从厂商将商品卖给消费者,消费者不用很麻烦的跑到厂商那里去购买。

        2、程序中的代理:要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如异常处理、日志、计算方法的运行时间、事物管理等等。

        3、简单示例:编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码,如:

代理架构图

代码体现:

相同接口:interfaceA{void sayHello();}

目标类:                                               代理类:

class X implements A{                          class Xproxy implements A{

         void sayHello(){                                    void sayHello(){

                 System.out.println(“Hello”);                 startTime;

          }                                                                    X. sayHello();

}                                                                              endTime;}}

目标类和代理类通常实现同一个或多个接口,一般用该接口来引用其子类(代理类),如:

        Collection  coll = new ArrayList();

4、代理类的优点:

        如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类。这样以后很容易切换,如果想要日志功能时,就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想换掉系统功能也很容易。

 

二、AOP

1、简述:AOPAspectOriented Program)即面向方面的编程。

2、示意图

        系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:

                                              安全      事务         日志

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

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

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

        安全、事务、日志等功能要贯穿于好多个模块中,所以他们就是交叉业务。

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

        1)交叉业务的代码实现

                method1         method2          method3

                {                     {                     {

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

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

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

                }                     }                      }

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

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

                func1         func2            func3

                {                {                   {

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

                }                }                    }

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

        因此使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术,只要是用到面向方面的编程,就涉及到代理。

 

第二讲    动态代理技术

一、概述

1、要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,这时就不能全部采用静态代理的方式,因为要写成百上千个代理类是非常麻烦的,这时就需用动态代理技术。

2、动态代理类:JVM可以在运行期,动态生成类的字节码,这种动态生成的类(不是代理,只是拿来用作代理类)往往被用作代理类,即动态代理类。

注:JVM生成的动态类必须实现一或多个接口,所以JVM生成的动态代理类只能用作具有相同接口的目标类代理。

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

4、代理类各个方法通常除了调用目标相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下位置上加上系统功能代码:

        1)在调用目标方法之前

        2)在调用目标方法之后

        3)在调用目标方法前后

        4)在处理目标方法异常的catch块中。

 

二、分析JVM动态生成的类

以创建实现了Collection接口的代理类为例:

1、用Proxy.getProxyClass方法创建实现了Collection接口的动态类和查看其名称,分析getProxyClass方法的各个参数。

        1)编码列出动态类中的所有构造方法和参数签名

        2)编码列出动态类中的所有方法和参数签名 

2、创建动态类的实例对象

        1)用反射获得构造方法

        2)编写一个最简单的InvocationHandler类(代理类构造方法接收的参数)

        3)调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去

        4)打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。

        5)将创建动态类的实例对象的代理改成匿名内部类的形式编写,锻炼匿名内部类的使用。

3、也可以直接用Proxy.newInstance方法直接一步就创建出代理对象。

示例:

package cn.itheima.demo;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

public class ProxyDemo {

	public static void main(String[] args) throws Exception{
		//利用Proxy的方法getProxyClass(ClassLoader loader,Class<?>... interface)方法来生成动态类
		//这里以实现Collection接口的代理类为例
		Class clazzProxy=Proxy.getProxyClass(ProxyDemo.class.getClassLoader(), Collection.class);
		System.out.println(clazzProxy.getName());//com.sun.proxy.$Proxy0
		
		System.out.println("----------begin constructors list----------");
		/*获取这个代理类的构造方法,以下列形式输出
		 * $Proxy0()
		 * $Proxy0(InvocationHandler,int)
		 * */
		Constructor[] constructors=clazzProxy.getConstructors();
		for (Constructor constructor : constructors) {
			//System.out.println(constructor);
			//直接输出的结果public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
			
			String name=constructor.getName();
			name=name.substring(name.lastIndexOf('.')+1);//将方法名进行处理
			StringBuilder sBuilder=new StringBuilder(name);//定义一个容器来存储
			sBuilder.append('(');
			Class[] clazzParams=constructor.getParameterTypes();//获取参数列表的类型
			for (Class clazzParam : clazzParams) {
				String paramName=clazzParam.getName();//对参数类型名进行处理
				//paramName=paramName.substring(paramName.lastIndexOf('.')+1);
				sBuilder.append(paramName).append(',');
			}
			//如果没有构造函数或者构造函数没有参数,则删除最后一个","字符
			if(clazzParams!=null&&clazzParams.length!=0){
				sBuilder.deleteCharAt(sBuilder.length()-1);
			}
			sBuilder.append(')');
			
			System.out.println(sBuilder.toString());//输出
		}
		
		System.out.println("----------begin method list----------");
		/*获取这个代理类具备的方法,以下列形式输出
		 * toString()
		 * hashCode()
		 * clear()
		 * addAll(java.util.Collection)
		 * */
		
		Method[] methods=clazzProxy.getMethods();
		for (Method method : methods) {
			String name=method.getName();
			name=name.substring(name.lastIndexOf('.')+1);//将方法名进行处理
			StringBuilder sBuilder=new StringBuilder(name);//定义一个容易来存储
			sBuilder.append('(');
			Class[] clazzParams=method.getParameterTypes();//获取参数列表的类型
			for (Class clazzParam : clazzParams) {
				String paramName=clazzParam.getName();//对参数类型名进行处理
				//paramName=paramName.substring(paramName.lastIndexOf('.')+1);
				sBuilder.append(paramName).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----------");
		Constructor constructor =clazzProxy.getConstructor(InvocationHandler.class);
		// 方式一:通过创建InvocationHandler的子类,传递给构造函数的创建实例对象方法参数,来创建实例
		class MyInvocationHandler1 implements InvocationHandler{
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				
				return null;
			}
		}
		Collection proxy1 =(Collection)constructor.newInstance(new MyInvocationHandler1());
		System.out.println(proxy1.toString());
		proxy1.clear();//无异常
		//proxy1.size();//报错,空指针异常
		
		
		//方式二:利用匿名内部类来创建InvocationHandler子类对象
		Collection proxy2=(Collection)constructor.newInstance(new InvocationHandler(){
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				
				return null;
			}
		});
		
		//方式三:利用Proxy提供的静态newProxyInstance方法来一步到位的创建代理类实例对象
		Collection proxy3=(Collection)Proxy.newProxyInstance(
				//第一个参数,定义代理类的类加载器
				ProxyDemo.class.getClassLoader(),
				//第二个参数,代理类要实现的接口列表
				new Class[]{Collection.class},
				//第三个参数,代理类的构造函数的参数
				new InvocationHandler() {
					ArrayList target=new ArrayList();//指定目标
					@Override//复写invoke方法
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						long startTime=System.currentTimeMillis();//开始时间
						Object retval=method.invoke(target, args);//调用目标
						long endTime=System.currentTimeMillis();//结束时间
						//测试方法运行时间
						System.out.println(method.getName()+"running time:"+(endTime-startTime));
						return retval;		
					}
				});
		
		 //通过代理类调用目标方法,每调用一个目标的方法就会执行代理类的方法  
		//当调用一次add方法时,就会找一次InvocationHandler这个参数的invoke方法 
		proxy3.add("itheima");
		proxy3.add("itcast");
		proxy3.add("itshenma");
		System.out.println(proxy3.size());//3
		System.out.println(proxy3.getClass().getName());//com.sun.proxy.$Proxy0
	}
}

4、让JVM创建动态类及其实例对象,需要提供的信息:

        1)生成类中的哪些方法,通过让其实现哪些接口的方式进行告知。

        2)产生的类字节码必须有一个关联的类加载器对象

        3)生成的类中的方法的代码是怎么样的,也得由我们自己提供。把我们的代码写在一个约定好的子接口对象的方法中,把对象传给它,它调用我们的方法,即相当于插入了我们自己的代码。提供执行代码的对象就是InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的,在上面的InvocationHandler对象的invoke方法中,加一点代码就可以看到这些代码被调用运行了。

 

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

1、如上示例中,动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个接收InvocationHandler参数的构造方法。接受此对象的用处:在类中定义这个对象的引用,以便在需要时应用它。

2、实现Collection接口的动态类中的各个方法的代码的解析:

        InvocationHandler接口中定义的invoke方法接受三个参数的含义:

       第一、Client(客户端)程序调用objProxy.add(avc)方法时,涉及到了三个要素,分别为:objProxy对象,add方法,”avc”参数。代码如下:

class Proxy${
  add(Object obj){
      return handler.invoke(Object proxy, Method method, Object[] args);
   }
}

        第二、其中的Object proxy即为objProxy对象,Method method对应add方法,Object[] args就是”avc”参数。在使用newProxyInstance的方式创建代理对象实现时,当前正在调用的代理对象(Object proxy),调用当前对象的哪个方法(Method method),调用此对象方法时传入的参数(Object[] args)。

4、对于上面代码中的Object retVal = method.invoke(target,args)的分析:

        目标对象target执行完返回一个值为retVal,接着将值作为结果return回去,则代理方法就会收到一个返回值。其中还可以定义一个过滤器,对参数args(即当前对象的方法传入的参数)进行过滤(修改)。

5、对于上面代码中的proxy3.add(sdfd)的分析:

        1)代理对象调用add方法,传递了sdfd参数。

        2add方法内部会找到InvocationHandler中的invoke方法,将代理对象proxy传进去,把add方法传入,将“sdfd”参数传入代理对象中的handler参数,返回了一个结果给了add方法,add方法继续向外返回给调用的对象proxy3,即最终结果。

 

四、问题

1、在上面的方式一的代码中,调用无返回值的方法返回的是null,而调用有返回值方法的时候会报NullPointException异常的原因:

        在proxy1.size()方法中,size()会调用Object invoke()方法,而对其覆写时的返回值是null,而size()本身会返回int类型,两者返回的类型不相等,所以就会报错。

        而对于proxy3.size()不报错,是因为size()返回值与invoke()方法返回值是一致的,当前目标返回什么,代理就返回什么,所以不会报错。

注意:目标返回值和代理返回值必须是同一类型。

2、为何动态类的实例对象的getClass()方法返回了正确结果:

        因为只有调用代理对象从Object类继承的hashCode,equals,toString这几个方法时,代理对象会将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。所以对于getClass()方法,还是Object实例的原方法。即proxy3.getClass(),该是什么结果还是什么结果,并不会交给invoke方法处理。

实现代码如下:

$Proxy0 implements Collection
{
	InvocationHandler handler;
	public $Proxy0(InvocationHandler handler)
	{
		this.handler = handler;
	}
	//生成的Collection接口中的方法的运行原理
	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);
	}
}

 

五、总结分析动态代理类的统计原理和结构

1、怎样将目标传进去:

        1)直接在InvocationHandler实现类中创建目标类的实例对象,可看运行效果和加入日志代码,但是没有实际意义。

        2)为InvocationHandler实现类注入目标的实例对象,不能采用匿名内部类的形式了。

        3)让匿名内部类的InvocationHandler实现类访问外面的方法中的目标类实例对象的final类型的引用变量。

2、动态代理的工作原理:

        1Client(客户端)调用代理,代理的构造方法接收一个InvocationHandlerclient调用代理的各个方法,代理的各个方法请求转发给刚才通过构造方法传入的handler对象,又把各请求分发给目标的相应的方法。

示意图:

 

        2)将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接收目标,同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API

       在这里将InvocationHandler加入到Proxy的构造方法中,因此,在创建出来的对象,就会存有构造方法中InvocationHandler的一些功能和信息,因为我们把想要运行的代码封装在InvocationHandler对象,把它传入到构造函数中,那么就实现了代理对象每次调用与目标方法相同方法(因为实现了同一接口)时,都会调用我们加入到InvocationHandler对象中的代码。这就保证了每次调用代理时,可以在目标上加入我们自己加入的功能。

3、把系统功能代理模块化,即切面代码也改为通过参数形式提供,怎么把要执行的系统功能代码以参数的形式提供:

        1)把要执行的代码装到一个对象的某个方法中,然后把此对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外接提供的代码。

        2)为getProxy方法增加一个Advice参数。

示例:

package cn.itheima.demo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

public class ProxyDemo {

	public static void main(String[] args) throws Exception{
		final ArrayList target=new ArrayList();//指定目标
		Advice advice=new MyAdvice();//将要添加的代码封装成对象
		//使用Proxy提供的静态newProxyInstance方法来一步到位的创建代理类实例对象
		Collection proxy3 = (Collection)getProxy(target,advice);
		 
		proxy3.add("itheima");
		proxy3.add("itcast");
		proxy3.add("itshenma");
		System.out.println(proxy3.size());//3
	}

	//作为一个通用的方法,就使用Object  
	//传入一个目标,并传入一个接口,此接口作为通信的契约,才能调用额外的方法
	private static Object getProxy(final Object target,final Advice advice) {
		Object proxy3=Proxy.newProxyInstance(
				//第一个参数,定义代理类的类加载器
				target.getClass().getClassLoader(),
				//第二个参数,代理类要实现的接口列表,这里要与target实现相同的接口
				//new Class[]{Collection.class},
				target.getClass().getInterfaces(),
				//第三个参数,代理类的构造函数的参数
				new InvocationHandler() {
					
					@Override//复写invoke方法
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						
						//使用约定的对象中的方法
						advice.beforeMehod();
						Object retval=method.invoke(target, args);//调用目标
						advice.afterMethod(method);
						return retval;	
					}
				});
		return proxy3;
	}
}

import java.lang.reflect.Method;
//只要实现Advice中的方法,里面的代码功能可以随便定义,调用代理时就会被使用
public class MyAdvice implements Advice {
	long startTime;
	@Override
	public void beforeMehod() {
		System.out.println("学习结束。。。");
		startTime=System.currentTimeMillis();
	}

	@Override
	public void afterMethod(Method method) {
		System.out.println("开始学习...");
		long endTime=System.currentTimeMillis();
		System.out.println(method.getName()+"  running time:"+(endTime-startTime));
	}
}

import java.lang.reflect.Method;

/*接口中需要实现四个方法 
 * 调用目标方法之前 
 * 调用目标方法之后 
 * 调用目标方法前后 
 * 在处理目标方法异常的catch块中 
 */  
//这里用两个作为示例,创建Advice接口
public interface Advice {
	void beforeMehod();
	void afterMethod(Method method);
}

 

第三讲     实现AOP功能的封装与配置

一、工厂类BeanFactory

1、工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。

2getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则返回该类示例对象的getProxy方法返回的对象。

3BeanFactory的构造方法接收代表配置文件的输入流对象的配置文件格式如下:

        #xxx=java.util.ArrayList

        xxx=cn.itcast.test3.aopframework.ProxyFactoryBean

        xxx.advice=cn.itcast.test3.MyAdvice

        xxx.target=java.util. ArrayList

注意:其中的#代表注释当前行。

4ProxyFactoryBean充当封装成动态的工厂,需为工厂提供的配置参数信息包括:

        目标(target  

        通知(advice

5BeanFactoryProxyFactoryBean

        1BeanFactory是一个纯粹的bean工程,就是创建bean即相应的对象的工厂。

        2ProxyfactoryBeanBeanFactory中的一个特殊的Bean,是创建代理的工厂。

 

二、实现类似spring的可配置的AOP框架的思路

1、创建BeanFactory类:

        1)构造方法:接受一个配置文件,通过Properties对象加载InputStream流对象获得。

        2)创建getBean(String name)方法,接收Bean的名字,从上面加载后的对象获得。

        3)通过其字节码对象创建实例对象bean

        4)判断bean是否是特殊的BeanProxyFactoryBean,如果是,就要创建代理类,并设置目标和通告,分别得到各自的实例对象,并返回代理类实例对象。如果不是在返回普通类的实例对象。

2、创建ProxyFactoryBean(接口),此处直接定义为类做测试,其中有一个getProxy方法,用于获得代理类对象。

3、编写实现Advice接口的类和在配置文件中进行配置。

4、定义一个测试类:AopFrameworkTest,也称客户端,调用BeanFactory获取对象。

示例:

配置文件信息:

        xxx=java.util.ArrayList

        xxx=cn.itheima.demo.aopframework.ProxyFactoryBean

        xxx.target=java.util.ArrayList

        xxx.advice=cn.itheima.demo.MyAdvice

 
package cn.itheima.demo.aopframework;

import java.io.InputStream;
import java.util.Collection;
//测试类
public class AopFrameworkTest {

	public static void main(String[] args) {
		//用输入流关联配置文件
		InputStream in=AopFrameworkTest.class.getResourceAsStream("config.properties");
		Object bean =new BeanFactory(in).getBean("xxx");//获取bean对象
		System.out.println(bean.getClass().getName());//打印获得的bean对象
		((Collection)bean).toString();//测试使用
	}
}


import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import cn.itheima.demo.Advice;
//创建BeanFactory类,用于创建目标类或者代理类的实例对象。
public class BeanFactory {
	Properties properties=new Properties();//定义properties集合
	public BeanFactory(InputStream in){
		try {
			properties.load(in);
		} catch (IOException e) {
			
			e.printStackTrace();
		}
	}
	//从配置文件中获取对象
	public Object getBean(String name){
		String className=properties.getProperty(name);//获取配置文件中的值
		Object bean=null;
		try {
			Class clazz=Class.forName(className);//通过反射创建对象
			bean=clazz.newInstance();
		} catch (Exception e) {
			
			e.printStackTrace();
		} 
		//如果创建的对象是ProxyFactoryBean类型,则通过getProxy方法获取代理类对象
		if (bean instanceof ProxyFactoryBean) {
			Object proxy=null;
			//已知bean的类型
			ProxyFactoryBean proxyFactoryBean=(ProxyFactoryBean)bean;
			try {
				//从配置文件中获取代理类额外添加的代码封装成的对象
				Advice advice=(Advice)Class.forName(properties.getProperty(name+".advice")).newInstance();
				//从配置文件中获取目标
				Object target=Class.forName(properties.getProperty(name+".target")).newInstance();
				proxyFactoryBean.setAdvice(advice);
				proxyFactoryBean.setTartget(target);
				//调用getProxy方法,获取代理对象
				proxy= proxyFactoryBean.getProxy(target, advice);
			} catch (InstantiationException | IllegalAccessException
					| ClassNotFoundException e) {
				
				e.printStackTrace();
			}
			return proxy;
		}
		return bean;
	}
}


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import cn.itheima.demo.Advice;
//创建ProxyFactoryBean类,用于产生代理类实例对象
public class ProxyFactoryBean {
	private Advice advice;
	private Object target; 
	public Advice getAdvice() {
		return advice;
	}
	public void setAdvice(Advice advice) {
		this.advice = advice;
	}
	public Object getTartget() {
		return target;
	}
	public void setTartget(Object tartget) {
		this.target = tartget;
	}
	public Object getProxy(final Object target,final Advice advice) {
		Object proxy3=Proxy.newProxyInstance(
				//第一个参数,定义代理类的类加载器
				target.getClass().getClassLoader(),
				//第二个参数,代理类要实现的接口列表,这里要与target实现相同的接口
				//new Class[]{Collection.class},
				target.getClass().getInterfaces(),
				//第三个参数,代理类的构造函数的参数
				new InvocationHandler() {
					
					@Override//复写invoke方法
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						
						//使用约定的对象中的方法
						advice.beforeMehod();
						Object retval=method.invoke(target, args);//调用目标
						advice.afterMethod(method);
						return retval;	
					}
				});
		return proxy3;
	}
	
}
---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值