高新技术(反射、内省、代理)

1、反射
一、概述:

1)Java反射机制是在运行状态中,对于任意一个类,都可以知道这个类中的全部属性和方法;对于任意一个对象,都可以调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。简单一句话:反射技术能够对类进行解剖。
2)一个已经可使用的应用程序,由于程序已经作好能够运行使用,不能再进行代码的加入了。而当后期咱们新的功能加入程序时,该怎么作呢?经常使用的做法,会提供一个配置文件,来供之后实现此程序的类来扩展功能。对外提供配置文件,让后期出现的子类直接将类名字配置到配置文件中便可。该应用程序直接读取配置文件中的内容。并查找和给定名称相同的类文件。进行以下操做:
java

    a) 加载这个类。 c++

    b)建立该类的对象。 面试

    c)调用该类中的内容。 spring

Note:应用程序使用的类不肯定时,能够经过提供配置文件,让使用者将具体的子类存储到配置文件中。而后该程序经过反射技术,对指定的类进行内容的获取。所以,反射技术大大提升了程序的扩展性。 apache

二、定义:

    反射就是把Java类中的各类成分映射成相应的java类。一个 Class 表明一个字节码,一个 Method 表明一个字节码中方法,一个 Constructor表明一个构造方法。 编程

    例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示。例如:汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来得到其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
    反射使得一个类中的每一个成员均可以用相应的反射API类的一个实例对象来表示,经过调用Class类的方法能够获得这些实例对象后,获得这些实例对象后所作的事正是学习和应用反射的要点。
三、反射的基石——Class类
设计模式

1)全部的类文件都有共同属性,因此能够向上抽取,把这些共性内容封装成一个类,这个类就叫Class(描述字节码文件的对象)。Class类中就包含属性有field(字段)、method(方法)、construction(构造函数)。而field中有修饰符、类型、变量名等复杂的描述内容,所以也能够将字段封装称为一个对象。用来获取类中field的内容,这个对象的描述叫Field。同理方法和构造函数也被封装成对象Method、Constructor。要想对一个类进行内容的获取,必需要先获取该字节码文件的对象,该对象是Class类型。Class类描述的信息:类的名字,类的访问属性,类所属于的包名,字段名称的列表,方法名称的列表等。每个字节码就是class的实例对象。 数组

2)Class和class的区别

    class:Java中的类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则由此类的实例对象肯定,不一样的实例对象有不一样的属性值。 浏览器

    Class:指的是Java程序中的各个Java类是属于同一类事物,都是Java程序的类,这些类称为Class,即字节码。例如人对应的是Person类,Java类对应的就是Class。 安全

3)获取Class对象的三种方式

    加载XX.class文件进内存时就被封装成了对象,该对象就是字节码文件对象。下面就是获取Class对象得三种方式:

方式一:

    经过对象的getClass方法进行获取。

    如:Class clazz=new Person().getClass();//Person是一个类名

Note:每次都须要具体的类和该类的对象,以及调用getClass方法。

方式二:

    任何数据类型都具有着一个静态的属性class,这个属性直接获取到该类型的对应Class对象。

    如:Class clazz=Person.class;//Person是一个类名

Note:比第一种较为简单,不用建立对象,不用调用getClass方法,可是仍是要使用具体的类,和该类中的一个静态属性class完成。

方式三:

    较为简单,只要知道类的名称便可。不须要使用该类,也不须要去调用具体的属性和行为,就能够获取到Class对象了。

    如:Class clazz=Class.forName("包名.Person");//Person是一个类名

Note:这种方式仅知道类名就能够获取到该类字节码对象的方式,更有利于扩展。

拓展:

     1)九个预约义的Class:
        a)包括八种基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和一种返回值为void类型的void.class。

        b)Integer.TYPE是Integer类的一个常量,它表明此包装类型包装的基本类型的字节码,和int.class是相等。基本数据类型的字节码均可以用与之对应的包装类中的TYPE常量表示。

     2)只要是在源程序中出现的类型都有各自的Class实例对象,如int[].class。数组类型的Class实例对象,能够用Class.isArray()方法判断是否为数组类型的。

4)Class类中的方法

     static Class forName(String className)       // 返回与给定字符串名的类或接口的相关联的Class对象。

     Class getClass()        //返回的是Object运行时的类,即返回Class对象即字节码对象

     Constructor getConstructor()        //返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。

     Field getField(String name)        //返回一个Field对象,它表示此Class对象所表明的类或接口的指定公共成员字段。

     Field[] getFields()        //返回包含某些Field对象的数组,表示所表明类中的成员字段。

     Method getMethod(String name,Class… parameterTypes)   //返回一个Method对象,它表示的是此Class对象所表明的类的指定公共成员方法。

     Method[] getMehtods()        //返回一个包含某些Method对象的数组,是所表明的的类中的公共成员方法。

     String getName()        //以String形式返回此Class对象所表示的实体名称。

     String getSuperclass()        //返回此Class所表示的类的超类的名称

     boolean isArray()        //断定此Class对象是否表示一个数组

     boolean isPrimitive()        //判断指定的Class对象是不是一个基本类型。

     T newInstance()        //建立此Class对象所表示的类的一个新实例。

5)经过Class对象获取类实例

    经过查看API咱们知道,Class类是没有构造方法的,所以只能经过方法获取类实例对象。

以前咱们用的已知类,建立对象的作法:

    1)查找并加载XX.class文件进内存,并将该文件封装成Class对象。

    2)再依据Class对象建立该类具体的实例。

    3)调用构造函数对对象进行初始化。

       如:Person p=new Person();

