——java动态代理及AOP

代理的概念及作用

   代理模式的概念:

为其他对象提供一种代理以控制对这个对象的访问。

说白了就是,在一些情况下客户不想或者不能直接引用一个对象,
而代理对象可以在客户和目标对象之间起到中介作用,去掉客户不能看到的内容和服务或者增添客户需要的额外服务。

   生活中的代理:

       一,买东西可以从代理商那里买,也可以从厂商那里卖。

       但目的都是要得到商品。从代理商那里买显然更方便。

   程序中的代理:

     1,要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能

   例如,异常处理,日志,计算方法的运行时间,事务管理等等。。

     2,编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类

   的相同方法,并在调用方法时加上系统功能的代码(参见原理图)

  原理图:

     3,如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,

   在配置文件中配置使用用的目标类,还是代理类。这样可以很容易切换,

  譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易

  以后运行一段时间后,又想去掉系统功能也很容易。

AOP(Aspect Orlented Program 即:面向方面编程,简称AOP):

什么是AOP?

将一切实物都抽象的看做是多个实体的抽象体,而每个不同类型的抽象体都能够作为这个实物的一种实现机制的表现,
从而在业务拓展时减少对原有代码的维护,取而代之的则是 增加->切换 的操作。

AOP的优势:

相对于OOP,面向AOP更具有可拓展性和高维护性的优势。

具体表现在:以往我们都以“世界万物皆对象”的思想进行编程时,会将一切事物抽象成一个实体,并使用这个实体进行我们业务方面的拓展。

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

   如下所示:

                安全    事务    日志

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

CurseService   -----|-------|-------|----

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

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

method  method     method

{         {    {

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

。。。   。。。      。。。

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

}  }    }

  三,交叉业务的编程问题即为面向方面的编程(Aspect Orlented Program 简称AOP),

   AOP的目标就是要是交叉业务模块。可以采用切面代码移动到原始方法的周围,这与直接在方法

   中编写切面代码的运行效果是一样的。

   如下所示:

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

func1 func2 func3

{ {{

... ... ...

} }}

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



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



动态代理技术

   一,要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理的方式,

是一件很麻烦的事情。

   二,JVM可以在运行时动态的生成出类的字节码,这种动态生成的字节码问问被用作代理类,及动态代理类。

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

   四,CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现

接口的类生成动态代理类,那么可以使用CGLIB库。

   五,代理类的各种方法中通常除了药调用目标类的相应方法和队伍返回目标返回结果外,还可以在代理方法中的

如下四个位置加上系统功能代码:

1,在调用目标方法之前

2,在调用目标方法之后

3,在调用目标方法前后

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

分析JVM动态生成的类

   一,创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的更改参数。

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

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

   四,创建动态的实例对象

1,用反射获得构造方法

2,编写一个最简单的InvocationHandler接口的实现类

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

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

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

让JVM创建动态类,需要给它提供如下信息:

   三个方面:

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

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

3,生成的类中的方法代码是怎么样的,也是自定义的,把我们的代码写在一个约定好了接口对象的方法中。

  把对象传给它,它调用自定义的方法,相当于插入了自定义的代码,提供执行代码的对象就是那个InvocationHandler

  对象,它是在创建动态类的实例对象的构造方法时传递进去的,在上面的InvocationHandler对象的invoke得到中加了

  一些代码,就可以看到执行代码被调用运行了。

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

完整的实例代码如下:

package com.itheima.proxydemos;

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;

/**
 * 动态代理类的操作
 * 编写可生成代理和插入通告的通用方法。
 * @author wuyong
 *
 */
public class ProxyTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		//通过代理获取动态生成的类的class字节码
		Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		System.out.println("生成代理类的名称:" + clazzProxy1.getName());
		
		//输出所有的可见构造函数信息
		System.out.println("--------------Constructors---------------");
		showConstructors(clazzProxy1);//输出结果是:只有一个带参的构造方法
		
		//输出所有的可见方法信息
		System.out.println("--------------Methods---------------");
		showMethods(clazzProxy1);
		
		System.out.println("----------------new instance object--------------------");
		//通过获取到的字节码,实例化一个Collection对象
		Constructor con = clazzProxy1.getConstructor(InvocationHandler.class);
		//因为InvocationHandler是一个接口,所有不能直接实例化,故,要定义一个其实现类
		class MyInvocationHandler implements InvocationHandler{
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				return null;
			}
			
		}
		/**
		 * 方式一:用实现类的对象来实例化
		 */
		System.out.println("------------方式一:用实现类的对象来实例化------------");
		Collection col1 = (Collection)con.newInstance(new MyInvocationHandler());
		System.out.println(col1.toString());//结果为空,是因为toString()方法中的返回值是null.若果col为空的话,则肯定会报空指针异常
		
		//可以调用无返回值的方法。因为invoke方法返回值是null,而无返回值的方法与返回值没关系,所以可以执行。
		col1.clear();
		System.out.println("asd");
		
		//不可以调用有返回值的方法,
		//因为此时在调用invoke()方法时,其执行后返回的是null,而size()返回的是一个整数。
