Java基础(十五)——高新技术之内省、类加载器、动态代理



第一部分 内省机制
1,JavaBean
        JavaBean是一种用Java语言写成的可重用组件,其实JavaBean就是一个满足一些规则的特殊的Java类。为了写成JavaBean,类必须要满足以下的规则:
1)JavaBean类必须是具体的和公共的,不能是抽象的类或者其它访问权限。
2)必须提供一个无参数的构造方法,可以提供若干有参构造方法,但是此时必须手写一个无参构造方法。
3)JavaBean类中属性最好声明为私有权限,定义setter和getter方法类对属性进行操作。
例如:
public class Pointer {
    private int x;
    private int y;

    public Pointer() {
    }
    public Pointer(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }
    public int getX() {
        return x;
    }
    public void setX(int x) {
        this.x = x;
    }
    public int getY() {
        return y;
    }
    public void setY(int y) {
        this.y = y;
    }
}
         JavaBean多用于JSP编程中,这里不作过多的了解,以后学习相关JSP技术时,再做深究。但是为什么会出现JavaBean这个组件呢?
这里必须明白JavaBean的一些特点:
1)可以实现代码的重复利用
2)易编写、易维护、易使用
3)可以在任何安装了Java运行环境的平台上的使用,而不需要重新编译。

2,内省
        开发框架时,经常需要使用java对象的属性来封装程序的数据,如果每次通过反射技术来完成这样的操作过于麻烦,所以sun开发了一套API,用于专门操作JavaBean对象的属性。内省(IntroSpector)是Java语言对Bean类属性、事件的一种缺省处理方法。这些API存放在java.beans包下。以上面的名为Pointer的JavaBean类作为例子,对其属性进行操作。
1)通过Introspector类获得Bean对象的 BeanInfo,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应getter/setter 方法,然后通过反射机制来调用这些方法。
Introspector类:
static BeanInfo getBeanInfo(Class<?> beanClass):在JavaBean上进行内省,了解其所有属性、公开的方法和事件。
BeanInfo接口:
MethodDescriptor[] getMethodDescriptors():获取属性信息,返回一个方法描述器的数组。
PropertyDescriptor[] getPropertyDescriptors():获取属性信息,返回一个属性描述器的数组。
测试代码:
import java.beans.*;
import java.lang.reflect.*;
public class IntroSpectorDemo {
    public static void main(String[] args) throws Exception {
        Pointer p = new Pointer(3, 5);
        String propertyName = "x";
        getPropertyByBeanInfo(p, propertyName);
    }
    // 通过内省对JavaBean的复杂操作
    public static void getPropertyByBeanInfo(Pointer p, String propertyName)throws Exception {
        Object retVal = null;
        BeanInfo beanInfo = Introspector.getBeanInfo(p.getClass());
        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor pd : pds) {
            if (pd.getName().equalsIgnoreCase(propertyName)) {
                Method methodGetX = pd.getReadMethod();
                retVal = methodGetX.invoke(p);
                break;
            }
        }
        System.out.println(retVal);
    }
}
2)通过PropertyDescriptor来操作Bean对象(java.beans.PropertyDescriptor)
构造方法:
PropertyDescriptor(String propertyName, Class<?> beanClass):通过调用 getFoo 和 setFoo 存取方法,为符合标准 Java 约定的属性构造一个 PropertyDescriptor。
PropertyDescriptor(String propertyName, Class<?> beanClass, String readMethodName, String writeMethodName):此构造方法带有一个简单属性的名称和用于读写属性的方法名称。
PropertyDescriptor(String propertyName, Method readMethod, Method writeMethod):此构造方法带有某一简单属性的名称,以及用来读取和写入属性的 Method 对象。
常用方法:
boolean equals(Object obj):将此 PropertyDescriptor 与指定对象进行比较。
Class<?> getPropertyType():获得属性的 Class 对象。
Method getReadMethod():获得应该用于读取属性值的方法。
Method getWriteMethod():获得应该用于写入属性值的方法。
void setReadMethod(Method readMethod):设置应该用于读取属性值的方法。
void setWriteMethod(Method writeMethod):设置应该用于写入属性值的方法。
测试代码:
import java.beans.*;
import java.lang.reflect.*;
public class IntroSpectorDemo {
    public static void main(String[] args) throws Exception {
        Pointer p = new Pointer(3, 5);
        String propertyName = "x";
        getProperty(p, propertyName);
        int objVal = 7;
        setProperty(p, propertyName, objVal);
    }
    //获取指定的属性的值
    public static void getProperty(Pointer p, String propertyName)throws Exception {
        PropertyDescriptor pd = new PropertyDescriptor(propertyName,Pointer.class);
        Method methodGetX = pd.getReadMethod();
        Object retVal = methodGetX.invoke(p);
        System.out.println(retVal);
    }
    //设置指定的属性的值
    public static void setProperty(Pointer p, String propertyName, int objVal)throws Exception {
        PropertyDescriptor pd1 = new PropertyDescriptor(propertyName,Pointer.class);
        Method methodSetX = pd1.getWriteMethod();
        methodSetX.invoke(p, objVal);
        System.out.println(p.getX());
    }
}
3,BeanUtils工具包
    由上述可看出,内省操作非常的繁琐,所以所以Apache开发了一套简单、易用的API来操作Bean的属性——BeanUtils工具包。
