黑马程序员—Java基础加强(动态代理) - Go语言中文社区

黑马程序员—Java基础加强(动态代理)


------- android培训java培训、期待与您交流! ----------

 

 

代理:

假如要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?

编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。

 

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

 

 

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

                              安全       事务        日志

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

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

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

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

method1         method2          method3

{                      {                       {

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

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

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

}                       }                       }

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

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

func1         func2            func3

{             {                {

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

}            }                }

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

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

 

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

重要原则:不要把供货商暴露给你的客户

 

 

动态代理技术

1、要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!

2JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。

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

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

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

1)在调用目标方法之前

2)在调用目标方法之后

3)在调用目标方法前后

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

 

 

分析JVM动态生成的类:

思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息?

三个方面:

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

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

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

newProxyInstance()方法直接一步就创建出代理对象。

 

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

 

1、怎样将目标传进去:

 

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

 

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

 

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

 

动态代理的工作原理:

1Client(客户端)调用代理,代理的构造方法接受一个InvocationHandlerclient调用代理的各个方法,代理的各个方法请求转发给刚才通过构造方法传入的handler对象,又把各请求分发给目标的相应的方法。就是将handler封装起来,其中this引用了当前的放(发来什么请求就接受哪个方法)

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

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

2、构造方法接受一个InvocationHandler对象,接受对象了要干什么用呢?该方法内部的代码会是怎样的呢?

实现Collection接口的动态类中的各个方法的代码又是怎样的呢?InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?图解说明如下:

 

分析为什么动态类的实例对象的getClass()方法返回了正确结果呢?

为何动态类的实例对象的getClass()方法返回了正确结果,而没调用invoke方法:

因为代理类从Object上继承了许多方法,其中只对三个方法(hashCodeequalstoString)进行开发,委托给handler去自行处理,对于它身上其他方法不会交给代理类去实现,所以对于getClass()方法,还是由Object本身实现的。即proxy3.getClass(),该是什么结果还是什么结果,并不会交给invoke方法处理。

 

代码示例以及注释:

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 ProxyTest {
	public static void main(String[] args) throws Exception {
		
		//通过Proxy类的getProxyClass方法得到一个动态代理类的字节码,指定了代理类的类加载器和要实现的接口
		Class classProxy =
				Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//		System.out.println(classProxy.getName());
		
		//打印提示 "开始列出构造函数列表"
		System.out.println("----------begin constructors list----------");
		
		//通过getConstructor方法获取动态代理类字节码的构造函数列表,存入构造函数数组
		Constructor[] constrs = classProxy.getConstructors();
		
		//对构造函数数组进行迭代
		for (Constructor constr : constrs) {
			
			//迭代出的每一个具体构造函数的对象,就获取该方法的名称,用name变量记录
			String name = constr.getName();
			
			//创建一个字符串缓冲区,并初始化为指定的name构造函数名称的内容
			StringBuilder strb = new StringBuilder(name);
			
			//在每个构造函数名的后面追加一个 "(" 符号
			strb.append("(");
			
			//通过单个构造函数对象,获取它的参数类型的列表,并存入Class数组
			Class[] classParameters = constr.getParameterTypes();
			
			//迭代每一个构造函数的参数类型列表
			for (Class classParameter : classParameters) {
				
				//添加每个构造函数中的参数类型名称到字符串缓冲区,每添加一个参数类型名称,就在那个参数名称后面加上一个 ","
				strb.append(classParameter.getName()).append(",");
			}
			//判断只要构造函数的参数类型数组的长度大于0,就表示有参数类型。符合这个条件就执行下一行操作
			if (classParameters.length > 0)
				
				//符合上一行代码条件,则执行,将最后一个参数类型名称后面的 "," 去掉
				strb.deleteCharAt(strb.length()-1);
			
			//构造函数的参数类型名称添加完之后,需要再加上一个  ")"
			strb.append(")");
			
			//最后打印这个字符串缓冲区中的内容
			System.out.println(strb.toString());
		}
		
		//打印提示信息  "开始列出方法列表" 
		System.out.println("----------begin methods list----------");
		
		//通过动态代理类的字节码获取该类的所有方法对象,存入Method方法数组
		Method[] methods = classProxy.getMethods();
		
		//对方法数组进行迭代操作
		for (Method method : methods) {
			
			//迭代出的每一个具体方法对象,就获取该方法的名称,用name变量记录
			String name = method.getName();
			
			//创建一个字符串的缓冲区,初始化为迭代出的方法的名称
			StringBuilder strb = new StringBuilder(name);
			
			//在方法名的后面加上  "("
			strb.append("(");
			
			//通过方法对象的getParameterTypes方法来获取单个方法里所有的参数类型列表,用Class数组记录
			Class[] classParameters = method.getParameterTypes();
			
			//对参数类型数组进行迭代,获取每一个具体的参数类型对象
			for (Class classParameter : classParameters) {
				
				//得到每一个具体的参数类型对象后,获取该参数类型的名称,追加到字符串缓冲区,然后继续追加一个 ","
				strb.append(classParameter.getName()).append(",");
			}
			//判断如果参数类型数组不为空,就执行下一行代码
			if (classParameters.length != 0)
				
				//符合上行代码条件,就执行去掉参数类型列表最后一个参数类型后面的  ","
				strb.deleteCharAt(strb.length()-1);
			
			//然后追加一个 ")"
			strb.append(")");
			
			//最后打印这个字符串缓冲区中的内容
			System.out.println(strb.toString());
		}
		
		
		//打印提示信息 "begin create instance object"
		System.out.println("------begin create instance object------");
		//该动态代理类没有无参数的构造方法,所以不能用newInstance方法来实例化对象
//		classProxy.newInstance();
		//通过动态代理类的字节码对象,获取那个带InvocationHandler参数类型的构造函数
		Constructor constructor = classProxy.getConstructor(InvocationHandler.class);
		//用获取到的构造函数实例化出一个对象。以内部类的形式传入参数 InvocationHandler的子类对象
		Collection proxy = (Collection) constructor.newInstance(new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				// TODO Auto-generated method stub
				return null;
			}
		});
		
		//打印得到的实例对象
		System.out.println(proxy);
		//调用没有返回值的方法,则没有问题
