JAVA基础知识|java基础知识学习之代理深入学习(用动态代理实现AOP,FacotryBean)

1.代理的概念与作用 程序中的代理
能为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等;编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。(参看下页的原理图)如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。 JAVA基础知识|java基础知识学习之代理深入学习(用动态代理实现AOP,FacotryBean)
文章图片



1.1代理应用场景
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示: 安全事务日志
StudentService ------|----------|------------|-------------
CourseService------|----------|------------|-------------
MiscService------|----------|------------|-------------
用具体的程序代码描述交叉业务: method1method2method3
{{{
------------------------------------------------------切面
..............
------------------------------------------------------切面
}}}
交叉业务的编程问题即为面向方面的编程( Aspect oriented program , 简称 AOP ), AOP 的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示: 【JAVA基础知识|java基础知识学习之代理深入学习(用动态代理实现AOP,FacotryBean)】------------------------------------------------------切面
func1func2func3
{{{
..............
}}}
------------------------------------------------------切面

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


1.2JDK动态代理类 要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累! JVM 可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。 JVM 生成的动态类必须实现一个或多个接口,所以, JVM 生成的动态类只能用作具有相同接口的目标类的代理。 CGLIB 库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用 CGLIB 库。 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码: 1. 在调用目标方法之前 2. 在调用目标方法之后 3. 在调用目标方法前后 4. 在处理目标方法异常的 catch 块中


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

package AsbstractFactory; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; 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 InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { // 1.创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数,获得代理类的字节码。 Class clazz = Proxy.getProxyClass(ProxyTest.class.getClassLoader(), Collection.class); System.out.println(clazz.getName()); // 2.通过反射获得代理类的方法 Method[] methods = clazz.getMethods(); for (Method m : methods) { System.out.println(m.getName()); } System.out.println("---------------------"); // 3.用反射获得构造方法 Constructor[] contructors = clazz.getConstructors(); // 创建动态类的实例对象 // clazz.newInstance(); // clazz.getConstructor().newInstance(); // 4.通过反射获得对应的构造函数 Constructor constructor = clazz.getConstructor(InvocationHandler.class); // 编写一个最简单的InvocationHandler类 Collection proxy = (Collection) constructor.newInstance(new InvocationHandler() { //实现了接口的要被代理的对象 ArrayList target = new ArrayList(); @Override // 这里的三个参数正好就是proxy.add(obj)中的三部分 。 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object tObject = invoke(target, method, args); return tObject; } }); proxy.size(); }}

JAVA基础知识|java基础知识学习之代理深入学习(用动态代理实现AOP,FacotryBean)
文章图片


动态代理的工作原理图(见下页)
JAVA基础知识|java基础知识学习之代理深入学习(用动态代理实现AOP,FacotryBean)
文章图片



2.实现在代理类中加上系统功能代码例子
import java.lang.reflect.Method; //通知接口 public interface Advice { void beforeMethod(Method method); void afterMethod(Method method); }import java.lang.reflect.Method; //通知接口实现类 public class MyAdvice implements Advice { long beginTime = 0; public void afterMethod(Method method) { // TODO Auto-generated method stub System.out.println("从传智播客毕业上班啦!"); long endTime = System.currentTimeMillis(); System.out.println(method.getName() + " running time of " + (endTime - beginTime)); } public void beforeMethod(Method method) { // TODO Auto-generated method stub System.out.println("到传智播客来学习啦!"); beginTime = System.currentTimeMillis(); }}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 { /** * @param args */ @SuppressWarnings("rawtypes") public static void main(String[] args) throws Exception { // 1.被代理类 ArrayList target = new ArrayList(); // 切入的通知功能 Advice advice = new MyAdvice(); // 给到代理类对象 Collection proxy = (Collection) getProxy(target, advice); System.out.println(proxy.isEmpty()); } private static Object getProxy(final Object target, final Advice advice) { Object proxy3 = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外, // 还可以在代理方法中的如下四个位置加上系统功能代码: // 1.在调用目标方法之前 // 2.在调用目标方法之后 // 3.在调用目标方法前后 advice.beforeMethod(method); Object retVal = method.invoke(target, args); advice.afterMethod(method); return retVal; } }); return proxy3; }}


3.实现AOP功能的封装与配置(例子) 逻辑:工厂类 BeanFactory 负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其 getBean 方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是 ProxyFactoryBean ,则直接返回该类的实例对象,否则,返回该类实例对象的 getProxy 方法返回的对象。BeanFactory 的构造方法接收代表配置文件的输入流对象。

1.配置文件config.properties格式如下: #xxx=java.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.MyAdvice

import java.io.InputStream; import java.util.Collection; public class AopFrameworkTest { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub //读取配置文件 InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties"); // Object bean = new BeanFactory(ips).getBean("xxx"); System.out.println(bean.getClass().getName()); ((Collection)bean).clear(); }}import java.io.IOException; import java.io.InputStream; import java.util.Properties; //工厂类BeanFactory负责创建目标类或代理类的实例对象, //并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象, //如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象, //否则,返回该类实例对象的getProxy方法返回的对象。 public class BeanFactory { //读取Properties的key-value值 Properties props = new Properties(); public BeanFactory(InputStream ips){ try { props.load(ips); } catch (IOException e) { e.printStackTrace(); } } //getBean方法根据参数字符串返回一个相应的实例对象, //如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象, //否则,返回该类实例对象的getProxy方法返回的对象。 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 Auto-generated catch block 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(); proxyFactoryBean.setAdvice(advice); proxyFactoryBean.setTarget(target); proxy = proxyFactoryBean.getProxy(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return proxy; } return bean; } }import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import cn.itcast.day3.Advice; public class ProxyFactoryBean { private Advice advice; private Object target; 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; } public Object getProxy() { Object proxy3 = 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 proxy3; }}

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

    推荐阅读