如今用Class对象来获取类实例对象的作法:

    1)查找并加载指定名字的字节码文件进内存,并被封装成Class对象。

    2)经过Class对象的newInstance方法建立该Class对应的类实例。

    3)调用newInstance()方法会去使用该类的空参数构造函数进行初始化。

       如:

          String className="包名.Person";

          Class clazz=Class.forName(className);

          Object obj=clazz.newInstance();

示例:
//Person类
class Person {
	private String name;
	public int age;
	public Person(){
		System.out.println("Person is run");
	}
	public Person(String name,int age){
		this.age=age;
		this.name=name;
	}
	
	public String toString(){
		return name+":"+age;
	}
}
//示例
public class CreateClassDemo {
	public static void main(String[] args) throws Exception {
		createPersonClass();
	}
	//经过Class对象建立类实例方法
	public static void createPersonClass() throws Exception{
		//获取Person类的Class对象
		String className="cn.itheima.Person";
		Class clazz=Class.forName(className);
		//经过newInstance方法获取类的无参构造函数实例
		Person p=(Person)clazz.newInstance();
	}
}

四、Constructor类

1)概述:若是指定的类中没有空参数的构造函数,或者要建立的类对象须要经过指定的构造函数进行初始化。这时就不能使用Class类中的newInstance方法了。既然要经过指定的构造函数进行对象的初始化,就必须先获取这个构造函数——Constructor,而Constructor类表明某个类的构造方法。

2)获取构造方法:

    1)获得这个类的全部构造方法:如获得上面示例中Person类的全部构造方法

       Constructor[] cons = Class.forName(“cn.itheima.Person”).getConstructors();

    2)获取某一个构造方法:

        Constructor con=Person.class.getConstructor(String.class,int.class);

3)建立实例对象:

    1)一般方式:Person p = new Person(“lisi”,30);

    2)反射方式:Person p= (Person)con.newInstance(“lisi”,30);

Note:

    1)建立实例时newInstance方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致。

    2)newInstance():构造出一个实例对象,每调用一次就构造一个对象。

    3)利用Constructor类来建立类实例的好处是能够指定构造函数,而Class类只能利用无参构造函数建立类实例对象。

示例:
public class ConstructorDemo{
        private static void main(String[] args) throws Exception {
		// 获取构造器时————要根据参数类型和个数来肯定
		Constructor<String> conString = String.class
				.getConstructor(StringBuffer.class);
		// 根据构造器获取相应的对象时————也要肯定参数类型和个数(必须和获取时的相同)
		String str = conString.newInstance(new StringBuffer("abc"));
		System.out.println(str.charAt(2));
	}
}

五、Field类

1)概述:Field类表明某个类中一个成员变量。

2)方法:

    Field getField(String s);  //只能获取公有和父类中公有

    Field getDeclaredField(String s);  //获取该类中任意成员变量,包括私有

    setAccessible(ture);        //若是是私有字段,要先将该私有字段进行取消权限检查的能力,也称暴力访问。

    set(Object obj, Object value);  //将指定对象变量上此Field对象表示的字段设置为指定的新值。

    Object get(Object obj);  //返回指定对象上Field表示的字段的值。

示例:

package reflect;

/*
 * 反射点类ReflectPoint
 */
public class ReflectPoint {

	private int x;
	public int y;
	public String str1 = "ball";
	public String str2 = "basketball";
	public String str3 = "itheima";

	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;
	}

	public ReflectPoint() {
		super();
	}

	public ReflectPoint(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ReflectPoint other = (ReflectPoint) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "ReflectPoint [x=" + x + ", y=" + y + ", str1=" + str1
				+ ", str2=" + str2 + ", str3=" + str3 + "]";
	}
}

public class FileDemo{
        private static void main(String[] args) throws Exception {
		// 对于源文件中的类成员变量public 只要用getField()方法就可得到Field对象
		ReflectPoint rp = new ReflectPoint(2,5);
		Field fieldY = rp.getClass().getField("y");
		System.out.println(fieldY.get(rp));
		// 若是成员变量为非公用,则必需要用getDeclearedField()得到Field对象
		Field fieldX = rp.getClass().getDeclaredField("x");
		// 暴力反射
		fieldX.setAccessible(true);
		System.out.println(fieldX.get(rp));
	}
}

六、Method类

1)概述:Method类表明某个类中的一个成员方法。调用某个对象身上的方法,要先获得方法,再针对某个对象调用。

2)方法:

    Method[] getMethods();//只获取公共和父类中的方法。

    Method[] getDeclaredMethods();//获取本类中包含私有。

    Method   getMethod("方法名",参数.class(若是是空参能够写null));

    Object invoke(Object obj ,参数);//调用方法,若是方法是静态,invoke方法中的对象参数能够为null。

    如:获取某个类中的某个方法(如String str =”abc”)

        1)一般方式:str.charAt(1);

        2)反射方式:

           Method charAtMethod =Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);

           charAtMethod.invoke(str,1);

示例:
public class MethodDemo{
        public static void main(String[] args){
               getAllMethod();
        }
        public static void getAllMethod() throws Exception {
		// 若是想要获取方法,必须先要有对象。
		Class clazz = Class.forName("reflect.ReflectPoint");
		ReflectPoint p = (ReflectPoint) clazz.newInstance();

		// 获取因此方法
		Method[] mes = clazz.getMethods();// 只获取公共的和父类中的。
		// mes=clazz.getDeclaredMethods();//获取本类中包含私有。
		for (Method me : mes) {
			System.out.println(me);
		}

		// 获取单个方法
		Method me = clazz.getMethod("toString", null);
		Object returnVaule = me.invoke(p, null);
		System.out.println(returnVaule);
	}
}

七、数组的反射

