随着学习的东西原来越多,发现设计模式越来越重要,很多是辛苦的想着要解决代码中的耦合问题,其实这些东西都已经被总计出来,并归纳为设计模式。这就是我们要去加强学习设计模式的原因。
关于设计模式,其中感触最深的就是动态代理。刚开始接触这个模式的时候并不知道这个就是代理模式,只是在spring框架中的aop思想用到。后到听老师的设计模式,原来Spring框架就是运用了 代理来实现面向切面编程的。
代理模式其实在很多地方都可以用到,如关于系统的日志记录功能,和记录操作时间等额外操作,都可以利用代理来实现。
我想要弄清楚动态代理,那么就必须先要了解反射机制。准确理解反射,应该理解类的加载过程及Class相关的东西。这里大概讲一下类的加载过程,比如说要new一个对象,会是如下步骤:
1) 在可知的路径下查找是否存在该类文件
2) 使用相应的ClassLoader加载类文件
3) 加载.class文件时,会初始化static部分
4) 分配相应的内存空间来存储相关数据
5) 内存空间清零(即获得默认值)
6) 构造基类(只有基类构造完成,才能构造子类)
7) 初始化成员
8) 构造该类的对象
反射机制中Class类是一个入口和核心:
1) 得到该类对应的Class对象
2) 由类的Class对象产生该类的实例(利用构造器)
3) 由类的Class对象动态得到类的属性和方法
4) 运行时动态调用对象的任意属性和方法
5) 运行时动态产生一个对象的代理对象
得到该类对应的Class对象三种方式:
1) Class.forName(“类的完整字符串名字")
2) 类名.class
3) 对象.getClass()
代表了load到内存中的Class对象
Object的getClass()可以拿到该类对象(=类名.class)
Class的getClassLoader可以拿到装载这个class的ClassLoader
看下面的一个练习的例子,我们可以看如下代码来模拟框架配置文件功能:
/** * * IO流读取文件 * ***/ InputStream fis = new FileInputStream("src/config.properties"); Properties props = new Properties(); props.load(fis); fis.close(); /** * 获取配置文件属性 * */ String className = props.getProperty("className"); System.out.println("className : " + className); String methodName = props.getProperty("methodName"); System.out.println("methodName : " + methodName); String result = props.getProperty("result"); System.out.println("result : " + result); /** * 以下为反射代码 * ***/ Class<?> clazz = Class.forName(className); Object obj = clazz.newInstance(); Method method = clazz.getMethod(methodName); String resultVal = (String) method.invoke(obj); /** * 实现servlet接口跳转 * * ***/ if (result.equals(resultVal)) { System.out.println("跳转index.jsp"); } else { System.out.println("返回"); }
那么以上程序就可以实现通过工程中的配置文件config.properties中的配置去模拟框架中的配置文件实现的功能。
Class<?> clazz = Class.forName(className);通过反射加载className对应的类文件,再去调用newInstance()来实例化对象。然后再接收方法名称,最后通过Method类的invoke()方法执行传入的方法。这就实现了方法的动态调用。可以实现让用户通过配置文件来修改系统。增强系统的灵活性。当需求改变的时候不再需要修改源文件。
动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandlder接口提供了生成动态代理类的能力.先来看一下动态代理的架构图:
根据上面的架构图可以看出来,这里的代理类是和目标类或者说是目标是实现了同一个父类接口。它们只是兄弟的关系,但不是互相并不互相认识。通常代理类会目标类多如一些功能,这也就是代理的功能所在。利用代理类去完成日志的记录,权限的控制功能。
那么现在就让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:
1) 通过实现 InvocationHandler 接口创建自己的调用处理器;
2) 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建 动态代理类;
3) 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
4) 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入
/** *代理类中的源代码 */ public class MyProxy implements InvocationHandler{ private Object obj; public MyProxy(Object obj){ this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args)throws Throwable { start(); Object o = method.invoke(obj,args); end(); return o; } public static void start(){ System.out.println("start..."); } public static void end(){ System.out.println("end..."); } }
代理类MyProxy实现InvocationHandler接口,实现invoke方法。该方法三个参数 proxy(被代理类),method(被调用的方法),args(传入的参数).这些参数会通过反射机制拿到并出去方法中。该方法会通过method的invoke()方法去调用传入的方法。当然invoke方法的前面和后面可以别个一些额外的操作,如日志功能。
/** *Human为Person类的父类接口。 **/ public static void dynamicProxyByConstructor() throws Exception{ Person person = new Person(); MyProxy myProxy = new MyProxy(person); Class<?> clazzProxy = Proxy.getProxyClass(Person.class.getClassLoader(), Person.class.getInterfaces()); Constructor<?> constructor = clazzProxy.getConstructor(InvocationHandler.class); Human personPrxoy = (Human)constructor.newInstance(myProxy); personPrxoy.save(); }
这利用java中的Proxy类动态生成一个代理类。上面说到代理类是需要实现和目标类一样的接口。所以这里需要传入二个参数.第一个被代理类的加载对象,其实也就相当于是代理对象。因为Proxy会通过类加载器找到被代理类的Class文件,第二个参数就为被代理类实现的接口.然后通过反射得到构造对象,实例化对象生成代理类,最后通过代理类去调用 save()方法。那么法代理类调用save方法的时候,会反射去指定代理类中的invoke方法。这时invoke接受到被代理类,需要执行的save()方法,以及传入的参数。然后根据这些参数执行方法。完成动态代理。
关于在spring中AOP也是底层运用了java中的动态代理。通过在配置文件中配置连接点和切面等信息。通过通过动态代理来实现在连接点前后执行切面类中的方法.还有hibernate中的懒加载其实也是应用了动态代理。只有当应用到对象的时候才去建立对象。
以上就是java中的动态代理就一些个人的理解。另外通过这次学了设计模式,感觉学到了很多东西。以前写代码,总是不断的去重复代码。现在学了设计知道国面向接口编程,面向抽象编程。我们需要做在不仅仅是实现程序,更重要的是写出高质量的代码,即使在需求做出变动的时候,也尽可能的减少需要修改的代码。当然,设计模式虽然好,还是需要慎重选择应用,毕竟没有最好的设计模式,只有最适合的设计模式。