//		proxy.clear();
		//调用有返回值的方法,就会出错
//		System.out.println(proxy.size());
		
				
		
		//创建一个目标类对象
		final ArrayList target = new ArrayList();
		//调用getProxy方法,获取动态代理类对象。传入目标对象参数和一个需要被动态代理执行的系统功能对象
		Collection proxy1 = (Collection) getProxy(target, new MyAdvice());
		
		//调用动态代理对象的add方法,添加一系列元素
		proxy1.add("itheima");
		proxy1.add("itcast");
		proxy1.add("ZOL");
		proxy1.add("CSDN");
		//打印调用动态代理对象的size方法的结果
		System.out.println(proxy1.size());
	}

	//getProxy方法,该方法返回一个动态代理类的对象
	private static Object getProxy(final Object target, final Advice advice) {
		//通过Proxy类的newProxyInstance方法获取到一个动态代理类,传入类加载器,接口列表,还有InvocationHandler对象。
		Collection proxy1 = (Collection) Proxy.newProxyInstance(
				
				//目标对象类的的类加载器
				target.getClass().getClassLoader(),
				
				//接口列表
//				new Class[]{Collection.class},
				//目标对象的接口列表数组
				target.getClass().getInterfaces(),
				
				//用内部类的形式创建一个InvocationHandler的子类对象
				new InvocationHandler() {
					
					//重写invoke方法,该方法需要传入动态代理类对象,和动态代理类对象调用的方法,以及方法所需要的参数
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {

						//调用工具类对象的beforeMethod方法
						advice.beforeMethod(method);
						
						//动态代理类对象被调用的方法,会将请求转给InvocationHandler对象
						//InvocationHandler对象又将请求转给目标对象的相应方法,然后传入要操作的参数
						Object retValue = method.invoke(target, args);
						
						//调用工具类对象的afterMethod方法
						advice.afterMethod(method);
						
						//返回调用目标方法的结果
						return retValue;
					}
					
			    });
		//返回动态代理类的对象
		return proxy1;
	}
}


 

------- android培训java培训、期待与您交流! ----------
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/caihongxiuyujay/article/details/23856991
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-29 20:21:49
  • 阅读 ( 896 )
  • 分类:职场

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