1)具备相同维数和元素类型的数组属于同一个类型,即具备相同的Class实例对象。数组字节码的名字:有[和数组对应类型的缩写,如int[]数组的名称为:[I

2)Object[]与String[]没有父子关系,Object与String有父子关系,因此new Object[]{“aaa”,”bb”}不能强制转换成new String[]{“aaa”,”bb”}; Object x =“abc”能强制转换成String x =“abc”。

3)如何获得某个数组中的某个元素的类型,

    例:

       int a = new int[3];Object[] obj=new Object[]{”ABC”,1};//没法获得某个数组的具体类型,只能获得其中某个元素的类型,

    例:

       Obj[0].getClass().getName()获得的是java.lang.String。

4)Array工具类用于完成对数组的反射操做。

    Array.getLength(Object obj);//获取数组的长度

    Array.get(Object obj,int x);//获取数组中的元素

5)基本类型的一维数组能够被看成Object类型使用,不能看成Object[]类型使用;非基本类型的一维数组,既能够当作Object类型使用,又能够当作Object[]类型使用。

示例:
import java.lang.reflect.Array;
import java.util.Arrays;

public class ArrayReflect {
	public static void main(String[] args) {
		int [] a1 = new int[]{1,2,3};
		int [] a2 = new int[4];
		int[][] a3 = new int[2][3];
		String [] a4 = new String[]{"a","b","c"};
		System.out.println(a1.getClass().equals(a2.getClass()));//true
	        System.out.println(a1.getClass().equals(a3.getClass()));//false
	        System.out.println(a1.getClass().equals(a4.getClass()));//false
	        System.out.println(a1.getClass().getName());//[I
	        System.out.println(a4.getClass().getName());//[Ljava.lang.String;
	        System.out.println(a1.getClass().getSuperclass());//class java.lang.Object
	        System.out.println(a4.getClass().getSuperclass());//class java.lang.Object
		
		Object obj1=a1;
		Object obj2=a3;
		Object obj3=a4;
		
//		Object[] obj11=a1;//这样是不行的,由于a1中的元素是int类型,基本数据类型不是Object
		Object[] obj13=a3;
		Object[] obj14=a4;//这样能够,由于String数组中的元素属于Object
		
		System.out.println(a1);//[I@4caaf64e
		System.out.println(a4);//[Ljava.lang.String;@6c10a234
		System.out.println(Arrays.asList(a1));//[I@4caaf64e
		System.out.println(Arrays.asList(a4));//[a, b, c]
		
		//Array工具类用于完成对数组的反射操做。如打印任意数值
		printObject(a1);
		printObject(a4);
		printObject("abc");
		
	}
	//打印任意数值
	private static void printObject(Object obj) {
		Class clazz=obj.getClass();
		//若是传入的是数组,则遍历
		if(clazz.isArray()){
			int len =Array.getLength(obj);//Array工具类获取数组长度方法
			for(int x=0;x<len;x++){
				System.out.println(Array.get(obj, x));//Array工具获取数组元素
			}
		}
		else
			System.out.println(obj);
	}
}
八、反射的应用——>实现框架功能

1)框架:经过反射调用Java类的一种方式。框架和工具类的区别:工具类被用户类调用,而框架是调用用户提供的类。

2)框架机器要解决的核心问题:在写框架(造房子的过程)的时候,调用的类(安装的门窗等)还未出现,那么框架没法知道要被调用的类名,因此在程序中没法直接new其某个类的实例对象,而要用反射来作。

3)简单框架程序的步骤:

     a)建立一个配置文件如:config.properties,而后写入配置信息。如键值对:className=java.util.ArrayList,等号右边的配置键,右边是值。

     b)代码实现,加载此文件:

         将文件读取到读取流中,要写出配置文件的绝对路径。

         如:InputStream is=new FileInputStream(“配置文件”);

         用Properties类的load()方法将流中的数据存入集合。

         关闭流:关闭的是读取流,由于流中的数据已经加载进内存。

     c)经过getProperty()方法获取className,即配置的值,也就是某个类名。

     d)用反射的方式,建立对象newInstance()。

     e)执行程序主体功能

示例:
package reflect;

import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;

public class ReflectDemo2 {

	public static void main(String[] args) throws Exception {

		// 用完整的路径,但不能是硬编码而是运算得出的
		// InputStream in = new FileInputStream("config.properties");
		// InputStream in =
		// ReflectDemo1.class.getClassLoader().getResourceAsStream("reflect/config.properties");
		InputStream in = ReflectDemo1.class
				.getResourceAsStream("config.properties");
		Properties prop = new Properties();
		prop.load(in);
		in.close();

		String className = prop.getProperty("className");
		Collection<ReflectPoint> al = (Collection<ReflectPoint>) Class.forName(
				className).newInstance();

		// Collection al = new HashSet();
		ReflectPoint rp1 = new ReflectPoint(3, 3);
		ReflectPoint rp2 = new ReflectPoint(6, 6);
		ReflectPoint rp3 = new ReflectPoint(3, 3);
		al.add(rp1);
		al.add(rp1);
		al.add(rp2);
		al.add(rp3);

		// 如下会出现内存泄漏,由于改变了rp1的y值,其hashCode也就随之改变及内存地址改变,再用remove移除时就没有成功
		// rp1.y = 7;
		// al.remove(rp1);

		System.out.println(al.size());
	}
}
2、内省
一、概述:内省就是对程序内部进行检查,了解更多的底层细节,主要针对JavaBean进行操做。
二、JavaBean:

    1)JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,且方法符合某种特殊的命名规则。

    2)它是一种特殊的Java类,其中的方法符合特殊的规则。只要一个类中含有get或is和set打头的方法,就能够将其当作JavaBean使用。

    3)字段和属性:JavaBean的字段就是咱们定义的一些成员变量,如private String name等。JavaBean的属性是根据其中的setter和getter方法来肯定的,而不是依据其中的变量,如方法名为setId,则中文意思是设置Id,getId也是如此;去掉set或者get前缀,剩余部分就是属性名称。若是剩余部分的第二个字母小写,则把剩余部分改成小写。如:getAge/setAge-->age;gettime-->time;setTime-->time;getCPU-->CPU。

    总之,一个类被看成javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。

