---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------
Java高新技术(内省、注解、泛型、类加载器、代理)
内省—>了解JavaBean
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object简称OV)。这些信息在类中用私有字段来存储。如果读取或设置这些字段的值,则需要通过一些相应的方法来访问。JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上去,不用管。去掉set前缀,后面就是属性名,如果剩余部分的第二个字母小写则把剩余部分的首字母小写。
setId()的属性名——》id
isLast()的属性名——》last
getCPU()的属性名——》CPU
总之,一个类被当做JavaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
一个符合JavaBean特点的类可以当做普通类一样进行使用,但把它当做JavaBean使用肯定需要带来一些额外的好处,我们才会去了解和应用它,好处如下:
在JavaEE开发中,经常要使用到JavaBean。很多环境就要求按照JavaBean方式进行操作,别人都这么要求和使用,你就别无选择了!
JDK中提供了对JavaBean进行操作的一些API这套API就称为内省。如果你要自己去通过getX方法来访问私有的x,用内省比普通方法要更方便。
package cn.itcast.day1; 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; import java.util.Map; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.PropertyUtils; public class IntroSpectorTest { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub ReflectPoint pt1 = new ReflectPoint(3,5); String propertyName = "x"; //"x"-->"X"-->"getX"-->MethodGetX--> Object retVal = getProperty(pt1, propertyName); System.out.println(retVal); Object value = 7; setProperties(pt1, propertyName, value); System.out.println(BeanUtils.getProperty(pt1, "x").getClass().getName()); BeanUtils.setProperty(pt1, "x", "9"); System.out.println(pt1.getX()); /* //java7的新特性 Map map = {name:"zxx",age:18}; BeanUtils.setProperty(map, "name", "lhm"); */ BeanUtils.setProperty(pt1, "birthday.time", "111"); System.out.println(BeanUtils.getProperty(pt1, "birthday.time")); PropertyUtils.setProperty(pt1, "x", 9); System.out.println(PropertyUtils.getProperty(pt1, "x").getClass().getName()); } private static void setProperties(Object pt1, String propertyName, Object value) throws IntrospectionException, IllegalAccessException, InvocationTargetException { PropertyDescriptor pd2 = new PropertyDescriptor(propertyName,pt1.getClass()); Method methodSetX = pd2.getWriteMethod(); methodSetX.invoke(pt1,value); } private static Object getProperty(Object pt1, String propertyName) throws IntrospectionException, IllegalAccessException, InvocationTargetException { /*PropertyDescriptor pd = new PropertyDescriptor(propertyName,pt1.getClass()); Method methodGetX = pd.getReadMethod(); Object retVal = methodGetX.invoke(pt1);*/ BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass()); PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); Object retVal = null; for(PropertyDescriptor pd : pds){ if(pd.getName().equals(propertyName)) { Method methodGetX = pd.getReadMethod(); retVal = methodGetX.invoke(pt1); break; } } return retVal; } }
</pre></p><p></p><p></p><p><strong><span style="font-size:14px;">Beanutils<span style="font-family:宋体;">工具包</span></span></strong></p><p> 演示用<span style="font-family:Consolas;">eclipse</span><span style="font-family:宋体;">如何加入</span><span style="font-family:Consolas;">jar</span><span style="font-family:宋体;">包,先只是引入</span><span style="font-family:Consolas;">beanutils</span><span style="font-family:宋体;">包,等程序出错之后再引入</span><span style="font-family:Consolas;">logging</span><span style="font-family:宋体;">包。</span></p><p> 在前面内省的例子基础之上,用<span style="font-family:Consolas;">BeanUtils</span><span style="font-family:宋体;">类先</span><span style="font-family:Consolas;">get</span><span style="font-family:宋体;">原来设置好的属性,再将其</span><span style="font-family:Consolas;">set</span><span style="font-family:宋体;">为一个新值。</span></p><p> get<span style="font-family:宋体;">属性时返回的结果为字符串,</span><span style="font-family:Consolas;">set</span><span style="font-family:宋体;">属性时可以接受任意类型的对象,通常使用字符串。</span></p><p> 用<span style="font-family:Consolas;">PropertyUtils</span><span style="font-family:宋体;">类先</span><span style="font-family:Consolas;">get</span><span style="font-family:宋体;">原来设置的属性,再将其</span><span style="font-family:Consolas;">set</span><span style="font-family:宋体;">为一个新值,</span> get<span style="font-family:宋体;">属性时返回的结果为该属性本来的类型,</span><span style="font-family:Consolas;">set</span><span style="font-family:宋体;">属性只接受该属性本来的类型。</span></p><p><pre class="java" name="code" snippet_file_name="blog_20140708_3_4691615" code_snippet_id="422055"><span style="white-space:pre"> </span>System.out.println(BeanUtils.getProperty(pt1, "x").getClass().getName()); BeanUtils.setProperty(pt1, "x", "9"); System.out.println(pt1.getX()); /* //java7的新特性 Map map = {name:"zxx",age:18}; BeanUtils.setProperty(map, "name", "lhm"); */ BeanUtils.setProperty(pt1, "birthday.time", "111"); System.out.println(BeanUtils.getProperty(pt1, "birthday.time")); PropertyUtils.setProperty(pt1, "x", 9); System.out.println(PropertyUtils.getProperty(pt1, "x").getClass().getName());
了解注解及java提供的几个基本注解
先通过@SuppressWarnings的应用让大家直观地了解注解:
通过System.runFinalizersOnExit(true);的编译警告引出 @SuppressWarings(“deprecation”)
@Deprecated
直接在刚才的类中增加一个方法,并加上@Deprecated标注,在另一个类中调用这个方法。
@Override
Public boolean equals(Reflect other)方法与HashSet结合讲解
总结:
注解相当于一种标记,加了注解就等于打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。看java.lang包,可看到JDK中提供的最基本的annotation。
自定义注解及其应用
定义一个最简单的注解:public @interface MyAnnotation {}
把它加在某个类上:@MyAnnotation public class AnnotationTest{}
用反射进行测试AnnotationTest的定义上是否有@MyAnnotation
根据发射测试的问题,引出@Retention元注解的讲解,其三种取值:
RetetionPolicy.SOURCE、RetetionPolicy.CLASS、RetetionPolicy.RUNTIME;
分别对应:java源文件-->class文件-->内存中的字节码。
演示和讲解@Target元注解
Target的默认值为任何元素,设置Target等于ElementType.METHOD,原来加在类上的注解就报错了,改为用数组方式设置{ElementType.METHOD,ElementType.TYPE}就可以了。
元注解以及其枚举属性值不用记,只要会看jdk提供那几个基本注解的API帮助文档的定义或其源代码,按图索骥即可查到,或者直接看java.lang.annotation包下面的类。
为注解增加基本属性
什么是注解的属性
一个注解相当于一个胸牌,如果你胸前贴了胸牌,就是传智播客的学生,否则,就不是。如果还想区分出是传智播客哪个班的学生,这时候可以为胸牌在增加一个属性来进行区分。加了属性的标记效果为:@MyAnnotation(color="red")
定义基本类型的属性和应用属性
在注解类中增加String color();
@MyAnnotation(color="red")
用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法
MyAnnotation a = (MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
System.out.println(a.color());
可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象
为属性指定缺省值:
String color() default "yellow";
value属性:
String value() default "zxx";
如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,例如:@MyAnnotation("lhm")。
为注解增加高级属性:
数组类型的属性:
int [] arrayAttr() default {1,2,3};
@MyAnnotation(arrayAttr={2,3,4})
如果数组属性中只有一个元素,这时候属性值部分可以省略大括
枚举类型的属性:
EnumTest.TrafficLamp lamp()
@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)
注解类型的属性:
MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx");
@MyAnnotation(annotationAttr=@MetaAnnotation(“yyy”) )
可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象,同样的道理,可以认为上面这个@MetaAnnotation是MetaAnnotation类的一个实例对象,调用代码如下:
MetaAnnotation ma = myAnnotation.annotationAttr();
System.out.println(ma.value());
注解的详细语法可以通过看java语言规范了解,即看java的language specification。
package cn.itcast.day2; import java.lang.reflect.Method; import javax.jws.soap.InitParam; @ItcastAnnotation(annotationAttr=@MetaAnnotation("flx"),color="red",value="abc",arrayAttr=1) public class AnnotationTest { /** * @param args */ @SuppressWarnings("deprecation") @ItcastAnnotation("xyz") public static void main(String[] args) throws Exception{ // TODO Auto-generated method stub System.runFinalizersOnExit(true); if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)){ ItcastAnnotation annotation = (ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class); System.out.println(annotation.color()); System.out.println(annotation.value()); System.out.println(annotation.arrayAttr().length); System.out.println(annotation.lamp().nextLamp().name()); System.out.println(annotation.annotationAttr().value()); } Method mainMethod = AnnotationTest.class.getMethod("main", String[].class); ItcastAnnotation annotation2 = (ItcastAnnotation)mainMethod.getAnnotation(ItcastAnnotation.class); System.out.println(annotation2.value()); } @Deprecated public static void sayHello(){ System.out.println("hi,传智播客"); } }
package cn.itcast.day2; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import cn.itcast.day1.EnumTest; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.TYPE}) public @interface ItcastAnnotation { String color() default "blue"; String value(); int[] arrayAttr() default {3,4,4}; EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED; MetaAnnotation annotationAttr() default @MetaAnnotation("lhm"); }
</pre><p><strong><span style="font-size:18px;">泛型</span></strong></p><p>JDK1.5<span style="font-family:宋体;">的集合希望在定义集合时,明确表示你要向集合中装哪种类型的数据,无法加入指定类型以外的数据。</span></p><p> 泛型是提供给<span style="font-family:Consolas;">javac</span><span style="font-family:宋体;">编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉类型信息,使程序运行效率不受影响,对于参数化的泛型类型,</span><span style="font-family:Consolas;">getClass()</span><span style="font-family:宋体;">方法的返回值和原始类型完全一致。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就能往某个泛型集合中加入其它类型的数据,例如用反射得到集合,再调用其</span><span style="font-family:Consolas;">add</span><span style="font-family:宋体;">方法即可。</span></p><p>ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:</p><p> 整个称为ArrayList<E>泛型类型</p><p> ArrayList<E>中的E称为类型变量或类型参数</p><p> 整个ArrayList<Integer>称为参数化的类型</p><p> ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数</p><p> ArrayList<Integer>中的<>念typeof</p><p> ArrayList称为原始类型</p><p>参数化类型与原始类型的兼容性:</p><p> 参数化类型可以引用一个原始类型的对象,编译报告警告,例如:</p><p> Collection<String> c = new Vector(); //warning </p><p>原始类型可以引用一个参数化类型的对象,编译报告警告,例如:</p><p> Collection c = new Vector<String>(); //warning</p><p>参数化类型不考虑类型参数的继承关系:</p><p> Collection<String> c = new Vector<Object>(); //error </p><p> Collection<Object> c = new Vector<String>(); //error</p><p>在创建数组实例的时候,不可以使用参数化类型,例如:</p><p> Vector<Integer> vectorList[] = new Vector<Integer>[10]; </p><p></p><p><strong><span style="font-size:14px;">泛型中的?通配符</span></strong></p><p>使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。</p><p>限定通配符的上边界:</p><p>正确:Vector<?Extends Number>x = new Vector<Integer>();</p><p>错误:Vector<?Extends Number>x = new Vector<String>();</p><p>限定通配符的下边界:</p><p>正确:Vector<?Super Integer>x = new Vector<Number>();</p><p>错误:Vector<?Super Integer>x = new Vector<Byte>();</p><p>提示:</p><p>限定符总是包含自己。</p><p> </p><p><strong><span style="font-size:14px;">定义泛型类型</span></strong></p><p>如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型.</p><p>类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:</p><p>GenericDao<String>dao = null;</p><p>New genericDao<String>();</p><p><strong>注意:</strong></p><p>在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。</p><p>当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的参数类型。</p><p> </p><p>通过反射获取泛型的实际参数类型</p><p> Method method = Test.class.getMethod("applyVector", Vector.class); </p><p> Type[] types = method.getGenericParameterTypes(); </p><p> ParameterizedType pType = (ParameterizedType)types[0]; </p><p> System.out.println(pType.getRawType()); </p><p> System.out.println(pType.getActualTypeArguments()[0]);</p><p> GenericTest。Java</p><p><pre class="java" name="code" snippet_file_name="blog_20140708_7_3949293" code_snippet_id="422055">package cn.itcast.day2; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.Vector; import cn.itcast.day1.ReflectPoint; public class GenericTest { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub ArrayList collection1 = new ArrayList(); collection1.add(1); collection1.add(1L); collection1.add("abc"); //int i = (Integer)collection1.get(1); ArrayList<String> collection2 = new ArrayList<String>(); //collection2.add(1); //collection2.add(1L); collection2.add("abc"); String element = collection2.get(0); //new String(new StringBuffer("abc")); Constructor<String> constructor1 = String.class.getConstructor(StringBuffer.class); String str2 = constructor1.newInstance(/*"abc"*/new StringBuffer("abc")); System.out.println(str2.charAt(2)); ArrayList<Integer> collection3 = new ArrayList<Integer>(); System.out.println(collection3.getClass() == collection2.getClass()); //collection3.add("abc"); collection3.getClass().getMethod("add", Object.class).invoke(collection3, "abc"); System.out.println(collection3.get(0)); printCollection(collection3); //Class<Number> x = String.class.asSubclass(Number.class); Class<?> y; Class<String> x ;//Class.forName("java.lang.String"); HashMap<String,Integer> maps = new HashMap<String, Integer>(); maps.put("zxx", 28); maps.put("lhm", 35); maps.put("flx", 33); Set<Map.Entry<String,Integer>> entrySet = maps.entrySet(); for(Map.Entry<String, Integer> entry : entrySet){ System.out.println(entry.getKey() + ":" + entry.getValue()); } add(3,5); Number x1 = add(3.5,3); Object x2 = add(3,"abc"); swap(new String[]{"abc","xyz","itcast"},1,2); //swap(new int[]{1,3,5,4,5},3,4); Object obj = "abc"; String x3 = autoConvert(obj); copy1(new Vector<String>(),new String[10]); copy2(new Date[10],new String[10]); //copy1(new Vector<Date>(),new String[10]); GenericDao<ReflectPoint> dao = new GenericDao<ReflectPoint>(); dao.add(new ReflectPoint(3,3)); //String s = dao.findById(1); //Vector<Date> v1 = new Vector<Date>(); Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class); Type[] types = applyMethod.getGenericParameterTypes(); ParameterizedType pType = (ParameterizedType)types[0]; System.out.println(pType.getRawType()); System.out.println(pType.getActualTypeArguments()[0]); } public static void applyVector(Vector<Date> v1){ } private static <T> void fillArray(T[] a,T obj){ for(int i=0;i<a.length;i++){ a[i] = obj; } } private static <T> T autoConvert(Object obj){ return (T)obj; } private static <T> void swap(T[] a,int i,int j){ T tmp = a[i]; a[i] = a[j]; a[j] = tmp; } private static <T> T add(T x,T y){ return null; } public static void printCollection(Collection<?> collection){ //collection.add(1); System.out.println(collection.size()); for(Object obj : collection){ System.out.println(obj); } } public static <T> void printCollection2(Collection<T> collection){ //collection.add(1); System.out.println(collection.size()); for(Object obj : collection){ System.out.println(obj); } } public static <T> void copy1(Collection<T> dest,T[] src){ } public static <T> void copy2(T[] dest,T[] src){ } }
类加载器
Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:
BootStrap,ExtClassLoader,AppClassLoader
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。
Java虚拟机中的所有类装载器采用具有父子类关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
类加载器树状结构
类加载器的委托机制
当java虚拟机要加载一个类时 到底指派哪个类加载器去加载呢?首先当前线程的类加载器去加载线程中的第一个类;如果类A中引用了类B,java虚拟机将使用加载类A的类加载器来加载类B;还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器,当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了则抛出ClassNotFoundException,不会去找发起者类加载器的儿子。---避免出现相同的字节码
package cn.itcast.day2; import java.util.Date; public class ClassLoaderTest { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub System.out.println( ClassLoaderTest.class.getClassLoader().getClass().getName() ); System.out.println( System.class.getClassLoader() ); System.out.println("xxx"); ClassLoader loader = ClassLoaderTest.class.getClassLoader(); while(loader != null){ System.out.println(loader.getClass().getName()); loader = loader.getParent(); } System.out.println(loader); //System.out.println(new ClassLoaderAttachment().toString()); System.out.println("xxx2"); Class clazz = new MyClassLoader("itcastlib").loadClass("cn.itcast.day2.ClassLoaderAttachment"); Date d1 = (Date)clazz.newInstance(); System.out.println(d1); } }
自定义类加载器
知识讲解:
自定义的类加载器的必须继承ClassLoader
loadClass方法与findClass方法
defineClass方法
编程步骤:
编写一个对文件内容进行简单加密的程序。
编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类,程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。
实验步骤:
对不带包名的class文件进行加密。加密结果存放到另一个目录,例如:java MyClassLoader MyTest.Class F:\itcast
运行加载器的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyTest F:\itcast
用加密后的文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。
删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。
package cn.itcast.day2; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; public class MyClassLoader extends ClassLoader{ /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub String srcPath = args[0]; String destDir = args[1]; FileInputStream fis = new FileInputStream(srcPath); String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1); String destPath = destDir + "\\" + destFileName; FileOutputStream fos = new FileOutputStream(destPath); cypher(fis,fos); fis.close(); fos.close(); } private static void cypher(InputStream ips ,OutputStream ops) throws Exception{ int b = -1; while((b=ips.read())!=-1){ ops.write(b ^ 0xff); } } private String classDir; @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // TODO Auto-generated method stub String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1) + ".class"; try { FileInputStream fis = new FileInputStream(classFileName); ByteArrayOutputStream bos = new ByteArrayOutputStream(); cypher(fis,bos); fis.close(); System.out.println("aaa"); byte[] bytes = bos.toByteArray(); return defineClass(bytes, 0, bytes.length); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public MyClassLoader(){ } public MyClassLoader(String classDir){ this.classDir = classDir; } }
一个类加载器的高级问题分析
编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassLoader。
把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet错误。
把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader。
父级类加载器加载的类无法引用只能被子级类加载器加载的类。原理图如下:
代理的概念与作用
生活中的代理
武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客来找联想总部买电脑,你觉得最终的主体业务目标有什么区别?从代理商那里买真的一点好处都没么?
程序中的代理
要为已存的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事物管理、等等,你准备如何做?
编写一个与目标类具有相同接口的代理类,代理类的每一个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
AOP
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
用具体的程序代码描述交叉业务:
交叉业务的编程问题即为面向方面的编程(Aspect oriented program,简称APO),AOP的目标就是要使用交叉业务模块化,可采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
动态代理技术
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以作用该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在调用目标方法异常的catch块中
分析JVM动态生成的类
创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
编码列出动态类中的所有构造方法和参数签名
编码列出动态类中的所有构造方法和参数签名
创建动态类的实例对象
用反射获得构造函数
编写一个最简单的invocationHandler类
调用构造方法创建动态类的实例对象,并将编写的invocationHandler类的实例对象传进去
打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。
将创建动态类的实例对象代理改成匿名内部类的形式编写,锻炼大家习惯匿名内部类
总结思考:让jvm创建动态类,需要给它提供哪些信息?
三个方面:
生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
产生的类字节码必须有一个关联的类加载器对象;
生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个invocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的invocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
用Proxy.newinstance方法直接一步就创建出代理对象。
Proxy.newProxyInstance(loader, interfaces, h);
package cn.itcast.day3; import java.lang.reflect.Constructor; 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 ProxyTest { /** * @param args */ public static void main(String[] args) throws Exception{ // TODO Auto-generated method stub Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); System.out.println(clazzProxy1.getName()); System.out.println("----------begin constructors list----------"); /*$Proxy0() $Proxy0(InvocationHandler,int)*/ Constructor[] constructors = clazzProxy1.getConstructors(); for(Constructor constructor : constructors){ String name = constructor.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzParams = constructor.getParameterTypes(); for(Class clazzParam : clazzParams){ sBuilder.append(clazzParam.getName()).append(','); } if(clazzParams!=null && clazzParams.length != 0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } System.out.println("----------begin methods list----------"); /*$Proxy0() $Proxy0(InvocationHandler,int)*/ Method[] methods = clazzProxy1.getMethods(); for(Method method : methods){ String name = method.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzParams = method.getParameterTypes(); for(Class clazzParam : clazzParams){ sBuilder.append(clazzParam.getName()).append(','); } if(clazzParams!=null && clazzParams.length != 0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } System.out.println("----------begin create instance object----------"); //Object obj = clazzProxy1.newInstance(); Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); class MyInvocationHander1 implements InvocationHandler{ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } } Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHander1()); System.out.println(proxy1); proxy1.clear(); //proxy1.size(); //System.out.println("111111111111111"); Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }); final ArrayList target = new ArrayList(); Collection proxy3 = (Collection)getProxy(target,new MyAdvice()); proxy3.add("zxx"); proxy3.add("lhm"); proxy3.add("bxd"); System.out.println(proxy3.size()); System.out.println(proxy3.getClass().getName()); } private static Object getProxy(final Object target,final Advice advice) { Proxy.newProxyInstance(loader, interfaces, h) Object proxy3 = Proxy.newProxyInstance( target.getClass().getClassLoader(), /*new Class[]{Collection.class},*/ target.getClass().getInterfaces(), new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /*long beginTime = System.currentTimeMillis(); Object retVal = method.invoke(target, args); long endTime = System.currentTimeMillis(); System.out.println(method.getName() + " running time of " + (endTime - beginTime)); return retVal;*/ advice.beforeMethod(method); Object retVal = method.invoke(target, args); advice.afterMethod(method); return retVal; } } ); return proxy3; } }
实现AOP功能的封装与配置
工厂类BeanFactory负责创建目标类或者代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回对象。
BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
#xxx =java.util.ArrayList
xxx=cn,itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.ticast.MyAdvice
ProxyFactoryBean充当封装生成动态代理的工厂,需要为工厂提供哪些配置参数信息?
目标
通知
编写客户端应用
编写实现Advice接口的类在配置文件中进行配置
调用BeanFactory获取对象
---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------详细请查看:www.itheima.com