BeanUtils工具包:下载:http://commons.apache.org/beanutils/ 注意:应用的时候还需要一个logging包 http://commons.apache.org/logging/下载完成后,将BeanUtils工具包和日志文件全部解压,并导入到当前工程下,就可以使用该工具包了。
步骤:1)在当前工程下右键单击——>新建文件夹——>文件夹取名为lib
            2)解压得到commons-beanutils-1.8.3.jar和commons-logging-1.1.3.jar两个文件都复制黏贴到lib目录下。
            3)在导入的文件下,右键单击——>构建路径——>添加至构建路径——>完成
测试代码如下:
import org.apache.commons.beanutils.BeanUtils;

public class BeanUtilsDemo {
    public static void main(String[] args) throws Exception {
        Pointer p = new Pointer(3,5);
        //获取属性的值,x
        System.out.println(BeanUtils.getProperty(p, "x"));
        //设置属性的值,y设置成100
        BeanUtils.setProperty(p, "y", 100);
        System.out.println(p.getY());
    }
}
static String getProperty(Object bean, String name):获取属性值,参数是bean对象和属性名。
static void setProperty(Object bean, String name, Object value):设置属性,参数是字符串或基本类型自动包装。
BeanUtils的特点:
1)对基本数据类型的属性的操作:在WEB开发、使用中,录入和显示时,值会被转换成字符串,但底层运算用的是基本类型,这些类型转到动作由BeanUtils自动完成。
2)对引用数据类型的属性的操作:首先在类中必须有对象,不能是null,例如,private Date birthday=new Date();。操作的是对象的属性而不是整个对象,例如,BeanUtils.setProperty(userInfo,"birthday.time",111111); 
测试代码:
import java.util.Date;
public class Person {
    private Date birthday = new Date();
    public Person(){}
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}

import org.apache.commons.beanutils.BeanUtils;
public class BeanUtilsDemo {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        BeanUtils.setProperty(p, "birthday.time", "10000000");
        System.out.println(BeanUtils.getProperty(p, "birthday.time"));
    }
}
注意:BeanUtils操作只能是属性,不能是对象,上述的birthday是Date的一个对象,通过API发现Date对象下有个setTime(long time)方法, 可以判定Date对象有个time的属性,所以BeanUtils应该操作这个属性,即birthday.time。
第二部分 类加载器
1,类加载器
1.1,类加载器简介

        类加载器负责加载所有的类,系统为所有被载入内存中的类生成一个java.lang.Class实例。一旦一个类被载入JVM中,同一个类就不会被再次载入了。当JVM启动时,会形成由三个类加载器组成的初始类加载器的层次结构:
》》Bootstrap ClassLoader:根类加载器,它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。主要加载位于<JAVA_HOME>/jre/lib目录下的类,如rt.jar,jce.jar等。
》》Extension ClassLoader:扩展类加载器,它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。主要加载位于<JAVA_HOME>/jre/lib/ext或者java.ext.dirs这个系统属性指定的路径下的代码。
》》System ClassLoader:系统类加载器,它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
      来自命令java中的-classpath选项或java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。
另外:类加载器也是Java类,所以Java类加载器本身也要被类加载器加载,显然必须由一个类加载器不是Java编写的类,这正是Bootstrap ClassLoader。它主要加载

1.2,类加载机制
JVM的类加载机制主要有如下三种机制:
》》全盘负责:所谓全盘负责,就是说当一个类加载器负责加载某个Class的时候,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。
》》父类委托:所谓父类委托则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
》》缓存机制:缓存机制将会保证所有被加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存中搜寻该Class,只有当缓存中不存在该Class对象时,系统才会重读该类对应的二进制数据,并将其转换成Class对象,并存入cache。这就是为什么我们修改了Class后,程序必须重新启动JVM,程序所作的修改才会生效的原因。

类加载器加载Class大致要经过如下8个步骤:
(1)检测此Class是否载入过(即在缓存中是否有此Class),如果有则直接进入第(8)步,否则接着执行第(2)步。
(2)如果父加载器不存在(如果没有父加载器,则要么parent一定是根加载器,要么本身就是跟加载器),则跳到第(4)步执行。如果父加载器存在,则接着执行第(3)步。
(3)请求父加载器载入目标类,如果成功载入则跳到第(8)步,不成功接着执行第(5)步。
(4)请求使用根加载器来载入目标类,如果成功到(8)。如果不成功跳到第(7)步。
(5)寻找Class文件(从与此ClassLoader相关的类路径中寻找)。如果找到则执行第(6)步,如果找不到则跳到第(7)步。
(6)从文件中载入Class,成功载入后跳到第(8)步。
(7)抛出ClassNotFoundException。
(8)返回Class。

1.3,创建并使用自定义的类加载器
        JVM中除根加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过扩展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。
ClassLoader类有如下三个关键方法:
Class<?> loadClass(String name):
protected  Class<?> loadClass(String name, boolean resolve):该方法为ClassLoader的入口点,根据指定的二进制名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象。
protected  Class<?> findClass(String name):根据二进制名称来查找类。
附加:如果需要实现自定义的ClassLoader,可以通过重写以上三个方法来实现,当然我们推荐重写findClass()方法,而不是重写loadClass()方法。
protected  Class<?> defineClass(String name, byte[] b, int off, int len):该方法负责将指定类的字节码文件(即class文件)读入字节数组:byte[] b内,并把它转化为Class对象,该字节码文件可以来源于文件、网络等。
 除此之外,ClassLoader里还包含如下一些普通方法:
protected  Class<?> findSystemClass(String name):从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用defineClass将原始字节转换成Class对象,以将文件转换成类。
static ClassLoader getSystemClassLoader():用于返回系统类加载器。
ClassLoader getParent():获取该类加载器的父类加载器。
protected  void resolveClass(Class<?> c):链接指定的类。类加载器可以使用此方法来链接类c。
protected  Class<?> findLoadedClass(String name):如果此Java虚拟机已装载了名name的类,则直接返回该类对应的Class实例;否则,返回null。该方法是Java类加载里缓存机制的体现。
    第三部分 动态代理
1,动态代理简介
         代理模式是常用的java设计模式,就是对目标对象提供一种代理以控制对这个对象的访问,代理类与目标类(委托类)有同样的接口,代理是为了给目标类提供额外的处理或者不同的操作,如异常处理、运行时间、日志、事物管理等。代理类的对象本身并不真正实现服务,而是通过调用目标类/委托类对象的相关方法提供服务。
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。