//		col1.size();//包空指针异常
		
		
		/**
		 * 方式二:用匿名内部类的方式来实例化
		 */
		/*Collection col2 = (Collection)con.newInstance(new InvocationHandler(){
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				return null;
			}
			
		});*/
		
		/**
		 * 方式三:用Proxy类的静态方法newProxyInstance(ClassLoader loader, Class
   
   [] interfaces, InvocationHandler h) 
		 * 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
		 * 如果将对象放置在此处,则每次执行对应的方法,就可以保证,每次都是对同一个ArrayList对象进行操作
		 */
		System.out.println("--------------测试动态代理---------------");
		ArrayList target = new ArrayList();
		Collection col3 = (Collection)getProxy(target,new ImplAdvice());
		col3.add("abc");
		col3.add("abc");
		col3.add("abc");
		System.out.println(col3.size());
		for (Object col : col3) {
			System.out.println("集合元素是:" + col);
		}
		
		System.out.println("--------------自定义类的动态代理---------------");
		ProxyClassUtil pcu = new ProxyClassUtil();
		ProxyInterface it = (ProxyInterface)getProxy(pcu, new ImplAdvice());
		it.show();
	}

	/**
	 * 可生成代理和插入通告的通用方法。
	 * @param target 要代理的目标
	 * @param advice 要代理的建议接口。其中定义一些相应的操作
	 * @return 返回目标的字节码
	 */
	private static Object getProxy(final Object target,final Advice advice) {
		Object col3 = Proxy.newProxyInstance(
				target.getClass().getClassLoader(), //获得指定的类加载器,一般是与接口的加载器一样。
				
				target.getClass().getInterfaces(), //给定的接口类型
				
				new InvocationHandler() {//通过匿名内部类,生成InvocationHandler对象
					@Override
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						//如果将对象放置在此处,则每次执行对应的方法,都会重新new 一个ArrayList对象。彼此是独立的单独的操作。
//						ArrayList target = new ArrayList();
						/*long startTime = System.currentTimeMillis();
						Object retVal = method.invoke(target, args);
						long endTime = System.currentTimeMillis();
						System.out.println(method.getName() + ": running time of " + (startTime - endTime));
						return retVal;*/
						
						//方法开始时的时间
//						long startTime = System.currentTimeMillis();
						advice.beforeMethod(method);
						Object retVal = method.invoke(target, args);
						advice.afterMethod(method);
						//方法结束后的时间
//						long endTime = System.currentTimeMillis();
						//方法总共用时。
//						System.out.println(method.getName() + ": running time of " + (startTime - endTime));
						return retVal;
					}
				});
		return col3;
	}

	/**
	 * 获取,并打印出所有的构造函数名称及其参数列表
	 * @param clazz 出入的类的字节码
	 */
	public static void showConstructors(Class clazz) {
		//获得所有可见的构造方法
		Constructor[] cons = clazz.getConstructors();
		//遍历
		for (Constructor con : cons) {
			//获得构造方法的名称
			String name = con.getName();
			StringBuilder sBuilder = new StringBuilder(name);
			sBuilder.append("(");
			
			//获得方法中的所有的参数列表的类型的字节码
			Class[] clazzParams = con.getParameterTypes();
			for (Class clazzParam : clazzParams) {
				//追加参数名称
				sBuilder.append(clazzParam.getName() + ",");
			}
			
			if (clazzParams != null && clazzParams.length != 0) {
				//去掉最后一个逗号
				sBuilder.deleteCharAt(sBuilder.length() - 1);
			}
			sBuilder.append(")");
			
			System.out.println(sBuilder);
		}
	}
	
	/**
	 * 获取,并打印出所有的可访问方法名称及其参数列表
	 * @param clazz
	 */
	public static void showMethods(Class clazz) {
		//获得所有可见方法的名称
		Method[] meds = clazz.getMethods();
		
		//遍历数组,获得每一个Method对象
		for (Method med : meds) {
			//获得方法名称
			String name = med.getName();
			StringBuilder sBuilder = new StringBuilder(name);
			sBuilder.append("(");
			
			//获得方法的所有参数
			Class[] clazzParams = med.getParameterTypes();
			for (Class clazzParam : clazzParams) {
				//追加方法的参数名称
				sBuilder.append(clazzParam.getName() + ",");
			}
			if (clazzParams != null && clazzParams.length != 0) {
				//去掉最后一个逗号
				sBuilder.deleteCharAt(sBuilder.length() - 1);
			}
			sBuilder.append(")");
			System.out.println(sBuilder);
		}
	}
}


用到的自定义接口及类:


自定义的Advice接口:

package com.itheima.proxydemos;

import java.lang.reflect.Method;

/**
 * 使用代理类的提供的一个建议,规范,公告接口
 * @author wuyong
 *
 */
public interface Advice {
	void beforeMethod(Method method);
	void afterMethod(Method method);
}

Advice接口的实现类:

package com.itheima.proxydemos;

import java.lang.reflect.Method;

/**
 * 自定义公告,建议接口的实现类
 * @author wuyong
 *
 */
public class ImplAdvice implements Advice {
	long startTime;
	@Override
	public void beforeMethod(Method method) {
		System.out.println(method.getName() + "方法结束后的操作");
		long endTime = System.currentTimeMillis();
		System.out.println(method.getName() + " running time of " + (endTime - startTime));
	}

	@Override
	public void afterMethod(Method method) {
		System.out.println(method.getName() + "方法开始前的操作");
		startTime = System.currentTimeMillis();
	}

}

自定义的接口:
package com.itheima.proxydemos;

/**
 * 自定义的接口
 * @author wuyong
 *
 */
public interface ProxyInterface {
	void show();
	int show(boolean bool);
}


自定义的接口的实现类:

package com.itheima.proxydemos;


/**
 * 动态代理类的测试工具类
 * @author wuyong
 *
 */
public class ProxyClassUtil implements ProxyInterface {
	public void show() {
		
	}
	public int print(int j,String c) {
		return 0;
	}
	@Override
	public int show(boolean bool) {
		return 0;
	}
}

   
   
   
   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值