反射
一、基础知识
1.Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。
2.Java程序中各个Java类,它们是属于同一类事物,可以用一个类来描述这类事物,这个类的名字就是Class。Class类描述了类的名字,类的访问属性,类所属于的包名,字段名称的列表、方法名称的列表,等等。反射就是把Java类中的各个成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造汽车,包等信心也用一个个的java类来表示,就像汽车是一个类,汽车中俄发动机,变速箱等等也是一个个的类。表示JAVA类的Class类显然要提供一些列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,他们是Field、Method、Constructor、Package等等。
3.一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。每个java类都是Class的一个实例对象,它们的内容不同,但是,它们的特征相同,比如都有方法,有字段等。
Class的实例对象代表内存中的字节码 :
二、字节码
总结:字节码:.class加载到内存中才可以创建对象 eg: Class s=Date.class//字节码
得到各个字节码对应实例的对象的三种方法:
1、类名.Class 例如:System.class;
2、对象名.Class 例如 new Date().getClass();
3、静态方法Class.forName("类名"); 例如,Class.forName("java.util.Date");
Class.forName()得到字节码的情况:
1、字节码已经加载到java虚拟机中,去得到字节码
2、java虚拟机中还没有生成字节码 用类加载器进行加载,加载的字节码缓冲到虚拟机中
九个预定义Class的实例对象(byte.class char.classshort.class int.classlong.class float.class double.class boolean.class和void.class)八个基本数据类型和void类型
在源程序中出现的类型,都有各自的Class实例对象,如int[],void…
isPrimitive();判断是否是基本类型的字节码
int.class和Integer.class不是同一份字节码,Integer.TYPE,TYPE代表包装类对应的基本类的字节码 int.class==Integer.TYPE
数组类型的Class实例对象 用到Class的isArray()
总结:只要在源程序中国出现的类型,都有各自的Class实例对象,例如,int[],void…
构造方法的反射:
思路:class------>constructor-------->new object
Constructor类代表某个类中的一个构造方法
1.得到某个类所有的构造方法
Constructor[]constructors=Class.forName("java.lang.String").getConstructors();
2.得到某一个构造方法
Constructor constructor=Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
3.创建实例对象:
通常方式:String str=new String("abc");
反射方式:Stringstr=(String)constructor.newInstance(newStringBuffer("abc"));
Constructorconstructor1=String.class.getConstructor(StringBuffer.class);
String str2=(String)constructor1.newInstance(newStringBuffer("abc"));
第一个StringBuffer代表选择哪个构造方法
第二个StringBuffer代表用这个StringBuffer时还要传递一个StringBuffer对象
Class.newInstance()方法:
例子:String obj=(String)Class.forName(“java.lang.String”).newInstance();
1. 该方法内部先得到猫人的构造方法,然后调用该方法创建实例对象。
2. 该方法内部代码,用到了缓存机制来保存默认构造方法的实例对象。
Field成员变量的反射:
Field类代表某个类的中一个成员变量
publicclass ReflectpoiSecond {
public intx;
privateinty;
public ReflectpoiSecond(int x, int y) {
super();
this.x = x;
this.y = y;
}}
--------------------------------------
publicclass ReflectTest {
public static void main(String[]args) throws Exception{
ReflectpoiSecondrs=new ReflectpoiSecond(4,5);
FieldfieldX=rs.getClass().getField("x");//getField提供可见的 FieldfieldY=rs.getClass().getDeclaredField("y");//getDeclaredField得到声明过的属性
fieldY.setAccessible(true);//设置访问权限 ---暴力反射
System.out.println(fieldX.get(rs));
System.out.println(fieldY.get(rs));
}}
成员方法的反射:Method类代表某个类中的一个成员方法:
一.得到类中的某一个方法:
MethodmethodCharAt=Class.forName("java.lang.String").getMethod("charAt",int.class);
System.out.println(methodCharAt.invoke(str,1));//调用str对象中charAt(1)
二.调用方法:
1. 通常方式:System.out.println(str.charAt(1));
2.反射方式:System.out.println(charAt.invoke(str,1));//str是一个对象,这里str可以是null,说明invoke()方法是一个静态方法
Jdk1.4和jdk1.5的invoke方法的区别:
JDK1.5:public Objectinvoke(Object obj,Obj…args);
JDK1.4:public Objectinvoke(Object obj,Object[] args);
即按JDK1.4的语法,需要讲一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,charAt方法的代码也可以用JDK1.4改写为charAt.invoke(“str”,newObject[]{1})形式。
实例应用:用反射方式执行某个类中的main方法
1. 目标:
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的Main方法
作用:
2.问题
启动java程序的main方法的参数是一个字符串数组, 即public static void main(String[]args),通过反射方式来调用这个main方法时,按jdk1.5的语法,整个数组是一个参数,而jdk1.4的语法,数组的每个元素对应一个参数,当做把一个字符串数组作为参数传递给invoke方法如何处理(注意兼容)。所以,在给main方法传递参数时,不能使用代码mainMthod.invoke(null,newString[]{“xxxx”}),javac只把它当做JDK1.4的语法进行理解,。而不能把它当做JDK1.5的语法解释,因此,会出现参数类型不对的问题。
3.解决方法:
方法一:mainMethod.invoke(null,newObject[]{newString[]{xxxx}});
方法二:mainMethod.invoke)((Object)newString[]{"xxxx"});编译器会做特殊处理,编译时不把参数当做数组看待,也就不会数组达三成若干参数
数组的的反射
1. 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
2. 代表数组的Class实例对象的getSuperClass()方法,返回的父类为Object类对应的Class
3. 基本类型的一维数组可以被当做Object类型使用,不能作为Object[]类型使用,不能当做Object[]类型使用,非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类使用
4. 注意区别 Array.asList()方法处理int[]和String[]时的差异
5. Array工具类用于完成对数组的反射操作
注解
一 注解Annotation 注解是jdk1.5的新特性
入门:
1 在System有一个过时的方法:
System.runFinalizersOnExit(true);
在编译的时候是会警告的,所以我们在之前加上@SuppressWarnings("deprecation")就可以压制警告
2 另外我们有时一个方法已经被很多人使用,但是我们却不想让很多新人再用到它,但是很多老的人还是再用他,那怎么办呢,所以我要在新人要调用这个方法的时候就告诉他一声,你要用到的方法已经过时了,
那就这么干在这个方法前加上这个@Deprecated,人家再调用这个方法的时候就会提示此方法已经过时了,但是还是能用的。
@Deprecated
public static void sayHello(){
System.out.println("哈喽");
}
3 另外有时候我们写程序,一个类需要覆盖equls方法,但是我们有时候却把它重载了却没有覆盖,这时候编译器又不报错,那怎么办呢,还找不到哪里错误了,这个时候我们就可以在写的时候在equls方法上添加@override这样就可以了,如果没覆盖,编译器就会报错。
所以总结
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。
看java.lang包,可看到JDK中提供的最基本的annotation。 这里有刚刚看到的这三个注解
二 注解的应用和结构
注解就相当于一个你的源程序中要调用的一个类,要在源程序中应用某个注解,得先准备好了这个注解类。就像你要调用某个类,得先有开发好这个类。
一个注解的结构三为三个类:
1 注解类
格式:
@interface A{
}
2 应用了”注解类“的类
格式:
@A
Class B{
}
3 对应用了”注解类“的类进行反射。
格式:
Class C{
B.class.isAnnotionPresent(A.class);判断A这个注解是否存在
A a = B.class.getAnnotion(A.class);得到A这个注解
}
小知识点:
元注解:就是注解上的注解
元数据:就是数据上的数据
等等
例如:
定义注解类:
//RetentionPolicy这个类是一个枚举,里面就只有RUNTIME,SOURCE,CLASS这三个对象。
@Retention(RetentionPolicy.RUNTIME)
//根据发射测试的问题,引出@Retention元注解的讲解,其三种取值://RetetionPolicy.SOURCERetetionPolicy.CLASS、//RetetionPolicy.RUNTIME;分别对应:java源文件-->class文件-->内存中的字节码。@Override、@SuppressWarnings和@Deprecated这三个注解的属性值分别是SOURCE,SOURCE,RUNTIME。
@Target({ElementType.METHOD,ElementType.TYPE})
这个元注解是用来说明我们的注解是可以写在什么东西上面的,ElementType这个类也是枚举,有8个对象,ANNOTATION_TYPE,CONSTRUCTOR,FIELD,LOCAL_VATIABLE,METHOD,PACKAGE,PARAMETER,TYPE.
public @interface ItcastAnnotation {
String color() default "blue";指定缺省属性。
String value();
int[] arrayAttr() default {3,4,4};指定数组的缺省值,如果数组属性中只有一个元素,这时候属性值部分可以省略大括
这个的话就是枚举的举例,假设TrafficLamp是个枚举,(枚举定义在下面),所以我们就是这么调用的
EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED;
如果这个MetaAnnotation是个注解,(下面定义了这个注解类)这样定义
MetaAnnotation annotationAttr() default @MetaAnnotation("lhm");
另外还可以是class类型
}
什么是注解的属性
一个注解相当于一个胸牌,就像如果你胸前贴了胸牌,就是代表某一个公司的 否则,就不是。如果还想区分出是公司里的哪个班级的,这时候可以为胸牌在增加一个属性来进行区分。加了属性的标记效果为:@ItcastAnnotation(color="red")
@ItcastAnnotation(annotationAttr=@MetaAnnotation("flx"),color="red",value="abc",arrayAttr=1)注解就这么负值
public static void main(String[] args) throws Exception{
if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)){
ItcastAnnotation annotation =(ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
System.out.println(annotation.color());//这里打印就会打印出red
System.out.println(annotation.value());
System.out.println(annotation.arrayAttr().length);打印数组的长度
System.out.println(annotation.lamp().nextLamp().name());显示交通灯为绿灯
//这里就是打印出这个注解里面的属性值
System.out.println(annotation.annotationAttr().value());这里就打印flx
}
枚举:代码
public class EnumTest {
public enum TrafficLamp{
RED(30){
public TrafficLamp nextLamp(){
return GREEN;
}
},
GREEN(45){
public TrafficLamp nextLamp(){
return YELLOW;
}
},
YELLOW(5){
public TrafficLamp nextLamp(){
return RED;
}
};
public abstract TrafficLamp nextLamp();
private int time;
private TrafficLamp(int time){this.time = time;}
}
}
注解类代码:
public @interface MetaAnnotation {
String value();
}
泛型
1 简单应用:
看下如下代码:
ArrayList collection1 = new ArrayList();
collection1.add(1);
collection1.add(1L);
collection1.add("abc");
这个就是可以添加各种对象,是不确定的
而像下面这段代码的话加了<String>所以就只能添加字符串了
ArrayList<String> collection2 = newArrayList<String>();
//collection2.add(1);
//collection2.add(1L);
collection2.add("abc");
String element = collection2.get(0);
另外看一段代码,再没有泛型之前我们是这么写的:
Constructor constructor1 =String.class.getConstructor(StringBuffer.class);
String str2 =(String)constructor1.newInstance(/*"abc"*/newStringBuffer("abc"));
System.out.println(str2.charAt(2));
没有用泛型之前,我们这的得到的构造方法,反射回来再new一个对象的时候需要类型转换,然而我们用泛型的话就不需要这么写了
Constructor<String> constructor1 =String.class.getConstructor(StringBuffer.class);
String str2 =constructor1.newInstance(/*"abc"*/new StringBuffer("abc"));
System.out.println(str2.charAt(2));
你看我这里在Constructor后面加上了<String>所以再去new的时候就不需要转换类型了,下面紧跟着的语句就没有用小括号强制转换。
注意:
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
所以我们可以跳过这个编译器过程,我们直接用反射得到某个方法再去invoke去调用这个方法
关于另外一点专业术语:
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
整个称为ArrayList<E>泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念着typeof
ArrayList称为原始类型
下面两种写法只会有警告:
参数化类型可以引用一个原始类型的对象,编译报告警告,例如,Collection<String> c = new Vector();//可不可以,不就是编译器一句话的事吗?
原始类型可以引用一个参数化类型的对象,编译报告警告,例如,Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去
总结:编译器是一行一行扫描的,我们去看待这句语句编译会不会出错,是去要想单单这一句会不会报错,而不是去想整个程序运行下来会不会报错。
2 泛型的通配符?
首先我要弄个方法能打印任何集合类型的数据
所以我们写上<Object>
public static void printCollection(Collection<Object> collection){
}
Constructor<String> constructor1 =String.class.getConstructor(StringBuffer.class);
但是假如我们这么打印呢printCollection(collection1);这个时候就会报错了,那怎么办呢
这个时候我泛型中就有另外一个符号?这么写就没问题了
public static void printCollection(Collection<?>collection){
}
知识点:使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。比如调用add(E e)方法就不行因为 而调用size()方法就可以因为所有集合都是有这个方法
另外通配符有上下限定:
1)限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
2) 限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
3)限定通配符总是包括自己。?只能用作引用,不能用它去给其他变量赋值
Vector<? extends Number> y = newVector<Integer>();
Vector<Number> x = y;
上面的代码错误,原理与Vector<Object > x11 = newVector<String>();相似,
只能通过强制类型转换方式来赋值。
4)编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
Vector<Integer> vectorList[] = newVector<Integer>[10];
5) 看一个应用,我们搞个方法,让这个方法交换任何类型数组里的任意两个元素的值:
private static <T> void swap(T[] a,int i,int j){
T tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
这么定义就是可以的,我这么调用它swap(newString[]{"abc","xyz","itcast"},1,2);这是对的
但是这么调用就错了swap(new int[]{1,3,5,4,5},3,4);因为只有引用类型才能用作泛型方法的实际参数。int是基本类型就不可以
6)用下面的代码说明对异常如何采用泛型:
private static <T extends Exception> sayHello() throws T
{
try{
}catch(Exception e){//这里必须写成Exception而不能写成抓其他异常
throw (T)e;
}
}
7)在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static <K,V> V getValue(K key) { return map.get(key);}
8)除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable &cloneable> void method(){}
9)普通方法、构造方法和静态方法中都可以使用泛型。
3 根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
1)当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4) ? static<E> void swap(E[] a, int i, int j)
2)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5) ? static <T> T add(T a, T b)
3)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f) ? static <T> voidfill(T[] a, T v)
4)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x =(3,3.5f) ? static <T> T add(T a, Tb)
5)参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5],new String[5]) ? static <T> void copy(T[]a,T[] b);
copy(new Vector<String>(), new Integer[5]) ? static <T>void copy(Collection<T> a , T[] b);
4 在类上定义泛型
应用实例:
public class GenericDao<E> {
public void add(E x){
//增加方法
}
public E findById(int id){
return null;//查询id位置的值
}
public void delete(E obj){
//删除
}
public void delete(int id){
}
}
5 反射应用在泛型上:
1)类Method上有个方法:Type[] getGenericParameterTypes() 按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的。
2)Type是个接口,子接口有ParameterTypes,里面有方法:1, Type[]getActualTypeArguments() 返回表示此类型实际类型参数的 Type 对象的数组。 2,Type getRawType() 返回 Type 对象,表示声明此类型的类或接口。3 Type getOwnerType() 返回 Type 对象,表示此类型是其成员之一的类型。
重点知识点:
public static void applyVector(Vector<Date> v1){
}用反射获得这个方法中传递进来的类型,并得到实际类型,
Method applyMethod = 类名.class.getMethod("applyVector", Vector.class);
//用这个反射获得了这个方法,返回这个类型
Type[] types = applyMethod.getGenericParameterTypes();
//强制转换一下类型
ParameterizedType pType = (ParameterizedType)types[0];
//然后用这个类里的方法获取原始类型,打印出来的结果为Vector
System.out.println(pType.getRawType());
//然后再获得实际类型
System.out.println(pType.getActualTypeArguments()[0]);
}
首先了解下动态代理技术出现的背景
在Java中若是需要为一个类扩充功能,大概有这几种方式
1、继承
优点:方便、可以从父类中继承一些成员变量还有方法,若不满意某个方法的实现可以重写这个方法
缺点:由于Java语言只能从一个类,因此不够灵活,并且会使类与类之间的关系耦合度强
2、装饰模式
Java的io中就大量的使用了这个设计模式,把底层的InputStream和OutputStream通过一层层的装饰包装,不断的扩充功能(例如:可以读取字符流,加上缓存等等)
优点:可以动态的给一个对象添加一些职责,可以比继承使用较少的类
缺点:使用装饰模式会产生比使用继承关系更多的对象,出现错误的话不好查,回想一样,刚开始学习java的 io时,是不是感觉有点乱,new出来这个Stream放进那个对象的,一大堆对象,看着就晕,我是这样感觉 的,不知道你们刚学时神马情况,呵呵
3、组合方式:
个人觉得组合方式和装饰模式在语言层面上都差不多(设计模式也是如此,有好几个感觉差不多,主要是解决的问题不同思想不同而已,),内部都有一个被扩展类的引用,在装饰模式中这个引用被传递过来,组合是直接在类中new出来,
组合方式有诸多好处,但是在实际的项目中,需要在不改变原来类的基础上扩展它的功能,需求是多种多样的,可能今天要在dao层上加上统计方法的执行时间,以便于数据库的优化,明天又想加入个日志功能等等,时间一长类的数量就会爆炸性的增长。
代理
正是有这些需求,动态代理技术出现了,它可以动态的生成一段内部有一个被扩展类引用的代码,生成的类类似于用组合这种方式,并把它动态的编译成class文件,然后装载进jvm中执行。著名的框架Spring的核心技术之一aop就是使用的就是动态代理技术。
动态代理初级:
Java中为动态代理提供了支持,用到了类Proxy用于产生代理对理,还有一个接口InvocationHandler,用于在被代理的类的方法前后加入处理逻辑,这个接口内部有一个invoke方法,invoke接受三个参数,
proxy - 在其上调用方法的代理实例
method - 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
-------------------------------------------------------------
生成代理类的方法Proxy.newProxyInstance,它接受三个参数,第一个参数为被代理对象的类加载器,第二个参数为需要代理的接口,这些接口的所有方法都会被代理,第三个参数为建立的InvocationHandler的实例,现在就举个例子说明吧
由于Jdk提供的代理只能大力接口,所以首先我们建议一个被代理的接口还有他的实现,就用项目中的dao层演示
现在有个用户访问数据库的Userdao,里面有个保存用户的方法,接受一个名字
public interface UserDao {
void save(String username);
}
UserDao的实现类UserDaoImpl
public class UserDaoImpl implements UserDao {
@Override
public void save(String username) {
System.out.println(username + "存进了数据库");
}
}
现在想对UserDaoImpl记录日志,写一个处理类LogHandler实现InvocationHandler接口
public class LogHandler implements InvocationHandler {
//被代理的对象
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//对被代理对象target的对应方法进行调用,把代理类接受的参数传递进去
Object result = method.invoke(target, args);
System.out.println(new SimpleDateFormat("yyyy-MM-ddHH:mm:ss").format(new Date()) + "用户" + args[0] + "保存进数据库");
return result;
}
//接收被代理的对象
public LogHandler(Object target) {
super();
this.target = target;
}
}
用于测试的有main的类 ProxyTest
public class ProxyTest {
public static void main(String[] args) {
//被代理的UserDaoImpl对象
UserDao ud = new UserDaoImpl();
//生成代理对象,转成UserDao引用使用,注意这里不能转为UserDaoImpl的引用,只能使用对应接口,
//这是由于接口可以规范方法
UserDao proxy = (UserDao)Proxy.newProxyInstance(
ud.getClass().getClassLoader()//加载生成代理类的加载器
, ud.getClass().getInterfaces()//需要代理的接口,这里为UserDao
, new LogHandler(ud));//日志处理类
//对代理对象调用save方法
proxy.save("黑马程序员");
}
}
控制台打印:
黑马程序员存进了数据库
2012-09-06 14:12:23用户黑马程序员保存进数据库
怎么样,这样就为UserDaoImpl动态的增加了功能,呵呵
动态代理中级:
为例使生成代理的过程简单,对jdk的动态代理功能可以做下封装,下面这个类ProxyFactory,可以代理任何的接口,并能在方法的前后都加上逻辑,在出现异常时还能进行处理,此类模仿了Spring对动态代理的支持
处理逻辑的接口Advice
public interface Advice {
//在被代理的方法执行前执行
void before(Object proxy, Method method, Object[] args);
//在被代理的方法执行后执行
void after(Object proxy, Method method, Object[] args);
//出现异常时执行
void exception(Object proxy, Method method, Object[] args);
//在被代理的方法执行前后都执行
void beforeAndAfter(Object proxy, Method method, Object[] args);
}
代理生成工厂ProxyFactory
public class ProxyFactory {
//被代理的对象
private Object target;
//在被代理的对象方法上加入的建议
private Advice advice;
//产生代理的方法
public Object createProxy() {
//和上面那个例子一样,使用jdk的动态代理api
Object proxyObj = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
//最主要就是这个地方了,请仔细看里面的逻辑
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
try {
//在被代理方法前执行
advice.beforeAndAfter(proxy, method, args);
advice.before(proxy, method, args);
//调用目标方法
result = method.invoke(target, args);
//在被代理方法执行后执行
advice.after(proxy, method, args);
//呵呵,看到没,beforeAndAfter在前后都执行一次,达到了目的
advice.beforeAndAfter(proxy, method, args);
} catch (Exception e) {
//当出现异常时,就会调用建议中的对异常进行处理的exception方法
advice.exception(proxy, method, args);
}
return result;
}
});
return proxyObj;
}
public ProxyFactory (Object target,Advice advice) {
this.target = target;
this.advice = advice;
}
}
测试类AdviceImpl,ProxyFactoryTest
public class AdviceImpl implements Advice {
@Override
public void before(Object proxy, Method method, Object[] args) {
System.out.println("before");
}
@Override
public void after(Object proxy, Method method, Object[] args) {
System.out.println("after");
//为了测试处理异常的方法是否执行,自己搞出来一个异常
Integer.parseInt("黑马程序员");
}
@Override
public void exception(Object proxy, Method method, Object[] args) {
System.out.println("exception");
}
@Override
public void beforeAndAfter(Object proxy, Method method, Object[] args) {
System.out.println("beforeAndAfter");
}
}
public class ProxyFactoryTest {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(new UserDaoImpl(),new AdviceImpl());
UserDao ud = (UserDao)proxyFactory.createProxy();
ud.save("黑马");
}
}
运行结果:
beforeAndAfter
before
黑马存进了数据库
after
exception
怎么样,经过了这层封装,客户端中获得一个代理对象,只需要两行代码而已,并且功能强大,如果愿意的话,还可以继续把代理对象和一个新的Advice扔进去,在获得一个新的代理对象,这样就可以不断的扩展功能了,spring的面向切面编程和声明实的异常处理都是这个原理。