2,应用
         采用代理是为了通过不修改源代码的情况下给程序动态统一添加功能,利用代理技术可以将业务逻辑中一些非业务逻辑的代码分离出来,把他们独立到业务逻辑类外,比如日志记录,性能统计,安全控制,事务处理,异常处理等。这样做,不仅降低了业务逻辑和非业务逻辑的耦合性,提高程序的可重用性,同时提高了开发的效率。
        如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,只要在配置文件中明确要用目标类还是代理类。这样以后很容易切换,如果想要日志功能时,
就配置代理类,否则配置目标类。这样,增加系统功能很容易,以后运行一段时间后,又想换掉系统功能也很容易。

伪代码实例:
目标类:
Class x{
    void sayHello(){
        syso:hello.itcast
    }
}
代理类:
Class xProxy{
    void sayHello(){
        startTime;
        x.sayHello();//调用目标类的方法
        endTime;
    }
}
3,AOP面向切面编程
        系统中存在着交叉业务(安全、事务、日志等功能要贯穿于好多个模块中,所以他们就是交叉业务。)一个交叉业务就是要切入到系统中的一个方面。
交叉业务

交叉业务的代码实现

        交叉业务的编程问题即面向方面的编程(AOP,Aspect Orentied Program),AOP的目标就是使交叉业务模块化,可以采用将切面代理移动到原始方法的周围,这与直接在方法中编写切面代理的过程效果是一样的。
AOP实现交叉业务的代码实现

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

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

5,使用Proxy和InvocationHandler创建动态代理
         Proxy提供用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类,如果我们在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态代理类;如果需要为一个或多个接口动态地创建实例,也可以使用Proxy来创建动态代理实例。
Proxy提供了如下方法来创建动态代理类和动态代理实例:
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
创建一个动态代理类所对应的Class对象,该代理类将实现interfaces所指定的多个接口。第一ClassLoader指定生成动态代理类的类加载器。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
直接创建一个动态代理对象,该代理对象的实现类实现了interfaces指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Collection;

