黑马程序员_代理和AOP(一)

/**
* 代理和AOP(一)
*
* 1、分析代理类的作用与原理及AOP的概念
*
* 1.1 代理类:一个代理类通常有自己的代理目标类,代理类是对目标类的代理,一般代理类的方法和目标类的方法签名一致,是对
* 目标类的包装,代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标的方法返回的结果之外,
* 还可以在代理类的方法中 加入一些系统功能代码,添加的位置有四个地方:
* 分别是目标方法调用前,目标方法调用后,目标方法调用前后,在处理目标方法异常的catch块中。
* 例如:
* class Proxy //代理类
* {
*   void sayHello()
*    {
*      ........ //功能代码
*      try
*      {
*         target.method() //目标方法的调用
*      }catch(Exception e)
*      {
*        ........ //功能代码
*      }
*     .......... //功能代码
*    }
* }
*
* 代理类的背景:要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如异常处理、日志、计算方法的运行时间、
* 事物管理等等,在你拿不到已存在类的源代码的时候,也就是不允许修改已存在类的源代码,这时候该怎么做?使用代理类
*
* 代理类的作用:代理技术的一个主要应用就是在AOP(Aspect Oriented Programming)中,扩展了目标类的功能。
*
* 代理类在程序中的应用原理:
* 客户端程序 目标类 代理类 接口
* 在设计时,要使目标类和代理类实现相同的接口,这样才可以在写客户端程序的时候采用面向接口(父类)的方式编程
* ,面向父类或者接口编程的好处是,当采用工厂设计模式和配置文件的方式进行管理的时候,就不需要修改已经编写好的
* 客户端程序,如果想要切换目标类到代理类,只需要在配置文件中配置一下就可以了,比如,想要日志功能时,就配置
* 代理类,不想要时就配置目标类,这样为程序增加系统功能很容易,去掉系统功能也很容易。
*
* 代理类可以采用自定义的方式,即手动写一个类A的代理类B,但是当要为系统中各个接口的类增加系统功能,即为这些类写代理类,如果全部
* 采用静态代理的方式,为程序中的成百上千个类写代理类,就会非常麻烦。
* 所以JDK提供了动态生成代理类的方式。
*
* 1.2 动态类:这种动态生成代理类的方式基于的技术是生成动态类,即在程序运行期间由JVM在内存中构造出一份字节码,
* 也就是一个类。动态生成的类可以被用来创建对象,可以用来做其他的用途,作为代理类只是其用途之一。
* JVM生成的动态类必须实现一个或者多个接口,所以动态类只能用作具有相同接口的目标类的代理,对于没有实现任何接口的类,
* 可以采用第三方类库,CGLIB库,CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以可以使用
* CGLIB库为一个没有实现任何接口的类生成动态代理类。
*
*
* java.lang.reflect.Proxy :Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
* 创建一个动态类:
* public static Class getProxyClass(ClassLoader loader,Class<?> ...interfaces)
* //该方法返回动态类的Class对象,即字节码文件。需要指定生成的动态类的类加载是谁,以及这个动态类要实现的接口,可以是多个接口
* //生成的动态类是不需要类加载加载的,也就是说本质上讲它没有类加载器,但是为了统一,需要为其指定一个类加载器,通过传递参数的
* //方式指定,传递的参数是什么,它的类加载器就是什么。
*
* 创建的动态类的类名一般为:com.sun.proxy.$Proxy0 、com.sun.proxy.$Proxy1、com.sun.proxy.$Proxy2 之类
*
* 该动态类中只有一个构造方法,并且是带有参数的:
* com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
* //该动态类不存在无参数的构造方法,所以不能使用Class.newInstance()创建对象
* //否则会抛出实例化异常java.lang.InstantiationException
*
* 该动态类中的方法:继承自Object的方法、继承自Proxy的方法、实现的接口Collection的方法
*
* InvocationHandler接口:是代理类的构造方法要接受的实例对象的类要实现的接口,接口中只有一个方法是invoke。
*
* Object invoke(Object proxy, Method method, Object[] args) //在代理类的实例对象上处理方法调用并返回结果
* 。
* //proxy:就是被调用的代理类实例,method:代理对象被调用的方法,args:代理对象被调用方法的实际参数
*
* //利用反射在代理类proxy的目标类target上调用method方法,method方法的参数是args。
* //当调用代理类的实例的方法时,就会将该方法调用请求转发到构造时传入的InvocationHandler对象的Invoke方法上,由
* //InvocationHandler对象的Invoke方法来处理调用请求,所以InvocationHandler中文意思是“调用处理者”,将该代理类的
* //实例和调用的方法以及方法的参数传入到invoke中。
* InvocationHandler的子类,一般由程序员自定义,一般采用匿名内部类的方式定义,并且在内部类中定义代理类的目标类的引用(为了通用,
* 定义为Object类型,这样就可以接受任何目标类的对象了),以InvocationHandler构造函数的参数的形式传入目标类的对象。
* 在invoke方法中再来调用传入的目标对象的对应方法。并将目标对象的对应方法的返回值返回回去。invoke的返回值就会作为
* 代理对象的方法的返回值返回。
*
* 调用流程图及伪代码 :
* InvocationHandler handler = new MyInvocationHandler(目标类对象);
* proxy = new 代理类对象(InvocationHandler handler);
* proxy.method1(); <==> handler.invoke(proxy,method1,args) ---> target.method1(args);
*
* 代理类(proxy) ---------> InvocationHandler实现类(匿名对象) ------> 目标类(target)
* {             { Object target;              {
*    method1(){} -->      invoke(proxy,method,args)   -->    method1(){}
*                {
*      method2(){} -->      target.method(args);   -->     method2(){}
*                }
* }             }                     }
*
* 1.3 AOP(Aspect Oriented Programming):即是面向切面编程,或者面向方面编程
*
* AOP产生的背景:在软件开发中,会发现不同的模块被同一个交叉业务所贯穿,即不同的模块的某些方法中的代码有相似的部分,
* 也就是一系统功能(交叉业务)的代码需要在不同的模块中的某些方法中来实现,即这个系统功能不是独立的一个类,而是
* 一些分散在其他模块方法中的代码的集合,这些代码们看上去像一个切面,为了解决这个切面的问题,
* 即将切面代码抽离出来,形成单独的功能模块,封装到一个类中,就有了面向切面编程AOP。
*
* 专业回答:系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下:
* 安全 事务 日志 (各个交叉业务、系统功能)
* StudentService ------|-------|-------|------------------ (模块1)
* CourseService  ------|-------|-------|------------------ (模块2)
* MiscService     ------|-------|-------|------------------ (模块3)
*
*
*
* 用具体的程序代码描述交叉业务现象,如下:
*
* method1 method2 method3 原各个目标类的方法
* {     {     {
* -------------------------------------------------------- 切面(交叉业务)
*  ....     ....     ....          目标类中的代码
* -------------------------------------------------------- 切面(交叉业务)
* }      }     }
*
* 这种交叉业务的存在,使得系统中不同位置有若干功能相似的代码出现,而且对于功能切换很不方便,添加和去除一些系统功能时,
* 需要修改源代码,一切都归结于这个交叉业务(这个系统功能)没有模块化,没有被封装成一个类。
*
* 交叉业务的编程问题就是面向方面的编程(AOP)要解决的问题,AOP的目标就是要使交叉业务模块化,可以采用将切面代码
* 移动到目标类的原始方法的周围的方法来实现交叉业务模块化,这与直接在方法中编写切面代码的运行效果是一样的。如下:
*
* -------------------------------------------------------- 切面(交叉业务)
* method1 method2 method3 原各个目标类的方法
* {     {      {
*
*  ....     ....     .... 目标类中的代码
*
* }     }     }
* -------------------------------------------------------- 切面(交叉业务)
*
* 使用代理技术正好可以解决这种切面问题,代理是实现AOP功能的核心和关键技术
*
*/