三、做用:

    若是要在两个模板之间传递多个信息,可将这些信息封装到一个JavaBean中,这种JavaBean的实例对象一般称之为值对象(Value Object,简称VO),这些信息在类中用私有字段来储存,若是读取或设置这些字段的值,则须要经过一些相应的方法来访问。

四、JavaBean的好处:一个符合JavaBean特色的类当作普通类同样可使用,可是把它当作JavaBean类用会带来一些额外的好处:

    1)在Java EE开发中,常常要使用到JavaBean。不少环境就要求按JavaBean方式进行操做,别人都这么用和要求这么作,那你就没什么挑选的余地。

    2)JDK中提供了对JavaBean进行操做的API,这套API称为内省,若要本身经过getX的方式来访问私有x,可用内省这套API,操做JavaBean要比使用普通的方式更方便。

五、对JavaBean的复杂内省操做

        一、在IntroSpector类中有getBeanInfo(Class cls)的方法,经过此方法获取BeanInfo实例。参数是相应对象的字节码,即Class对象。

        二、BeanInfo类中有getPropertyDescriptors()的方法,可获取全部的JavaBean类中的属性信息,返回一个PropertyDescriptor[]。

        三、在经过遍历的形式,获取与想要的那个属性信息。

示例:

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
//接上述反射点类ReflectPoint继续
import reflect.ReflectPoint;

/*
 * 内省:操做的是JavaBean(特殊的类)
 * JavaBean类是含有get()、set()方法,其中的公有方法用于访问私有成员变量,而且命名符合必定规则
 * 例如:getAge——>age		getCPU——>CPU
 * 
 */
//接上述ReflectPoint类继续

public class IntroSpectorDemo {
	public static void main(String[] args) throws Exception {
		ReflectPoint rp = new ReflectPoint(3, 4);
		String propertyName = "x";
		// "x"-->"X"-->"getX"-->MethodGetX-->

		// 用内省的方式
		// 获取并getX方法
		Object retval = getProperty1(rp, propertyName);
		System.out.println(retval);

		Object value = 5;
		// 获取并调用setX方法
		setProperty(rp, propertyName, value);

		System.out.println(rp.getX());
	}

	// 获取并调用setX方法
	private static void setProperty(Object rp, String propertyName, Object value)
			throws IntrospectionException, IllegalAccessException,
			InvocationTargetException {
		PropertyDescriptor pd = new PropertyDescriptor(propertyName,
				rp.getClass());// 建立对象关联
		Method methodSetX = pd.getWriteMethod();// 获取JavaBean类中的setX方法
		methodSetX.invoke(rp, value);// 调用setX方法
	}

	// 获取并调用getX方法第一中方式
	private static Object getProperty1(Object rp, String propertyName)
			throws IntrospectionException, IllegalAccessException,
			InvocationTargetException {
		PropertyDescriptor pd = new PropertyDescriptor(propertyName,
				rp.getClass());// 建立对象关联
		Method methodGetX = pd.getReadMethod();// 获取JavaBean类中的getX方法
		Object retval = methodGetX.invoke(rp);// 调用getX方法
		return retval;
	}

	// 获取调用getX方法的第二种方式
	public static Object getProperty(Object rp, String propertyName)
			throws Exception {
		BeanInfo beanInfo = Introspector.getBeanInfo(rp.getClass());
		PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
		Object retVal = null;
		for (PropertyDescriptor pd : pds) {
			if (pd.getName().equals(propertyName)) {
				Method methodGetX = pd.getReadMethod();
				retVal = methodGetX.invoke(rp);
				break;
			}
		}
		return retVal;
	}
}

六、BeanUtils工具包

1)BeanUtils等工具包都是由阿帕奇提供的,为了便于开发。

2)BeanUtils能够将8种基本数据类型进行自动的转换,所以对于非基本数据类型,就须要注册转换器Converter,这就须要ConverUtils包。

3)好处:

    1)提供的set或get方法中,传入的是字符串,返回的仍是字符串,由于在浏览器中,用户输入到文本框的都是以字符串的形式发送至服务器上的,因此操做的都是字符串。也就是说这个工具包的内部有自动将整数转换为字符串的操做。

    2)支持属性的级联操做,即支持属性链。如能够设置:人的脑壳上的眼睛的眼珠的颜色。这种级联属性的属性连若是本身用反射,那就很困难了,经过这个工具包就能够轻松调用。

4)能够和Map集合进行相互转换:可将属性信息经过键值对的形式做为Map集合存储(经过static java.util.Mapdescribe(java.lang.Object bean)的方法)。也能够将Map集合转换为JavaBean中的属性信息(经过static void populate(java.lang.Objectbean, java.util.Map properties)的方法)。

Note:要正常使用BeanUtils工具,还要将Apache公司的logging(日志)的jar包也添加进Build Path。在工程中导入工具jar包。两种方式:

         a)右键项目--选择Properties---Java Build Path--选择Liberiers标签。AddExternal Jars--选择要导入的jar包便可。

            这样作有个问题就是若是jar路径发生变化,项目就不能使用到这个jar包。

         b)在项目中创建一个lib目录,专门用于存放项目所使用到的jar工具包。将要使用到jar包复制粘贴进来,并在jar上点右键--选择Builder Path---Add to BiuldPath,便可。
            这时jar包中的对象,就可使用了。这样作项目移动,jar随项目移动。