public class ProxyDemo {
    public static void main(String[] args) throws Exception {
        Class clazzProxy = Proxy.getProxyClass(
                Collection.class.getClassLoader(), Collection.class);
        System.out.println(clazzProxy.getName());

        System.out.println("------begin constructor list------");
        Constructor[] constructors = clazzProxy.getConstructors();
        for (Constructor constructor : constructors) {
            String name = constructor.getName();
            StringBuilder sBuilder = new StringBuilder();
            sBuilder.append('(');
            Class[] clazzParams = constructor.getParameterTypes();
            for (Class clazzParam : clazzParams) {
                sBuilder.append(clazzParam).append(',');
            }
            if (clazzParams != null && clazzParams.length != 0)
                sBuilder.deleteCharAt(sBuilder.length() - 1);
            sBuilder.append(')');
            System.out.println(name + sBuilder.toString());
        }

        System.out.println("------begin method list------");
        Method[] methods = clazzProxy.getMethods();
        for (Method method : methods) {
            String name = method.getName();
            StringBuilder sBuilder = new StringBuilder();
            sBuilder.append('(');
            Class[] clazzParams = method.getParameterTypes();
            for (Class clazzParam : clazzParams) {
                sBuilder.append(clazzParam).append(',');
            }
            if (clazzParams != null && clazzParams.length != 0)
                sBuilder.deleteCharAt(sBuilder.length() - 1);
            sBuilder.append(')');
            System.out.println(name + sBuilder.toString());
        }

        System.out.println("------begin crate instance object------");
        // 第一种方式
        Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);
        class MyInvocationHandler implements InvocationHandler {
            ArrayList target = new ArrayList();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                long startTime = System.currentTimeMillis();
                Object retVal = method.invoke(target, args);
                long endTime = System.currentTimeMillis();
                System.out.println(method.getName() + " running time of " + (endTime - startTime));
                return retVal;
            }
        }
        Collection proxy1 = (Collection) constructor.newInstance(new MyInvocationHandler());
        proxy1.add("abc");
        proxy1.add("opq");
        proxy1.add("xyz");
        System.out.println(proxy1.size());

        // 第二种方式
        Collection proxy2 = (Collection) constructor.newInstance(new InvocationHandler() {
                    ArrayList target = new ArrayList();
                    @Override
                    public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
                        long startTime = System.currentTimeMillis();
                        Object retVal = method.invoke(target, args);
                        long endTime = System.currentTimeMillis();
                        System.out.println(method.getName() + " running time of " + (endTime - startTime));
                        return retVal;
                    }
                });
        proxy2.add("abc");
        proxy2.add("opq");
        proxy2.add("xyz");
        System.out.println(proxy2.size());

        // 第三种
        Collection proxy3 = (Collection) Proxy.newProxyInstance(
                Collection.class.getClassLoader(),
                new Class[] { Collection.class },
                new InvocationHandler() {
                    ArrayList target = new ArrayList();
                    @Override
                    public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
                        long startTime = System.currentTimeMillis();
                        Object retVal = method.invoke(target, args);
                        long endTime = System.currentTimeMillis();
                        System.out.println(method.getName() + " running time of " + (endTime - startTime));
                        return retVal;
                    }
                });
        proxy3.add("abc");
        proxy3.add("opq");
        proxy3.add("xyz");
        System.out.println(proxy3.size());
    }
}
6,内部的原理:猜想分析动态生成的类的内部代码?
        动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。构造方法接受一个InvocationHandler对象,接受对象干什么了呢?该方法内部类的代码会是怎么样的呢?
       实现的Collection接口中的各个方法的代码又是怎么样的呢?InvocationHandler接口中定义的invoke方法接受的三个参数是什么意思呢?
内部原理


7,让动态生成的类,成为目标类的代理
7.1,分析动态代理的工作原理图
动态代理的工作原理图


7.2,怎样将目标类传进去?
1)在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但是没有实际意义
2)为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了
3)让匿名的InvocationHandler实现访问外面方法中的目标类实例对象的final类型的引用变量

7.3,将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接受目标同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。

7.4,把系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码从参数形式提供?
1)把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行外界提供的代码。
2)为bind方法增加一个Advice参数。

7.5,编写可生成代理和插入通告的通用方法
实现通告的接口:
interface Advice {
    void beforeMethod();
    void afterMethod(Method method);
}
实现通告接口的实例类:
class MyAdvice implements Advice {
    long startTime = 0;
    @Override
    public void beforeMethod() {
        System.out.println("到传智播客来学习了...");
        startTime = System.currentTimeMillis();
    }
    @Override
    public void afterMethod(Method method) {
        System.out.println("从传智播客毕业上班了...");
        long endTime = System.currentTimeMillis();
        System.out.println(method.getName() + " running time of " + (endTime - startTime));
    }
}
动态代理类:
import java.lang.reflect.*;
import java.util.*;
public class ProxyDemo2 {
	public static void main(String[] args) throws Exception {
		ArrayList target = new ArrayList();
		Collection proxy = (Collection) getProxy(target, new MyAdvice());
		proxy.add("abc");
		proxy.add("opq");
		proxy.add("xyz");
		System.out.println(proxy.size());
	}

	public static Object getProxy(final Object target, final Advice advice)throws Exception {
		Object proxy = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				target.getClass().getInterfaces(),
				new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
						// long startTime = System.currentTimeMillis();
						advice.beforeMethod();
						Object retVal = method.invoke(target, args);
						// long endTime = System.currentTimeMillis();
						// System.out.println(method.getName()+" running time of "+(endTime - startTime));
						advice.afterMethod(method);
						return retVal;
					}
				});
		return proxy;
	}
}



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值