package com.itheima;

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 AOPDemo {

	public static void main(String[] args)throws Exception {

		Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);//JVM创建动态类的字节码对象
		System.out.println(clazzProxy.getName());
		System.out.println(clazzProxy.getClassLoader());//打印出代理类的类加载器,指定的是Collection的类加载器,
														//是顶级类加载器,所以打印是null。顶级类加载器不是java类,是
														//C++编写的类
		Constructor[] constructors = clazzProxy.getConstructors();//反射获得构造方法
		System.out.println("----------构造方法列表----------------");
		for(Constructor constructor : constructors) //打印所有构造方法列表
		{
			String constructorName =constructor.getName();
			StringBuilder sBuilder = new StringBuilder(constructorName);
			sBuilder.append(‘(‘);
			Class[] clazzParameters = constructor.getParameterTypes();//获得该构造方法的参数列表中的各个参数的类型
			for(Class clazzParameter : clazzParameters)
			{
				sBuilder.append(clazzParameter.getName()).append(‘,‘);
			}
			if(clazzParameters!=null && clazzParameters.length != 0)
				sBuilder.deleteCharAt(sBuilder.length()-1);
			sBuilder.append(‘)‘);
			System.out.println(sBuilder);
		}

		Method[] methods = clazzProxy.getMethods(); //获得所有方法
		System.out.println("----------方法列表----------------");
		for(Method method : methods) //打印所有方法列表
		{
			String methodName =method.getName();
			StringBuilder sBuilder = new StringBuilder(methodName);
			sBuilder.append(‘(‘);
			Class[] clazzParameters = method.getParameterTypes(); //获得该方法的参数列表的各个参数的类型
			for(Class clazzParameter : clazzParameters)
			{
				sBuilder.append(clazzParameter.getName()).append(‘,‘);
			}
			if(clazzParameters!=null && clazzParameters.length != 0)
				sBuilder.deleteCharAt(sBuilder.length()-1);
			sBuilder.append(‘)‘);
			System.out.println(sBuilder);
		}
		//Collection p = (Collection) clazzProxy.newInstance(); //错误代码,报异常

		//创建动态类的实例对象
		System.out.println("----------创建实例对象----------------");

		//定义代理对象的调用处理程序类
		//在调用处理程序类中指定目标类和要添加的系统功能类,使得这个InvocationHandler类对象可以应用在任何代理类上
		//使用时只需在InvocationHandler的构造方法中传入要被代理类代理的目标类的对象,和要在目标对象各个方法上添加
		//的系统功能的对象即可。目标类必须和代理类实现了相同的接口,这样调用时才会找到相应的方法,才不会出错。
		class MyInvocationHandler implements InvocationHandler
		{
			Object target ; //
			Advice advice ;
			MyInvocationHandler(Object target,Advice advice)
			{
				this.target = target ;
				this.advice = advice ;
			}
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {

				advice.beforeMethod();

				Object retVal = null;
				try
				{
					System.out.print("目标类被调用了      :::  ");
					retVal = method.invoke(target, args);
				}catch(Exception e)
				{
					advice.exceptionCatch();
				}

				advice.endMethod();
				return retVal;
			}
		}
		Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);//JVM创建动态类的字节码对象

		Constructor  constructor = clazzProxy1.getConstructor(InvocationHandler.class);//用代理类的字节码获得构造方法

		Object target = new ArrayList();//创建目标类对象,该目标类必须和代理类实现相同接口,目标类是被代理类所实现的接口限制的

		Advice advice = new MyAdvice();//创建你想添加的系统功能类的对象,通过对象方法调用的方式还添加想要被执行的代码

		InvocationHandler handler = new MyInvocationHandler(target,advice);//创建调用处理程序实例对象

		Collection proxy = (Collection) constructor.newInstance(handler);//绑定该调用处理程序对象到指定的代理对象上

		//一个代理类可以代理多个目标类,代理类可以代理与其实现相同接口的那些目标类,这些目标类具体是代理哪个,由handler指定

		proxy.add("zhangsan");//调用代理对象方法
		proxy.add("lisi");
		proxy.add("wangwu");
		//proxy.lastIndexOf("lisi"); // ArrayList中有lastIndexOf方法,但是proxy中有的是Collection接口中的方法,即proxy没有该方法,报错

		System.out.println(AOPDemo.class.getClassLoader()); //打印内部类所在类的类加载器
		System.out.println(handler.getClass().getClassLoader()); //内部类是由内部类所在类的类加载器加载的

		for(Object str : proxy)
		{
			System.out.println(str);
		}

	}

}
interface Advice	//通告接口,系统功能的统一接口
{
	void beforeMethod();//在目标方法前添加系统功能代码
	void endMethod();	//在目标方法后添加系统功能代码
	void exceptionCatch();//在处理目标方法的异常catch块中添加系统功能代码
}

