一、反射
JAVA反射机制定义: JAVA反射机制是java程序在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射就是把Java类中的各种成分映射成相应的Java类。
Java反射机制主要提供了以下功能:
1、在运行时判断任意一个对象所属的类;
2、在运行时构造任意一个类的对象;
3、在运行时判断任意一个类所具有的成员变量和方法;
4、在运行时调用任意一个对象的方法;
5、生成动态代理。
我们了解了反射机制的功能,那么我们如何具体来实现它呢?
一个类中的每个成员都可以用相应的反射包中的一个类的实例对象来表示,通过Class类的方法可以得到这些实例对象;这个“成员”就包括:类的构造函数、成员变量、方法等一些信息。而他们所对应的反射包中的类分别是:Constructor类、Field类、Method类。
这里只要讲讲动态代理的知识。
二、动态代理
Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类:(1). Interface InvocationHandler:该接口中仅定义了一个方法Object:invoke(Object obj,Method method, Object[] args)。在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组。 这个抽象方法在代理类中动态实现。
(2).Proxy:该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含以下内容:
Protected Proxy(InvocationHandler h):构造函数,估计用于给内部的h赋值。
Static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)。实例:
1、定义一个HelloWorld接口
- /**
- * 定义一个HelloWorld接口
- *
- * @author
- *
- */
- public interface HelloWorld {
- public void sayHelloWorld();
- }
2、类HelloWorldImpl是HelloWorld接口的实现
- /**
- * 类HelloWorldImpl是HelloWorld接口的实现
- *
- * @author
- *
- */
- public class HelloWorldImpl implements HelloWorld{
- public void sayHelloWorld() {
- System.out.println("HelloWorld!");
- }
- }
3、HelloWorldHandler是 InvocationHandler接口实现
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- /**
- * 实现在方法调用前后向控制台输出两句字符串
- *
- * @author
- *
- */
- public class HelloWorldHandler implements InvocationHandler{
- //要代理的原始对象
- private Object obj;
- public HelloWorldHandler(Object obj) {
- super();
- this.obj = obj;
- }
- /**
- * 在代理实例上处理方法调用并返回结果
- *
- * @param proxy 代理类
- * @param method 被代理的方法
- * @param args 该方法的参数数组
- */
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- Object result = null;
- //调用之前
- doBefore();
- //调用原始对象的方法
- result=method.invoke(obj, args);
- //调用之后
- doAfter();
- return result;
- }
- private void doBefore(){
- System.out.println("before method invoke");
- }
- private void doAfter(){
- System.out.println("after method invoke");
- }
- }
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Proxy;
- public class HelloWorldTest {
- public static void main(String[] args) {
- HelloWorld helloWorld=new HelloWorldImpl();
- InvocationHandler handler=new HelloWorldHandler(helloWorld);
- //创建动态代理对象
- HelloWorld proxy=(HelloWorld)Proxy.newProxyInstance(
- helloWorld.getClass().getClassLoader(),
- helloWorld.getClass().getInterfaces(),
- handler);
- proxy.sayHelloWorld();
- }
- }
before method invoke
HelloWorld
after method invoke
总结:
一个典型的动态代理创建对象过程可分为以下四个步骤:
1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(...);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))
综合上面所说的,作为一个Dynamic Proxy,它必须满足以下三个条件:
1、实现了InvocationHandler接口,实现接口中定义的invoke方法;
2、包含接口实现类的实例;
3、通过Proxy.newProxyInstance方法实现Proxy与接口之间的绑定
再来回顾一下定义
Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作 。
代理
一、代理的概念与作用
程序中的代理:要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如:异常处理,日志,计算方法的运行时间,事务管理等等。
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
如果采用工厂模式和配置文件的方法进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类,还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
代理框架图如下:
面向方面的编程(Aspect oriented program,简称AOP),AOP的目标就是要使交叉业务模块化,这与直接在方法中编写切面代码的运行效果是一样的,如下图所示:
代理技术正好解决这种问题,代理是实现AOP功能的核心和关键技术。
动态代理技术:
- 要为系统中的各种接口增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情,写成千上百个代理类是不现实的。
- JVM可以在运行期间动态生成类的字节码,这种动态生成的类往往被用作代理类。即动态代理。
- JVM生成的动态类必须实现一个或多个接口,所以JVM生成的动态类只能用作具有相同接口的目标类的代理。
- CGLIB库可以动态生成一个类的子类,一个类的子类也可以用做该类的代理,所以如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
- 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置上加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中。
二、代码示例
例1:演示创建动态类及查看其方法列表信息
- import java.lang.reflect.Constructor;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.util.Collection;
- public class ProxyDemo {
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- Class proxy=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
- System.out.println(proxy.getName());
- System.out.println("-------Constructors List ---------");
- //按照以下格式输出构造函数列表,带参数
- //$Proxy0
- //$Proxy0(参数列表)
- Constructor[] constructors=proxy.getConstructors();
- for(Constructor constructor:constructors){
- String name=constructor.getName();
- System.out.println(name);
- StringBuilder stringBuilder=new StringBuilder();
- Class[] parames=constructor.getParameterTypes();
- stringBuilder.append(name);
- stringBuilder.append('(');
- for(Class parame:parames){
- stringBuilder.append(parame.getName()).append(",");
- }
- if(parames!=null&¶mes.length!=0)//为什么只用null判断不行呢???
- stringBuilder.deleteCharAt(stringBuilder.length()-1);
- stringBuilder.append(')');
- System.out.println(stringBuilder.toString());
- }
- System.out.println("-------Methods List ---------");
- //和上面的格式相同
- Method[] methods=proxy.getMethods();
- for(Method method:methods){
- String name=method.getName();
- //System.out.println(name);
- StringBuilder stringBuilder=new StringBuilder();
- Class[] parames=method.getParameterTypes();
- stringBuilder.append(name);
- stringBuilder.append('(');
- for(Class parame:parames){
- stringBuilder.append(parame.getName());
- stringBuilder.append(",");
- }
- if(parames!=null&¶mes.length!=0)//为什么只用null判断不行呢???
- stringBuilder.deleteCharAt(stringBuilder.length()-1);
- stringBuilder.append(')');
- System.out.println(stringBuilder.toString());
- }
- }