黑马程序员-------------(十一)Java基础知识加强(二)
反射、内省、类加载器、代理
目录
一 反射
1.概述
2.Class类-反射的基石
3.Constructor类
4.Field类
5.Method类
6.数组的反射
二 内省
1.内省的核心类
2.JavaBean
3.内省综合案例
4.Beanutils工具包
三 类加载器
1.类加载器和类加载器的作用
2.类加载器的委托机制
3.自定义类加载器
4.一个类加载器的高级问题分析
四 代理
####################################################################################
一 反射
####################################################################################
1.概述
(1)反射
反射就是把Java类中的各种成分映射成相应的java类。一个Java类用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,这些实例对象的作用以及如何使用是学习和应用反射的要点。
(2)反射的作用:实现框架功能
(3)什么是框架?
房地产商人做房子卖给我,我自己安装门,我还需要为门买一把锁。在这里房子就是框架,门就是我写的类,锁就是提供的工具类,工具类被我的类调用,而框架则是调用我提供的类。
(4)框架要解决的核心问题
写框架时,还不知道以后写的类。那框架程序怎样能调用到以后写的类呢?因为在写框架时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射方式来做。
2.Class类-反射的基石
(1)概述
Java程序中的各个Java类属于同一类事物,可以用一个类来描述这类事物,就是Class。Class类代表Java类,它的各个实例对象又分别对应各个类在内存中的字节码,一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示。学习反射,首先就要明白Class这个类。每个java类都是Class的一个实例对象。
(2)得到字节码的三种方式:
1)类名.class,例如,System.class
2)对象.getClass(),例如,new Date().getClass()
3)Class.forName("类名"),例如,Class.forName("java.util.Date");
注意:
Class.forName()返回字节码的方式有两种:第一种是这份字节码曾经被加载过,已经存在于虚拟机中,直接返回。还有一种是java虚拟机中还没有这份字节码,则用类加载器去加载,把加载进来的字节码缓存在虚拟机中以后要得到这份字节码就不用再加载了
(3)常用方法:
public Field getField(String name)
public Method getMethod(String name, Class<?>... parameterTypes)
public Constructor<T> getConstructor(Class<?>... parameterTypes)
public Field getDeclaredField(String name)
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
public T newInstance()
public static Class<?> forName(String className)
(4)九种预定义的Class:
包括八种基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和一种返回值为void类型的void.class 。
注意,基本类型的字节码和包装类的字节码不一样,例如,int.class 和Integer.class 对应的字节码不一样。但是,Integer.TYPE表示此包装类型包装的基本类型的字节码,所以int.class 和Integer.TYPE是一致的。9个预定义类型都是如此。
(5)自定义一个Person类
1 public class Person { 2 private int age; 3 public String name; 4 5 public Person() { 6 System.out.println("person"); 7 } 8 private Person(String name, int age) { 9 this.name = name; 10 this.age = age; 11 System.out.println(name+";"+age); 12 } 13 public void run(){ 14 System.out.println("run"); 15 } 16 private void run(String name){ 17 System.out.println(name+" run"); 18 } 19 public static void run1(){ 20 System.out.println("run1"); 21 } 22 public static void main(String[] args) { 23 System.out.println("main"); 24 for(String string:args){ 25 System.out.println(string); 26 } 27 } 28 }
3.Constructor类
Constructor类代表某个类中的一个构造方法
(1)常用方法
public T newInstance(Object... initargs)
public void setAccessible(boolean flag)
(2)通过反射用构造函数创建对象步骤:
1)通过Class类的forName()方法得到类的字节码;
2)通过Class对象的getConstructor()方法得到一个指定构造方法:
3)调用构造方法对象的newInstance()方法创建实例对象:
(3)代码示例
对于Person类
1 //得到公有空参数构造方法 2 public void test1() throws Exception{ 4 Class clazz=Class.forName("cn.itcast.reflect.Person"); 5 Constructor con= clazz.getConstructor(null); 6 Person p1=(Person) con.newInstance(null); 7 System.out.println(p1); 8 } 9 //得到私有对应参数构造方法 10 public void test2() throws Exception{ 12 Class clazz=Class.forName("cn.itcast.reflect.Person"); 13 Constructor con= clazz.getDeclaredConstructor(String.class,int.class);//可以得到私有的构造方法 14 //暴力反射,打开私有构造方法的权限 15 con.setAccessible(true); 16 Person p1=(Person) con.newInstance("zs",20); 17 System.out.println(p1); 18 }
4.Field类
Field类代表某个类中的一个成员变量
(1)常用方法
public Class<?> getType()
public Object get(Object obj)
public void set(Object obj, Object value)
public void setAccessible(boolean flag)
(2)通过反射设置和获取字段步骤:
1)通过Class类的forName()方法得到类的字节码;
2)通过Class对象的getField()方法得到一个指定字段:
3)调用字段对象的set()和get()方法。
(3)代码示例
1 //得到公有字段 2 public void test3() throws Exception{ 3 Person p=new Person(); 4 Class clazz=Class.forName("cn.itcast.reflect.Person"); 5 Field field=clazz.getField("name"); 6 System.out.println(p.name); 7 field.set(p, "abc"); 8 System.out.println(p.name); 9 } 10 11 //得到私有字段 12 public void test4() throws Exception{ 13 Person p=new Person(); 14 Class clazz=Class.forName("cn.itcast.reflect.Person"); 15 Field field=clazz.getDeclaredField("age"); 16 field.setAccessible(true); 17 field.set(p, 20); 18 System.out.println(field.get(p)); 19 }
(4)练习:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。
1 public static void changeStringValue(Object obj) throws Exception{ 2 Field[] fields=obj.getClass().getFields(); 3 for(Field field:fields){ 4 //这里应该用==,因为是同一份字节码 5 if(field.getType()==(String.class)){ 6 String oldValue=(String)field.get(obj); 7 String newValue=oldValue.replace('b', 'a'); 8 field.set(obj, newValue); 9 } 10 } 11 }
5.Method类
Method类代表某个类中的一个成员方法
(1)常用方法
public Object invoke(Object obj, Object... args)
public void setAccessible(boolean flag)
(2)通过反射调用方法步骤:
1)通过Class类的forName()方法得到类的字节码;
2)通过Class对象的getMethod()方法得到一个指定方法:
3)通过方法对象的invoke()方法调用方法。
(2)代码示例
1 //得到公有非静态方法 2 public void test5() throws Exception{ 3 Person p=new Person(); 4 Class clazz=Class.forName("cn.itcast.reflect.Person"); 5 Method method=clazz.getMethod("run", null); 6 method.invoke(p, null); 7 } 8 9 //得到私有非静态方法 10 public void test6() throws Exception{ 11 Person p=new Person(); 12 Class clazz=Class.forName("cn.itcast.reflect.Person"); 13 Method method=clazz.getDeclaredMethod("run", String.class); 14 method.setAccessible(true); 15 method.invoke(p, "zcg"); 16 } 17 18 //得到公有静态方法 19 public void test7() throws Exception{ 20 Class clazz=Class.forName("cn.itcast.reflect.Person"); 21 Method method=clazz.getMethod("run1", null); 22 method.invoke(null, null); 23 } 24 25 //得到主函数 26 public void test8() throws Exception{ 27 Person p=new Person(); 28 Class clazz=Class.forName("cn.itcast.reflect.Person"); 29 Method mainMethod=clazz.getMethod("main", String[].class); 30 mainMethod.invoke(null, new Object[]{new String[]{"aa","bb"}}); 31 mainMethod.invoke(null, (Object)new String[]{"aa","bb"}); 32 33 }
注意:
a.如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法。
b.通过反射方式来调用main方法时,如何为invoke方法传递参数呢?
按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,jdk1.5肯定要兼容jdk1.4的语法,因此javac会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码 mainMethod.invoke(null,new String[]{"aa","bb"});,javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。解决办法:
方法一:mainMethod.invoke(null, new Object[]{new String[]{"aa","bb"}});
方法二:mainMethod.invoke(null, (Object)new String[]{"aa","bb"});
通过这两种方法,编译器编译时就不会把参数当作数组看待,也就不会数组打散成若干个参数了
6.数组的反射
具有相同维数并且相同元素类型的数组属于同一个类型,即具有相同的Class实例对象(此处比较与值无关)。Array工具类用于完成对数组的反射操作。
int[] a1 = new int[]{2,5,7};
int[] a2 = new int[4];
int[][] a3 = new int[3][5];
String[] a4 = new String[]{"d","j","f"};
所有一维数组的父类都是Object,基本数据类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本数据类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。二维数组可以看作Object类型,也可以看做Object[]。也就是说,整个一维数组、二维数组中的数组都可以看做Object类型。例如,a1、a3的元素、a4的元素都是Object。另外,引用数据的数组既可以当做Object,也可以当做Object[]。
Arrays的asList()方法在处理基本类型数组(int[])和引用类型数组(String[])时的差异:
传入引用数据的数组将变成集合,数组中的元素变成集合中的元素;传入的基本数据的数组会变成一个Object对象。因为JDK1.4中,asList()接受的是数组Object[];JDK1.5中,asList()接受的是可变参数T…a,T可以是基本型或引用型。JDK1.5需要向下兼JDK1.4。a4会按照1.4处理,直接打印数组内容;a1会按照1.5处理,打印出哈希值。想要打印基本数据的数组,可以逐个元素录入,或者使用反射中的Array工具类对数组进行反射操作。Class中拥有Arrays没有的方法,例如判断对象所属的类是否是数组等,再用Array中的方法操作数组。这些方法基本是静态方法,需要将数组对象传入。例如,打印对象,类似拆包。
1 public static void printObject(Object obj){ 2 Class oss = obj.getClass();//变成Class对象进行判断。 3 if(oss.isArray()){ 4 int len = Array.getLength(obj); 5 for(int i=0; i<len; i++){ 6 System.out.println(Array.get(obj, i)); 7 } 8 }else 9 System.out.println(obj); 10 }
注意:
目前还无法通过数组中的元素获得数组的类型,因为基本数据无法使用getClass()方法,引用数据可以使用该方法。因为getClass()方法属于Object类。通过getClass().getName()获得该元素的类型。考虑到多态,无法确定数组的类型。
####################################################################################
二 内省
####################################################################################
JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省
1.内省的核心类
Introspector类
public static BeanInfo getBeanInfo(Class<?> beanClass)
BeanInfo类
public PropertyDescriptor[] getPropertyDescriptors()
PropertyDescriptor类
public Method getReadMethod()
public Method getWriteMethod()
2.JavaBean
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,JavaBean的属性是根据其中的set和get方法来确定的,而不是根据其中的成员变量。去掉set和get前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。
注意:
一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
3.内省综合案例,读Person类的age属性进行设置和获取
1 public static void main(String[] args) throws Exception { 2 3 //创建Person对象 4 Person p=new Person(); 5 String propertyName="age"; 6 Object retVal = getProperty(p, propertyName); 7 System.out.println(retVal); 8 9 Object value=7; 10 setProperty(p, propertyName, value); 11 System.out.println(getProperty(p,propertyName)); 12 } 13 //Set方法 14 private static void setProperty(Object p, String propertyName, Object value) 15 throws IntrospectionException, IllegalAccessException, 16 InvocationTargetException { 17 18 //得到p对象的属性描述器 19 PropertyDescriptor pd=new PropertyDescriptor(propertyName,p.getClass()); 20 21 //获取相应的read和write方法 22 Method setMethod=pd.getWriteMethod(); 23 setMethod.invoke(p, value); 24 } 25 //Get方法 26 private static Object getProperty(Object p, String propertyName) 27 throws IntrospectionException, IllegalAccessException, 28 InvocationTargetException { 29 30 //得到p对象的属性描述器 31 PropertyDescriptor pd=new PropertyDescriptor(propertyName,p.getClass()); 32 33 //获取相应的read和write方法 34 Method getMethod=pd.getReadMethod(); 35 Object retVal=getMethod.invoke(p); 36 return retVal; 37 }
4.Beanutils工具包
为JavaBean提供更多、放方便的功能。
(1)导包
beanutils.jar = beanutils-core.jar + beanutils-bean-collections.jar,可以通过BuildPath,添加额外的jar包,或者工程下建立lib目录,将jar包复制进来,再加载这个jar包:右键—>add to BuildPath。使用时需要导包:org.apache.commons.beanutils.BeanUtils。需要配合使用acpche提供的日志包:logging。
(2)方法
获得属性的值,例如,BeanUtils.getProperty(p,"name"),返回字符串
设置属性的值,例如,BeanUtils.setProperty(p,"age",22),参数是字符串或基本类型自动包装。设置属性的值是字符串,获得的值也是字符串,不是基本类型。
(3)BeanUtils的特点:
1)对基本数据类型的属性的操作:在WEB开发、使用中,录入和显示时,值会被转换成字符串,但底层运算用的是基本类型,这些类型转到动作由BeanUtils自动完成。get属性时返回的结果为字符串,set属性时可以接受任意类型的对象,通常使用字符串。
2)对引用数据类型的属性的操作:首先在类中必须有对象,不能是null,例如,private Date birthday=new Date();。操作的是对象的属性而不是整个对象,例如,BeanUtils.setProperty(pt1,"birthday.time",121);
(4)PropertyUtils的特点和BeanUtils不同在于,运行getProperty、setProperty操作时,没有类型转换,使用属性的原有类型或者包装类。get属性时返回的结果为该属性本来的类型,set属性时只接受该属性本来的类型。
####################################################################################
三 类加载器
####################################################################################
1.类加载器和类加载器的作用
Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap用于加载JRE\lib\rt.jar;ExtClassLoader用于加载JRE\lib\ext\*.jar;AppClassLoader用于加载classpath指定的所有jar或目录。类加载器也是Java类,因为java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是不是java类,这正是BootStrap。Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
代码示例
1 public class ClassLoaderTest { 2 public static void main(String[] args) { 3 System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());//打印sun.misc.Launcher$AppClassLoader 4 System.out.println(System.class.getClassLoader().getClass().getName());//抛NullPointerException,这两个类存放位置不同 5 System.out.println(System.class.getClassLoader());//打印的结果为null。使用的是BootStrap,它是由C++编写的,不是java编写的 6 7 ClassLoader loader=ClassLoaderTest.class.getClassLoader(); 8 //打印出当前的类装载器,及该类装载器的各级父类装载器 9 while(loader!=null){ 10 System.out.println(loader.getClass().getName()); 11 loader=loader.getParent(); 12 } 13 System.out.println(loader); 14 /*该循环打印结果 15 sun.misc.Launcher$AppClassLoader 16 sun.misc.Launcher$ExtClassLoader 17 null*/ 18 } 19 }
2.类加载器的委托机制
当Java虚拟机要加载一个类时,首先当前线程的类加载器去加载线程中的第一个类A。如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B。还可以直接调用ClassLoader.loadClass(String name)方法来指定某个类加载器去加载某个类。每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类加载器去加载类,这就是类加载器的委托模式。类加载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的加载,就会抛出ClassNotFoundException异常。而不是再去找发起者类加载器的子类,
3.自定义类加载器
自定义的类加载器的必须继承一个抽象类ClassLoader,用的是模版方法设计模式,继承并调用loadClass(Strign name)方法,复写findClass(Strign name)方法。loadClass方法内部是通过调用defineClass(String name, byte[] b, int off, int len)方法将class文件的二进制数据转成class字节码。
练习:编写一个对文件内容进行简单加密的类加载器。
思路:
(1)编写一个程序,对传入的类进行简单加密。
(2)编写一个自己的类装载器,可实现对加密过的类进行加载和解密。
(3)调用类加载器加载类时,源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中除了可以使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。
1 import java.io.ByteArrayOutputStream; 2 import java.io.FileInputStream; 3 import java.io.FileOutputStream; 4 import java.io.InputStream; 5 import java.io.OutputStream; 6 7 //定义一个类,继承ClassLoader 8 public class MyClassLoader extends ClassLoader{ 9 private String classDir; 10 public MyClassLoader() { 11 super(); 12 } 13 public MyClassLoader(String classDir) { 14 super(); 15 this.classDir = classDir; 16 } 17 //向main方法传递两个参数,分别是源class文件的绝对路径和目标的相对路径 18 public static void main(String[] args) throws Exception { 19 String srcPath=args[0]; 20 String destDir=args[1]; 21 String destFileName=srcPath.substring(srcPath.lastIndexOf("\\")+1); 22 String destPath=destDir+"\\"+destFileName; 23 FileInputStream fis=new FileInputStream(srcPath); 24 FileOutputStream fos=new FileOutputStream(destPath); 25 cypher(fis,fos); 26 fis.close(); 27 fos.close(); 28 } 29 //这是简单的加密程序 30 private static void cypher(InputStream ips,OutputStream ops) throws Exception{ 31 int b=-1; 32 while((b=ips.read())!=-1){ 33 ops.write(b^0xff); 34 } 35 ips.close(); 36 ops.close(); 37 } 38 //复写父类的findClass方法 39 @Override 40 protected Class<?> findClass(String name) throws ClassNotFoundException { 41 String classFileName=classDir+"\\"+name.substring(name.lastIndexOf(".")+1)+".class"; 42 FileInputStream fis=null; 43 ByteArrayOutputStream bos=new ByteArrayOutputStream(); 44 try { 45 fis=new FileInputStream(classFileName); 46 cypher(fis,bos); 47 } catch (Exception e) { 48 e.printStackTrace(); 49 } 50 byte[] bytes=bos.toByteArray(); 51 return defineClass(null, bytes, 0, bytes.length); 52 } 53 }
4.一个类加载器的高级问题分析
编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,在Tomcat服务器上正常发布后,看到打印结果为WebAppClassloader。把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader。这是因为MyServlet.class文件导入ext目录后由ExtclassLoader加载器加载,因为MyServlet.class用到了HttpServlet,所以HttpServlet也需要由ExtclassLoader加载器加载,但是ext目录下没有HttpServlet,所以会报错。将HttpServlet所在的servlet.jar也放到ext目录中后,ExtclassLoader加载器就可以成功的加载MyServlet了。
####################################################################################
四 代理 Proxy
####################################################################################
1.代理的概念与作用
(1)生活中的代理
武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗?
(2)程序中的代理
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,可以编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
2.AOP
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务
3.动态代理技术
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式将是一件非常麻烦的事情!JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
4.代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
(1)在调用目标方法之前
(2)在调用目标方法之后
(3)在调用目标方法前后
(4)在处理目标方法异常的catch块中
5.让jvm创建动态类及其实例对象,需要给它提供哪些信息?
(1)接口提供动态类中的方法。生成的类中的方法通过让其实现哪些接口的方式进行告知;
(2)类加载器对象提供动态类的加载器。产生的类字节码必须有个关联的类加载器对象;
(3)InvocationHandler对象提供动态类中方法的代码。生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。
注意:
用Proxy.newInstance()方法可以直接一步就创建出代理对象。
6.让动态生成的类成为目标类的代理
(1)直接在InvocationHandler实现类中创建目标类的实例对象
(2)为InvocationHandler实现类注入目标类的实例对象
(3)让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。
7.实现AOP功能的封装与配置
(1)工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。
BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
#xxx=java.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.MyAdvice
(2)ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?
1)目标
2)通知
(3)编写客户端应用:
1)编写实现Advice接口的类和在配置文件中进行配置
2)调用BeanFactory获取对象
(4)代码示例
//配置文件
config.properties
#xxx=java.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.MyAdvice
//Bean工厂
1 import java.io.IOException; 2 import java.io.InputStream; 3 import java.util.Properties; 4 public class BeanFactory { 5 public static void main(String[] args) { 6 7 //读取配置文件 8 InputStream ips = BeanFactory.class.getResourceAsStream("config.properties"); 9 BeanFactory beanFactory = new BeanFactory(ips); 10 11 //获取配置文件给定名称的java对象 12 Object obj = beanFactory.getBean("xxx"); 13 System.out.println(obj.getClass().getName()); 14 System.out.println(obj.toString()); 15 } 16 private InputStream ips = null; 17 private Properties props = new Properties(); 18 public BeanFactory(InputStream ips){ 19 this.ips = ips; 20 try { 21 props.load(ips); 22 } catch (IOException e) { 23 e.printStackTrace(); 24 } 25 } 26 Object getBean(String beanName){ 27 Object obj = null; 28 try { 29 String className = props.getProperty(beanName); 30 Class clazz = Class.forName(className); 31 obj = clazz.newInstance(); 32 if(obj instanceof ProxyFactoryBean){ 33 ProxyFactoryBean factoryBean = (ProxyFactoryBean)obj; 34 String adviceName = props.getProperty(beanName + ".advice"); 35 String targetName = props.getProperty(beanName + ".target"); 36 factoryBean.setAdvice((Advice)Class.forName(adviceName).newInstance()); 37 factoryBean.setTarget(Class.forName(targetName).newInstance()); 38 obj = factoryBean.getProxy(); 39 } 40 } catch (Exception e) { 41 e.printStackTrace(); 42 } 43 return obj; 44 } 45 }
//代理
1 import java.lang.reflect.Constructor; 2 import java.lang.reflect.InvocationHandler; 3 import java.lang.reflect.InvocationTargetException; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Proxy; 6 import java.util.Collection; 7 public class ProxyFactoryBean { 8 Advice advice = null; 9 Object target = null; 10 public Advice getAdvice() { 11 return advice; 12 } 13 public void setAdvice(Advice advice) { 14 this.advice = advice; 15 } 16 public Object getTarget() { 17 return target; 18 } 19 public void setTarget(Object target) { 20 this.target = target; 21 } 22 //AOP框架 23 public Object getProxy(){ 24 Class clazz1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); 25 class MyInvocationHander implements InvocationHandler { 26 public Object invoke(Object proxy, Method method, Object[] args) 27 throws Throwable { 28 advice.beforeMethod(); 29 Object retVal = method.invoke(target, args); 30 advice.afterMethod(); 31 return retVal; 32 } 33 } 34 Object proxy = null; 35 //Collection proxy = (Collection)clazz.newInstance(); 36 try { 37 Constructor constructor = clazz1.getConstructor(InvocationHandler.class); 38 proxy = constructor.newInstance(new MyInvocationHander()); 39 } catch (Exception e) { 40 e.printStackTrace(); 41 } 42 return proxy; 43 } 44 } 45 //Advice接口 46 public class MyAdvice implements Advice 47 { 48 public void afterMethod() { 49 System.out.println("从传智毕业习啦"); 50 } 51 public void beforeMethod() { 52 System.out.println("来传智学习啦"); 53 } 54 55 }