//定义一个的通告,定义一个系统功能
class MyAdvice implements Advice
{
	long  time ;
	public void beforeMethod() {
		System.out.println("开始计算运行时间");
		time = System.currentTimeMillis(); //添加要加入的功能代码,这段代码就是从目标类中的各个方法中抽离出来的交叉业务代码
	}

	public void endMethod() {
		time = time - System.currentTimeMillis();	//添加要加入的功能代码,这段代码就是从目标类中的各个方法中抽离出来的交叉业务代码
		System.out.println("运行时间   :"+time);
		System.out.println("结束计算运行时间");
	}

	public void exceptionCatch() {

		System.out.println("目标出现了异常");//添加要加入的功能代码,这段代码就是从目标类中的各个方法中抽离出来的交叉业务代码

	}

}

  

时间: 2024-08-23 09:05:45

黑马程序员_代理和AOP(一)的相关文章

黑马程序员_ 利用oc的协议实现代理模式

先说下代理模式是什么吧 定义: 为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象 而代理对象可以在客户端和目标对象之间起到中介的作用. 在看过李明杰老师的课程后,我对代理模式有了最初步的理解,虽然还很浅显 但是也明白了代理模式的 一些作用跟用法.首先使用代理模式可以降低耦合度.大大的增强了代码的弹性. 举个例子,小明想看电影,但是没时间买票 于是就拜托小强去买票 最简单的方式就是 建立一个person类(小明) 一个agent类(代理类) ag