5)BeanUtils工具包中还有一个工具类PropertyUtils,用法跟BeanUtils同样。区别:

    a)BeanUtils会对JavaBean的属性的类型进行转换,如属性自己是integer,会转换为String。

    b)PropertyUtils以属性自己的类型进行操做。

示例:
package cn.itheima.demo;

//接前面的reflectPoint类继续
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

public class IntroSpectorDemo {
	public static void main(String[] args) throws Exception {
		ReflectPoint rp=new ReflectPoint(2,3);
		String propertyName="x";
		//"x"-->"X"-->"getX"-->MethodGetX-->
		
		//用BeanUtils工具包的方法
		System.out.println(BeanUtils.getProperty(hct, propertyName));//get
		BeanUtils.setProperty(rp, propertyName, "9");//set
		
		System.out.println(rp.getX());	
		
		//对于JavaBean中的属性是对象的操做
		BeanUtils.setProperty(rp, "birthday.time", "10");//set
		System.out.println(BeanUtils.getProperty(rp, "birthday.time"));//get
	}
}
3、类加载器
一、概述: 简单说,类加载器就是加载类的工具。 在java程序中用到一个类,出现了这个类的名字。java虚拟机首先将这个类的字节码加载到内存中,一般这个类的字节码的原始信息放在硬盘上的classpath指定的目录下,把.class文件的内容加载到内存里面来,再对它进行处理,处理以后的结果就是字节码。这些工做就是类加载器在操做。

二、默认类加载器:

    1)Java虚拟机中可安装多个类加载器,系统默认的有三个主要的,每一个类负责加载特定位置的类:BootStrap、ExtClassLoader、AppClassLoader

    2)类加载器自己也是Java类,由于它是Java类的加载器,自己也须要被类加载器加载,显然必须有第一个类加载器而不是java类的,这正是BootStrap。它是嵌套在Java虚拟机内核中的,已启动即出如今虚拟机中,是用c++写的一段二进制代码。因此不能经过java程序获取其名字,得到的只能是null。

三、Java虚拟机中的全部类加载器采用父子关系的树形结构进行组织,在实例化每一个类装载器对象时,须要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
四、类加载器的继承关系和管辖范围示例图:


示例:
public class ClassLoaderDemo {

	public static void main(String[] args) throws Exception {
		System.out.println(ClassLoaderDemo.class.getClassLoader().getClass()
				.getName());//获取当前类的类加载器---AppClassLoader
		System.out.println(System.class.getClassLoader());//获取String类的类加载器,由于BootStrap不是Java语言编写的,因此此处返回null
        }
}
五、委托机制

1)每一个ClassLoader自己只能分别加载特定位置和目录中的类,但它们能够委托其余的类加载器去加载类,这就是类加载器的委托模式。

2)加载类的顺序:

    a)首先,当前线程的类加载器去加载线程中的第一个类。

    b)若A引用类B(继承或者使用了B),Java虚拟机将使用加载类的类加载器来加载类B。

    c)还可直接调用ClassLoader的LoaderClass()方法,来指定某个类加载器去加载某个类。

2)每一个类加载器加载类时,又先委托给上级类加载器。类装载器一级级委托到BootStrap类加载器,当BootStrap没法加载当前所要加载的类时,而后才一级级回退到子孙类加载器去进行加载。当回退到最初的发起者类装载器时,若是它本身也不能完成类的装载,那就会抛出ClassNotFoundException异常。这时就不会再委托发起者加载器的子类去加载了,若是它还有子类的话。 简单说,就是先由发起者将类一级级委托为BootStrap,从父级开始找,找到了直接返回,没找到再助剂让其子级找,直到发起者,再没找到就报异常。

3)委托机制的优势:能够集中管理,不会产生多字节码重复的现象。

补充:(面试题)可不能够本身写个类为:java.lang.System呢?

    第一:一般是不能够的,因为类加载器的委托机制,会先将System这个类一级级委托给最顶级的BootStrap,因为BootStrap在其指定的目录中加载的是rt.jar中的类,且其中有System这个类,那么就会直接加载本身目录中的,也就是Java已经定义好的System这个类,而不会加载自定义的这个System。

    第二:可是仍是有办法加载这个自定义的System类的,此时就不能交给上级加载了,须要用自定义的类加载器加载,这就须要有特殊的写法才能去加载这个自定义的System类的。
示例:

package reflect;

public class ClassLoaderDemo {

	public static void main(String[] args) {
		ClassLoader loader=ClassLoaderDemo.class.getClassLoader();
		while (loader!=null) {//循环获取本类的类加载器和上级类加载器
			System.out.println(loader.getClass().getName());
			loader=loader.getParent();//将此loader的上级赋给loader
		}
		System.out.println(loader);
	}
}

六、自定义类加载器

1)概述:自定义的类加载器必须继承抽象类ClassLoader,要覆写其中的findClass(String name)方法,而不用覆写loadClass()方法。

2)覆写findClass(Stringname)方法的缘由:

    a)在loadClass()内部是会先委托给父级,当父级找不到后返回,再调用findClass(String name)方法,也就是你自定义的类加载器去找。因此只须要覆写findClass方法,就能实现用自定义的类加载器加载类的目的。由于,通常自定义类加载器,会把须要加载的类放在本身指定的目录中,而java中已有的类加载器是不知道你这个目录的,因此会找不到。这样才会调用你复写的findClass()方法,用你自定义的类加载器去指定的目录加载类。

    b)这是一种模板方法设计模式。这样就保留了loadClass()方法中的流程(这个流程就是先找父级,找不到再调用自定义的类加载器),而咱们只需复写findClass方法,实现局部细节就好了。ClassLoader提供了一个protected  Class<?>defineClass(String name, byte[] b, int off, int len)方法,只须要将类对应的class文件传入,就能够将其变为字节码。

3)编程步骤:

    a)编写一个对文件内容进行简单加密的程序

    b)编写好了一个本身的类加载器,可实现对加密过来的类进行加载和解密。

    c)编写一个程序,调用类加载器加载类,在源程序中不能用该类名定义引用变量,由于编译器没法识别这个类,程序中除了可以使用ClassLoader的loadClass方法外,还可使用设置线程的上下文类加载器或系统类加载器,而后再使用Class.forName。

4)操做步骤:

    a)对不带包名的class文件进行加密,加密结果存放到另一个目录,例如: java MyClassLoader MyTest.class F:\itcast

    b)运行加载类的程序,结果可以被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast

    c)用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操做就出问题了,错误说明是AppClassLoader类装载器装载失败。

    d)删除CLASSPATH环境下的类文件,再执行上一步操做就没问题了。

示例:
package classloader;

import java.util.Date;
//定义一个测试类,继承Date,便于使用时加载
public class ClassLoaderAttachment extends Date{
	//复写toString方法
	public String toString(){
		return "Hello HeiMa!";
	}
}

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class MyClassLoader extends ClassLoader{

	public static void main(String[] args) throws Exception {
		String srcPath=args[0];//文件源
		String destDir=args[1];//文件目的
		InputStream ips=new FileInputStream(srcPath);
		String destFileName=srcPath.substring(srcPath.lastIndexOf("\\")+1);
		String destFilePath=destDir+"\\"+destFileName;
		OutputStream ops=new FileOutputStream(destFilePath);
		cypher(ips,ops);//加密class字节码
		ips.close();
		ops.close();
	}
	//加密、解密方法
	private static void cypher(InputStream ips,OutputStream ops) throws Exception{
		int b=-1;
		while((b=ips.read())!=-1){
			ops.write(b^0xff);
		}
	}

	@Override
	//覆盖ClassLoader的findClass方法
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		name=name.substring(name.lastIndexOf(".")+1);
		String classFileName=classDir+"\\"+name+".class";//获取class文件名
		InputStream ips=null;
		try {
			ips=new FileInputStream(classFileName);
			ByteArrayOutputStream bos=new ByteArrayOutputStream();//定义字节数组流
			cypher(ips,bos);//解密
			ips.close();
			byte[] buf=bos.toByteArray();//取出字节数组流中的数据
			return defineClass(null, buf,0,buf.length);//加载进内存
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	private String classDir;
	public MyClassLoader(){}
	//带参数的构造函数
	public MyClassLoader(String classDir){
		this.classDir=classDir;
	}

}

import java.util.Date;

public class ClassLoaderDemo {

	public static void main(String[] args) throws Exception {
		//将用自定义的类加载器加载.class文件
		Class clazz=new MyClassLoader("itheimalib").loadClass("cn.itheima.demo.ClassLoaderAttachment");
		Date d1 =  (Date)clazz.newInstance();//获取Class类的实例对象,由于使用ClassLoaderAttachment,编译器会检测出错误,因此用父类Date
		System.out.println(d1);
	}
}
Note:类加载器加载配置文件的方式:
    第一种:
        InputStream ips=ReflectDemo2.class.getClassLoader().getResourceAsStream(“blog/config.properties”);
    第二种:
        InputStream ips=ReflectDemo2.class.getResourceAsStream(“config.properties”);

4、代理

一、概述:生活中的代理,就是常说的代理商,从厂商将商品卖给消费者,消费者不用很麻烦的跑到厂商那里去购买。程序中的代理,要为已经存在的多个具备相同接口的目标类的各个方法增长一些系统功能,如异常处理、日志、计算方法的运行时间、事物管理等等。
二、做用:代理为已存在的多个具备相同接口的目标类的各个方法增长一些系统功能。
(例如: 编写 一个与目标类具备相同接口的代理类, 代理类的每一个方法调用目标类的相同方法,并在 调用方法时加上系统功能的代码。 )
三、代理架构图

示例:

// 接口
interface A {
	void sayHello();
}

// 目标类
class X implements A {
	@Override
	public void sayHello() {
		System.out.println("sayHello");
	}
}

// 代理类
class Y implements A {
	@Override
	public void sayHello() {
		long startTime = System.currentTimeMillis();//获取系统时间--程序开始点
		System.out.println("sayHello");
		long endTime = System.currentTimeMillis();//程序结束点
	}
}

    目标类和代理类一般实现同一个或多个接口,通常用该接口来引用其子类(代理类),如:

        Collection  coll = new ArrayList();

四、代理类的优势:

    若是采用工厂模式和配置文件的方式进行管理,则不须要修改客户端程序,在配置文件中配置是使用目标类仍是代理类。这样之后很容易切换,若是想要日志功能时,就配置代理类,不然配置目标类,这样,增长系统功能很容易,之后运行一段时间后,又想换掉系统功能也很容易。
五、AOP

1)概述:AOP(AspectOriented Program)即面向方面的编程。

2)示意图:(系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面)

                                              安全      事务         日志

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

                CourseService  ------|----------|------------|-------------

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

Note:安全、事务、日志等功能要贯穿于好多个模块中,因此他们就是交叉业务。

3)用具体的程序代码描述交叉业务:

    1)交叉业务的代码实现

                method1         method2          method3

                {                     {                     {

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

                  ....                    ....                  ......

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

                }                     }                      }

    2)交叉业务的编程问题即为面向方面的编程(Aspect orientedprogram ,简称AOP),AOP的目标就是要使交叉业务模块化。能够采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是同样的,以下所示:

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

                func1         func2            func3

                {                {                   {

                ....               ....                  ......

                }                }                    }

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

Note:所以使用代理技术正好能够解决这种问题,代理是实现AOP功能的核心和关键技术,只要是用到面向方面的编程,就涉及到代理,相似于模板方法。

六、动态代理

1)由来:要为系统中的各类接口的类增长代理功能,那将须要太多的代理类,这时就不能所有采用静态代理的方式,由于要写成百上千个代理类是很是麻烦的,这时就需用动态代理技术。

2)定义:JVM能够在运行期,动态生成类的字节码,这种动态生成的类(不是代理,只是拿来用做代理类)每每被用做代理类,即动态代理类。

Note:JVM生成的动态类必须实现一或多个接口,因此JVM生成的动态代理类只能用做具备相同接口的目标类代理。

3)CGLIB库能够动态生成一个类的子类,一个类的子类也能够用做该类的代理。因此,若是要为一个没有实现接口的类生成动态代理类,那么可使用CGLIB库。

4)代理类各个方法一般除了调用目标相应方法和对外返回目标返回的结果外,还能够在代理方法中的以下位置上加上系统功能代码:

    a)在调用目标方法以前

    b)在调用目标方法以后

    c)在调用目标方法先后

    d)在处理目标方法异常的catch块中。

5)分析JVM动态生成的类

以建立实现了Collection接口的代理类为例:

a)用Proxy.getProxyClass方法建立实现了Collection接口的动态类和查看其名称,分析getProxyClass方法的各个参数。

    1)编码列出动态类中的全部构造方法和参数签名

    2)编码列出动态类中的全部方法和参数签名 

b)建立动态类的实例对象

    1)用反射得到构造方法

    2)编写一个最简单的InvocationHandler类(代理类构造方法接收的参数)

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

    4)打印建立的对象和调用对象的没有返回值的方法和getClass方法,演示调用其余有返回值的方法报告了异常。

    5)将建立动态类的实例对象的代理改为匿名内部类的形式编写,锻炼匿名内部类的使用。

c)也能够直接用Proxy.newInstance方法直接一步就建立出代理对象。

d)让JVM建立动态类及其实例对象,须要提供的信息:

    1)生成类中的哪些方法,经过让其实现哪些接口的方式进行告知。

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

    3)生成的类中的方法的代码是怎么样的,也得由咱们本身提供。把咱们的代码写在一个约定好的子接口对象的方法中,把对象传给它,它调用咱们的方法,即至关于插入了咱们本身的代码。提供执行代码的对象就是InvocationHandler对象,它是在建立动态类的实例对象的构造方法时传递进去的,在上面的InvocationHandler对象的invoke方法中,加一点代码就能够看到这些代码被调用运行了。

6)总结分析动态代理类的统计原理和结构

1)怎样将目标传进去:

    a)直接在InvocationHandler实现类中建立目标类的实例对象,可看运行效果和加入日志代码,可是没有实际意义。

    b)为InvocationHandler实现类注入目标的实例对象,不能采用匿名内部类的形式了。

    c)让匿名内部类的InvocationHandler实现类访问外面的方法中的目标类实例对象的final类型的引用变量。

2)动态代理的工做原理:

    a)Client(客户端)调用代理,代理的构造方法接收一个InvocationHandler,client调用代理的各个方法,代理的各个方法请求转发给刚才经过构造方法传入的handler对象,又把各请求分发给目标的相应的方法。

示意图:

 

    b)将建立代理的过程改成一种更优雅的方式,eclipse重构出一个getProxy方法绑定接收目标,同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。在这里将InvocationHandler加入到Proxy的构造方法中,所以,在建立出来的对象,就会存有构造方法中InvocationHandler的一些功能和信息,由于咱们把想要运行的代码封装在InvocationHandler对象,把它传入到构造函数中,那么就实现了代理对象每次调用与目标方法相同方法(由于实现了同一接口)时,都会调用咱们加入到InvocationHandler对象中的代码。这就保证了每次调用代理时,能够在目标上加入咱们本身加入的功能。

3)把系统功能代理模块化,即切面代码也改成经过参数形式提供,怎么把要执行的系统功能代码以参数的形式提供:

    a)把要执行的代码装到一个对象的某个方法中,而后把此对象做为参数传递,接收者只要调用这个对象的方法,即等于执行了外接提供的代码。

    b)为getProxy方法增长一个Advice参数。

示例:
package proxy;

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 ProxyDemo {

	public static void main(String[] args) throws Exception{
		final ArrayList target=new ArrayList();//指定目标
		Advice advice=new MyAdvice();//将要添加的代码封装成对象
		//使用Proxy提供的静态newProxyInstance方法来一步到位的建立代理类实例对象
		Collection proxy3 = (Collection)getProxy(target,advice);
		 
		proxy3.add("it");
		proxy3.add("hei");
		proxy3.add("ma");
		System.out.println(proxy3.size());//3
	}

	//做为一个通用的方法,就使用Object  
	//传入一个目标,并传入一个接口,此接口做为通讯的契约,才能调用额外的方法
	private static Object getProxy(final Object target,final Advice advice) {
		Object proxy3=Proxy.newProxyInstance(
				//第一个参数,定义代理类的类加载器
				target.getClass().getClassLoader(),
				//第二个参数,代理类要实现的接口列表,这里要与target实现相同的接口
				//new Class[]{Collection.class},
				target.getClass().getInterfaces(),
				//第三个参数,代理类的构造函数的参数
				new InvocationHandler() {
					
					@Override//复写invoke方法
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						
						//使用约定的对象中的方法
						advice.beforeMehod();
						Object retval=method.invoke(target, args);//调用目标
						advice.afterMethod(method);
						return retval;	
					}
				});
		return proxy3;
	}
}

import java.lang.reflect.Method;
//只要实现Advice中的方法,里面的代码功能能够随便定义,调用代理时就会被使用
public class MyAdvice implements Advice {
	long startTime;
	@Override
	public void beforeMehod() {
		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:"+(endTime-startTime));
	}
}

import java.lang.reflect.Method;

//这里用两个做为示例,建立Advice接口
public interface Advice {
	void beforeMehod();
	void afterMethod(Method method);
}

七、实现AOP功能的封装与配置

工厂类BeanFactory

1)工厂类BeanFactory负责建立目标类或代理类的实例对象,并经过配置文件实现切换。

2)getBean方法根据参数字符串返回一个相应的实例对象,若是参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,不然返回该类示例对象的getProxy方法返回的对象。

3)BeanFactory的构造方法接收表明配置文件的输入流对象的配置文件格式以下:

    #xxx=java.util.ArrayList     (#标识注释此行)

    xxx=proxy.aopframework.ProxyFactoryBean

    xxx.advice=proxy.MyAdvice

    xxx.target=java.util.ArrayList

4)ProxyFactoryBean充当封装成动态的工厂,需为工厂提供的配置参数信息包括:目标(target  通知(advice

5)BeanFactory和ProxyFactoryBean:

    a)BeanFactory是一个纯粹的bean工程,就是建立bean即相应的对象的工厂。

    b)ProxyfactoryBean是BeanFactory中的一个特殊的Bean,是建立代理的工厂。

实现相似spring的可配置的AOP框架的思路

1)建立BeanFactory类:

    a)构造方法:接受一个配置文件,经过Properties对象加载InputStream流对象得到。

    b)建立getBean(String name)方法,接收Bean的名字,从上面加载后的对象得到。

    c)经过其字节码对象建立实例对象bean。

    d)判断bean是不是特殊的Bean即ProxyFactoryBean,若是是就要建立代理类,并设置目标和通告,分别获得各自的实例对象,并返回代理类实例对象。不然返回普通类的实例对象。

2)建立ProxyFactoryBean(接口),此处直接定义为类作测试,其中有一个getProxy方法,用于得到代理类对象。

3)编写实现Advice接口的类和在配置文件中进行配置。
4)定义一个测试类:AopFrameworkTest,也称客户端,调用BeanFactory获取对象。

示例:

package aopframework;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Properties;

/*
 * 配置文件config.properties中的信息以下:
 xxx=java.util.ArrayList
 xxx=aopframework.ProxyFactoryBean
 xxx.advice=proxy.MyAdvice
 xxx.target=java.util.ArrayList
 */

//两个示例方法,封装在Advice接口中
interface Advice {
	public void beforeMethod(Method method);

	public void afterMethod(Method method);
}

// 建立BeanFactory类,用于建立目标类或者代理类的实例对象。
class BeanFactory {
	// 定义properties集合用
	Properties props = new Properties();

	public BeanFactory(InputStream in) {
		try {
			props.load(in);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	// 从配置文件中获取对象
	public Object getBean(String name) {
		String className = props.getProperty(name);// 获取配置文件中的值
		Class<?> clazz = null;
		Object bean = null;
		try {
			clazz = Class.forName(className);// 经过反射建立对象
			bean = clazz.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 若是建立的对象是ProxyFactoryBean类型,则经过getProxy方法获取代理类对象
		if (bean instanceof ProxyFactoryBean) {
			ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean;
			Advice advice = null;
			Object target = null;
			try {
				// 从配置文件中获取代理类额外添加的代码封装成的对象
				advice = (Advice) Class.forName(
						props.getProperty(name + ".advice")).newInstance();
				// 从配置文件中获取目标
				target = Class.forName(props.getProperty(name + ".target"))
						.newInstance();
			} catch (Exception e) {
				e.printStackTrace();
			}
			proxyFactoryBean.setAdvice(advice);
			proxyFactoryBean.setTarget(target);
			// 调用getProxy方法,获取代理对象
			Object proxy = (proxyFactoryBean).getProxy();
			return proxy;
		}
		return bean;
	}
}

// 建立ProxyFactoryBean类,用于产生代理类实例对象
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;
	}

	Object getProxy() {
		// 第一个参数target.getClass().getClassLoader(),定义代理类的类加载器
		// 第二个参数target.getClass().getInterfaces(),代理类要实现的接口列表,这里要与target实现相同的接口
		// 第三个参数new InvocationHandler() {...},代理类的构造函数的参数
		Object proxy3 = Proxy.newProxyInstance(target.getClass()
				.getClassLoader(), target.getClass().getInterfaces(),
				new InvocationHandler() {
					@Override
					// 复写invoke方法
					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;
	};
}

public class AopFrameworkTest {

	public static void main(String[] args) {
		InputStream in = AopFrameworkTest.class
				.getResourceAsStream("config.properties");
		Object bean = new BeanFactory(in).getBean("xxx");
		System.out.println(bean.getClass().getName());
		((Collection<?>) bean).clear();
	}

}
    至此,高新技术阶段的主要知识:反射、内省、代理基本讲说完毕,因为和后期学习Java EE关系密切,因此知识点很重要。
     以上所述仅表明我的观点,若有出入请谅解。
相关文章
相关标签/搜索