黑马程序员_高新技术_1_Java反射

------- android培训.java培训.期待与您交流! ---------- 0.反射知识体系 下图为反射整体的知识体系,把握住此图也就全局上掌握住反射所有内容. 1.反射概论 1)反射概念 其实字面上可以这么理解反射,平时使用类时都是由类new出对象,而反射则是通过对象"反射"出类的信息,好比一个人照镜子可以看到人类的特征,而看出机制就是镜子反射. 2)Java对象两种类型 Java程序中的许多对象在运行时会出现两种类型:编译时类型和运行时类型.如下代码: Person p

黑马程序员_集合

集合1.集合和对象数组的区别: 数组的长度不可变,集合的可变: 数组可以存储基本数据类型和对象,集合只能存储对象. 集合的框架图 集合派系的顶层接口Collection1.Collection集合存储对象的方法: add(E e)将元素存储到集合中 addAll(Collection c)将一个集合添加到另外的集合中2.Collection集合提取对象的方法: 通过迭代器iterator中的方法:hasNext()和next()来取出 Iterator it=new iterator(); wh

黑马程序员_学习IOS之字典常用的方法

字典是无序的 数组是有序的.字典分为:可变字典和不可变字典  不可变字典对象 NSDictionary * dict = [[NSDictionary alloc]initWithObjectsAndKeys:@"one",@"1",@"two",@"2",@"three",@"3",@"four",@"4", nil]; //value = ke

黑马程序员_多线程

------- android培训.java培训.期待与您交流! ---------- 多线程1.进程: 正在运行的程序所占有的内存空间,叫做进程. 线程: 一个应用程序中的子程序,对于CPU,子程序可以有一条独立的执行路径,称为线程. 线程特点:依靠一个应用程序,对于其他子程序,独立被CPU执行的2.多线程的好处: 充分利用CPU的资源,程序的同时运行,提高效率3.java中线程创建的两种方法: 第一种: 定义类继承Thread class extends Thread 重写Thread中的r

黑马程序员_交通灯管理系统

(1)项目的需求 模拟实现十字路口的交通灯管理系统逻辑,具体需求如下: 例如: 由南向而来去往北向的车辆 右转车辆 由东向而来去往南向的车辆 ---- 左转车辆 平时开车过十字路口红绿灯的时候,也知道红绿灯运行的顺序 (1)任何方向的车,向右边转弯的时候,是不需要看红绿灯 (2)在十字路口,相对方向的红绿灯的工作方式是一样的,南相对与北,东相对与西,这把它分成两对 (3)红绿灯顺序,一对直行通道绿灯直行车辆,等直行变红,还是这对的车辆可以左转,等左转变红,就轮到下一对了.所以在设计程序的时候,只

黑马程序员_银行业务调度系统

1,项目的具体需求 银行业务调度系统 模拟实现银行业务调度系统逻辑,具体需求如下: 银行内有6个业务窗口,1 - 4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口. 有三种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费.电话费之类业务的客户). 异步随机生成各种类型的客户,生成各类型用户的概率比例为: VIP客户 :普通客户 :快速客户 = 1 :6 :3.   客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户办理业务所需的时间,快速

黑马程序员_构造函数间的调用

这是我的第一篇博客,从这篇开始我将记录下学习java的心得体会,欢迎志同道合的朋友随时与我讨论,共同进步. 我们都知道,函数间可以互相调用.构造函数是一种特殊的函数,它用来给对象进行初始化,如果想在构造构造函数中调用另一个构造函数,分为以下两种情况: 1.子类调用父类的构造函数 先来看一段代码 1 class Person 2 { 3 public Person() 4 { 5 // 构造语句 6 } 7 } 8 9 class Student extends Person 10 { 11 pu

黑马程序员_毕向东_Java基础视频教程_Java基础学习知识点总结

黑马程序员_毕向东_Java基础视频教程 Java基础学习知识点总结 2016年01月06日  day01 一.基础知识:软件开发 1.什么是软件?软件:一系列按照特定顺序组织的计算机数据和指令的集合. 2.常见的软件:系统软件:如:DOS,Windows,Linux等.应用软件:如:扫雷,迅雷,QQ等. 3.什么是开发?制作软件. 二.基础知识:人机交互方式 4.软件的出现实现了人与计算机之间的更好的交互. 5.交互方式:图形化界面:这种方式简单直观,使用者易于接受,容易上手操作.命令行方式: