JA V A的反射机制与动态代理
李海峰(QQ:61673110)-Andrew830314@https://www.sodocs.net/doc/1717863722.html,
运行时类型信息(RunTime Type Information,RTTI)使得你在程序运行时发现和使用类型信息。RTTI主要用来运行时获取向上转型之后的对象到底是什么具体的类型。
1.Class对象:
JAVA使用Class对象来执行RTTI。每个类都有一个Class对象,它用来创建这个类的所有对象,反过来说,每个类的所有对象都会关联同一个Class对象(对于数组来说,维数、类型一致的数组的Class对象才是相同的),每个对象的创建都依赖于Class对象的是否创建,Class对象的创建发生在类加载(https://www.sodocs.net/doc/1717863722.html,ng.ClassLoader)的时候。
https://www.sodocs.net/doc/1717863722.html,ng.Class类实现了Serializable、GenericDeclaration、Type、AnnotatedElement四个接口,分别实现了可序列化、泛型定义、类型、元数据(注解)的功能。
你可以把Class对象理解为一个类在内存中的接口代理(它代理了这个类的类型信息、方法签名、属性),JVM加载一个类的时候首先创建Class对象,然后创建这个类的每个实例的时候都使用这个Class对象。
Class只有一个私有的无参构造方法,也就是说Class的对象创建只有JVM可以完成。
如何验证同一个类的多个对象的Class对象是一个呢?
Cf1 cf1 = new Cf1();
Class clazz = Cf1.class;
System.out.println(cf1.getClass() == clazz);
我们知道==用来比较引用是否相等(也就是同一个引用),上面的输出语句结果是true。那么Class对象是否相等是JAVA对象中唯一可以使用==判断的。
如何获取Class对象:
1.所有的引用数据类型(类-类型)的类名、基本数据类型都可以通过.class方式获取其Class 对象(对于基本数据类型的封装类还可以通过.TYPE的方式获取其Class对象,但要注意.TYPE实际上获取的封装类对应的基本类型的Class对象的引用,那么你可以判断出int.class==Integer.TYPE返回true,int.class==Integer.class返回false!),通过这种方式不会初始化静态域,使用.class、.TYPE的方式获取Class对象叫做类的字面常量;
2.Class的forName(String name)传入一个类的完整类路径也可以获得Class对象,但由于使用的是字符串,必须强制转换才可以获取泛型的Class
2.对于引用数据类的引用(必须初始化),可以通过Object类继承的getClass()方法获取这个引用的Class对象,由于引用已经被初始化,所以这种方式也不会初始化静态域,因为静态域已经被初始化过。另外,前面两种方式如果说是创建Class对象,那么这种方式应该是取得Class对象,因为类的实例已经被创建,那么Class对象也一定早就被创建。
Class的常用方法:
l forName(String name):这是一个静态方法,传入的参数是一个类的完整类路径的字符串,返回这个类的Class对象,前面说过Class对象的创建发生在类的加载时,所以这个方法会导致静态成员被调用;
l forName(String name,boolean initialize,ClassLoader loader):这是上面的方
法的重载方法,initialize指定在创建Class对象时是否初始化这个类(即是否执行静态成员,由于在一次JVM的执行中,静态成员的初始化只类加载的时候执行一次,所以如果之前这个类已经被加载,那么即使initialize为true也不会再次执行静态成员的加载),loader指定使用哪个类加载器的实现类
(Thread.currentThread().getContextClassLoader()可以获取当前线程使用的类加载器)。forName(***)方法不可以获取基本数据类型的Class对象。
如果要测试initialize是否起作用,请不要在main()方法测试自身类,因为main()是静态方法,执行这个方法会导致静态域被初始化,所以你的initialize无论是true还是false,效果都是一样的。
l asSubClass(Class superClass):这个方法是将父类的class对象作为参数传入,并将其强制转换成当前的Class对象(子类的Class对象)。
例:
Class clazz = List.class;
Class extends List> subClazz = ArrayList.class.asSubclass(clazz);
System.out.println(subClazz.getCanonicalName());
注意红色的部分不能写成Class
l cast(Object o):这个方法是将传入的对象强制转换成Class对象所代表的类型的对象;
l getClassLoader():返回创建当前Class对象的类加载器,默认的,应用程序获得的是https://www.sodocs.net/doc/1717863722.html,uncher$AppClassLoader,Applet程序获得的是sun.applet.AppletClassLoader;
l getCanonicalName():返回JA V A语言中所定义的底层类的规范化名称,如果没有规范化名称就返回null,对于普通的引用数据类型,这个方法和getName()方法都返回完整的类路径,对于数组(以字符串数组为例),getName()返回[https://www.sodocs.net/doc/1717863722.html,ng.String;,这个方法返回https://www.sodocs.net/doc/1717863722.html,ng.String[];
l getSimpleName():返回底层规范化名称的简写,也就是去掉包名;
l getConstructors():返回Class对象的公有构造器的https://www.sodocs.net/doc/1717863722.html,ng.reflect.Contructor对象数组,如果找不到匹配的构造方法,返回NoSuchMethodExcetion异常;
l getConstructor(Class> …parameterTypes):按照指定的可变参数列表,返回符合参数条件的公有构造方法的Constructor,这里不可能是数组,因为构造方法不可能重复,如果找不到匹配的构造方法,返回NoSuchMethodExcetion异常;
l getDeclaredConstructors():返回Class对象的所有构造器的Constructor对象,如果找不到匹配的构造方法,返回NoSuchMethodExcetion异常;
l getDeclaredConstructor(Class> …parameterTypes):按照指定的可变参数列表,返回符合参数条件的所有构造方法的Constructor,这里不可能是数组,因为构造方法不可能重复,如果找不到匹配的构造方法,返回NoSuchMethodExcetion异常;
l getFields():获取Class对象的所有公有成员属性的https://www.sodocs.net/doc/1717863722.html,ng.reflect.Field数组;
l getField(String name):按照字段名称获取公有字段的Field对象,注意name是区分大小写的;
l getDeclaredFields():获取Class对象的所有成员属性的Field数组;
l getDeclaredField(String name):按照字段名称获取所有字段的Field对象,注意name是区分大小写的;
l getMethods():获取Class对象的公有方法(以及从父类继承的方法,但不包含构造
方法)的https://www.sodocs.net/doc/1717863722.html,ng.reflect.Method数组;
l getMethod(String name,Class> …parameterTypes):按照name指定的方法名称,parameterTypes指定的可变数组获取公有方法(以及从父类继承的方法,但不包含构造方法)的Method对象,注意name是区分大小写的;
l getDeclaredMethods():获取Class对象的所有方法(不包含父类继承的方法,构造方法)的Method数组;
l getDeclaredMethod(String name,Class> …parameterTypes):按照name 指定的方法名称,parameterTypes指定的可变数组获取所有方法(不包含父类继承的方法,构造方法)的Method对象,注意name是区分大小写的;
l getGenericInterface():以Type数组的形式返回Class对象的类直接实现的包含泛型参数的接口,返回顺序是implements后的接口顺序;
l getInterface():以Class数组的形式返回Class对象的类直接实现的接口,返回顺序是implements后的接口顺序;
l getModifiers():以https://www.sodocs.net/doc/1717863722.html,ng.reflect.Modifier的常量值的形式返回Class对象的类的修饰的整型值的和;
l getGenericSuperclass():以Type数组的形式返回Class对象的类直接实现的包含泛型参数的超类, 返回顺序是extends后的接口顺序;
l getSuperclass():以Class数组的形式返回Class对象的类直接实现的超类, 返回顺序是extends后的接口顺序;
l getName():以String形式返回Class对象所表示的实体的完整类名,基本数据类型返回自身,数组类型(以String数组为例)返回[https://www.sodocs.net/doc/1717863722.html,ng.String;,这个方法没有getCanonicalName()返回的完整,但不是所有的类型都有底层的规范化名称;
l getPackage():以https://www.sodocs.net/doc/1717863722.html,ng.reflect.Package形式返回Class对象的类所在的包,基本数据类型、数组抛出异常;
l getResource(String url):这个方法使用当前Class对象的ClassLoader实体加载url 指定的资源,返回https://www.sodocs.net/doc/1717863722.html,.URL对象。如果url以\ 开头,那么就从当前的classPath 开始定位资源,否则就从当前Class对象的类所在的包开始定位资源,Hibernate要求*.hbm.xml必须和PO类在一个包下,就是利用了没有\ 开头的url传入这个方法实现的;
l getResourceAsStream(String url):这个方法和上面的方法一样,只不过返回的是java.io.InputStream的对象;
l isAssignableFrom(Class> class):判断当前Class对象的类是否是参数class的类的超类或者接口;
l isInstance(Object o):判断参数o是否是Class对象的类的实例,相当于o instanceOf Class。
isInstance()、instanceOf关键字检查的某个实例是否是这个类型,具有继承关系的检查能力,即使是其子类的对象,也可以返回true。但是Class对象是严格的类型,所以super.class==sub.class是一定返回false的。
l newInstance():这个方法将会使得Class对象调用类中的公有无参构造方法实例化对象,,返回一个Object对象,大多数框架都会使用到这个方法,例如EJB容器、Spring 容器都会要求受管组件提供一个默认构造方法。
注意:以上的方法并不是对每种类型都可用,对于返回数组的方法,如果不可用则返回长度为0的数组,对于返回其他类型的方法,如果不可用则返回null。例如:getConstructors()
方法对于基本数据类型、数组就不可用,那么返回一个长度为0的Constructor数组。
另外,上面的方法获取类中的元素大都是只能获取public的元素,可以获取全部元素的大都是含有Declared字符。
某些方法不具有递归特性,例如只能查找本类的元素,不能查找内部类或者其父类中的元素,如果你非要这么做,需要自行递归操作,例如:调用getSuperClass()直到其返回null为止。
______________________________________________
2.JAVA的反射机制:
前面说过RTTI获取某个对象的确切类型,要求在这个对象在编译时必须已知,也就是必须已经在你的代码中存在完整的声明(T t),但是如果是运行时才会知晓的对象(例如网络中传递过来的字节),RTTI就没办法工作了。
https://www.sodocs.net/doc/1717863722.html,ng.Class与https://www.sodocs.net/doc/1717863722.html,ng.reflect.*包中的类提供了一种有别于RTTI(编译器在编译器时打开和检查*.class文件)的反射机制(运行时打开和检查*.class文件)。
JA V A的反射功能相当强大,例如前面说过的类的复用方式---组合,如果是动态组合的新类就叫聚合,反射就可以完成聚合功能。
(1.)Field:这个类用于获取类中的字段信息以及访问这些字段的能力。
l getObject(Object o):返回参数o的对象上的Field表示的字段的值;
l setObject(Object o,Object value):将参数o的对象上的Field表示的字段的值设置为value;
l getBoolean(Object o):获取参数o的对象的Field表示的布尔类型的字段的值;
l setBoolean(Object o,boolean value):将参数o的对象上的Field表示的布尔类型的字段的值设置为value;
……对于基本数据类型,都拥有自己的getXXX()、setXXX()方法。
l getGenericType():返回Field表示的字段声明的泛型类型的Type实例;
l getModifiers():以整型数值和的形式返回Field表示的字段的修饰符;
l getType():返回Field表示的字段的类型的Class对象;
l isEnumConstants():如果Field表示的字段是枚举类型,返回true;
l toGenericString():返回描述此字段的字符串(包含泛型信息),能够包含泛型信息是与toString()方法的唯一区别。
例:
public class Reflect1 {
private int i;
protected String s;
public List
public Reflect1() {
System.out.println("default");
}
protected Reflect1(String name) {
System.out.println(name);
}
public void f() {
System.out.println("invoke f");
}
String mf(int j, Object... args) {
System.out.println("invoke mf");
return String.valueOf(j + args.length);
}
}
public class Rf {
public static void main(String[] args) throws Exception {
Class
Reflect1 rf1 = clazz.newInstance();
// 含有Declared字符串的是获取所有的元素,否则就只能获取公有元素
Field[] f = clazz.getDeclaredFields();
for (Field field : f) {
// 设置这里本不具有访问权限的元素为可访问
field.setAccessible(true);
// 使用基本数据类型专有的API
if (field.getType().getCanonicalName().equals("int")
|| field.getType().getCanonicalName().equals(
"https://www.sodocs.net/doc/1717863722.html,ng.Integer")) {
field.setInt(rf1, 9);
}
System.out.println("Field is " + field.getName() + "\t"
+ field.toGenericString() + "\t" + field.get(rf1));
}
}
}
控制台输出如下语句:
default
Field is i private int net.ilkj.reflect.Reflect1.i 9
Field is s protected https://www.sodocs.net/doc/1717863722.html,ng.String net.ilkj.reflect.Reflect1.s null Field is list
public java.util.List
______________________________________________
(2.)Method:这个类用于获取类中的方法的信息以及访问这些方法的能力。
l getDefaultValue():返回此方法表示的注视成员的默认值;
l getParameterAnnotations():返回一个Annotation的二维数组,表示方法的参数列表的参数的注解;
l getExceptionTypes():返回这个方法抛出的(底层)异常的Class数组;
l getGenericExceptionTypes():返回这个方法抛出的异常的Type数组,包含泛型信息;
l getParameterTypes():返回这个方法的参数列表的Class对象数组;
l getGenericParameterTypes():返回这个方法的参数列表的包含泛型信息的Type 对象数组,例如一个List
l getReturnType():获取方法返回值的类型的Class对象;
l getGenericReturnType():获取方法返回值的类型的Type对象,含有泛型信息;l isBridge():如果此方法是bridge方法,返回true;
l isVarArgs():如果此方法的参数列表中含有可变参数,返回true;
l invoke(Object o,Object… args):使用类的对象和可变参数列表调用这个方法,这个方法返回Object对象,也就是类中的这个方法的返回值;
l getTypeParameters():以https://www.sodocs.net/doc/1717863722.html,ng.reflect.TypeV ariable数组返回Method对象的泛型方法的类型参数,数组的顺序是泛型定义的类型的顺序;
l toGenericString():返回描述此方法的字符串(包含泛型信息),能够包含泛型信息是此方法与toString()方法的唯一区别。
例:
public class Reflect1
private int i;
protected String s;
public List
public Reflect1() {
System.out.println("default");
}
protected Reflect1(String name) {
System.out.println(name);
}
public void f() throws NumberFormatException,
ArrayIndexOutOfBoundsException {
System.out.println("invoke f");
}
String mf(List
System.out.println("invoke mf");
return String.valueOf(list.size() + args.length);
}
}
public class Rf {
public static void main(String[] args) throws Exception {
Class
Reflect1 rf1 = clazz.newInstance();
Method[] m = clazz.getDeclaredMethods();
for (Method method : m) {
Class[] cs = method.getParameterTypes();
System.out.println("Method is " + method.getName());
// 获取泛型的异常
Type[] t = method.getGenericExceptionTypes();
for (Type type : t) {
System.out.println("GenericException is " + type.toString());
}
// 获取普通的异常
Class[] cc = method.getExceptionTypes();
for (Class c : cc) {
System.out.println("Exception is " + c.getCanonicalName());
}
if (cs.length == 2) {
if (cs[0].getCanonicalName().equals("java.util.List")
&& cs[1].getCanonicalName()
.equals("https://www.sodocs.net/doc/1717863722.html,ng.Object[]")) {
Object o = method.invoke(rf1, new ArrayList
new Object[] { "2" });
System.out.println("The Return Value is " + o);
}
}
}
}
}
控制台输出如下语句:
default
Method is mf
GenericException is X
Exception is https://www.sodocs.net/doc/1717863722.html,ng.Exception
invoke mf
The Return Value is 1
Method is f
GenericException is class https://www.sodocs.net/doc/1717863722.html,ng.NumberFormatException
GenericException is class https://www.sodocs.net/doc/1717863722.html,ng.ArrayIndexOutOfBoundsException
Exception is https://www.sodocs.net/doc/1717863722.html,ng.NumberFormatException
Exception is https://www.sodocs.net/doc/1717863722.html,ng.ArrayIndexOutOfBoundsException
注意红色的代码,你就能区分出含有Generic字符串的方法和不含有这个字符串的方法的区别了。
______________________________________________
(3.)Constructor:这个类用于获取类中的构造方法的信息以及访问这些构造方法的能力。
构造方法由于比较特殊,所以单独作为一个类,但是它的方法和Method没有任何区别。
______________________________________________
(4.)Member接口:Class、Field、Method、Constructor都实现了Member接口,表明他们是类的成员。
l getDeclaringClass():返回此成员所属的类的Class对象;
l getModifiers():以整型值的和的形式返回这个成员的修饰符;
l getName():返回此成员的名称的字符串;
l isSynthetic():如果此成员是编译器引入的,返回true。
______________________________________________
(5.)AccessibleObject类:Class、Field、Method(注意没有Constructor,可见构造方法的访问级别绝对不可以改变!)都扩展了AccessibleObject类,这个类提供了取消JAVA访问权限的限制。
l getAnnotation(Class
l getAnnotations():返回这个成员上的所有注解的Annotation数组;
l getDeclaredAnnotations():返回直接应用在此元素上的注解的Annotation数组;l isAccessible():如果该成员在当前位置可以被访问,返回true;
l isAnnotationPresent(Class extends Annotation> annotationClass):返回指定的annotationClass是否存在于当前的成员;
l setAccessible(boolean bool):设置此元素是否可以在当前位置访问,这个方法在外面访问类中的私有成员时,非常有用处;
l setAccessible(AccessibleObject[] array,boolean flag):这是一个静态方法,你可以将一组成员包装成AccessibleObject数组,一并设置他们的访问性。
______________________________________________
(6.)Modifier:这个类存放了所有修饰符的常量值,以及通过这些常量值判断是哪种修饰符。你可以将getModifiers()方法返回的int类型传入这个类的isXXX()静态方法,判断是哪种修饰符。
______________________________________________
(7.)Array:这个类提供了动态创建和访问数组的能力。
这个类中的方法全部是静态方法,它允许在get()、set()方法时进行扩展转型,如果进行收缩转型,将会抛出非法的参数异常。这个类的方法都是静态方法。
l get(Object o,int index):获取指定数组中的索引位置index上的元素;
l set(Object o,int index,Object value):设置指定数组中的索引位置index上的元素
为value;
getXXX()、setXXX()可以直接设置基本数据类型;
l newInstance(Class componentType,int length):创建一个指定类型为componentType的长度为length的一维数组;
l newInstance(Class componentType,int…args):创建一个指定类型为componentType的args指定的维数的多维数组;
例:
public class Rf {
public static void main(String[] args) throws Exception {
// 创建一个二维数组,第一维有三个元素,第二维有两个元素
Person[][] i = (Person[][]) Array.newInstance(Person.class, 3, 2);
Array.set(i[0], 0, new Child());
System.out.println(Array.get(i[0], 0));
}
}
class Person {
}
class Child extends Person {
}
______________________________________________
3.动态代理:
要看清楚什么是动态代理的,首先我们来看一下静态代理的做法。无论是那种代理方式,都存在代理对象和目标对象两个模型,所谓目标对象就是我们要生成的代理对象所代理的那个对象。
(1.)包装的模式进行静态代理:
接口:Animal
public interface Animal {
void eat(String food);
String type();
}
实现类:Monkey
public class Monkey implements Animal {
@Override
public String type() {
String type = "哺乳动物";
System.out.println(type);
return type;
}
@Override
public void eat(String food) {
System.out.println("The food is " + food + " !");
}
}
包装类:AnimalWrapper
public class AnimalWrapper implements Animal {
private Animal animal;
// 使用构造方法包装Animal的接口,这样所有的Animal实现类都可以被这个Wrapper 包装。
public AnimalWrapper(Animal animal) {
this.animal = animal;
}
@Override
public void eat(String food) {
System.out.println("+++Wrapped Before!+++");
animal.eat(food);
System.out.println("+++Wrapped After!+++");
}
@Override
public String type() {
System.out.println("---Wrapped Before!---");
String type = animal.type();
System.out.println("---Wrapped After!---");
return type;
}
}
运行程序:
AnimalWrapper aw = new AnimalWrapper(new Monkey());
aw.eat("香蕉");
aw.type();
控制台输出如下语句:
+++Wrapped Before!+++
The food is 香蕉 !
+++Wrapped After!+++
---Wrapped Before!---
哺乳动物
---Wrapped After!---
这里我们完成了对Animal所有子类的代理,在代理方法中,你可以加入一些自己的额外的处理逻辑,就像上面的+++、---输出语句一样。那么Spring的前置、后置、环绕方法通知,通过这种方式可以有限的模拟出来,以Spring的声明式事务为例,无非就是在调用包装的目标方法之前处开启事务,在之后提交事务,这样原有的业务逻辑没有受到任何事务管理代码的侵入。
这种方式的静态代理,缺点就是当Animal接口中增加了新的方法,那么包装类中也必须增加这些新的方法。
(2.)继承的模式进行静态代理:
继承类:MyMonkey
public class MyMonkey extends Monkey {
@Override
public void eat(String food) {
System.out.println("+++Wrapped Before!+++");
super.eat(food);
System.out.println("+++Wrapped After!+++");
}
@Override
public String type() {
System.out.println("---Wrapped Before!---");
String type = super.type();
System.out.println("---Wrapped After!---");
return type;
}
}
这个例子很容易看懂,我们采用继承的方式对MyMonkey中的方法进行代理,运行效果与包装的模式效果是一样的。
但这种方式的缺点更明显,那就是不能实现对Animal所有子类的代理,与包装的模式相比,大大缩小了代理范围。
_______________________________________________________________________________ (3.)基于Proxy的动态代理:
JA V A自带的动态代理是基于https://www.sodocs.net/doc/1717863722.html,ng.reflect.Proxy、https://www.sodocs.net/doc/1717863722.html,ng.reflect.InvocationHandler两个类来完成的,使用JA V A反射机制。
Proxy类中的几个方法都是静态的,通常,你可以使用如下两种模式创建代理对象:
①
Object proxy = Proxy.newProxyInstance(定义代理对象的类加载器,
要代理的目标对象的归属接口数组,回调接口InvocationHandler);
②
Class proxyClass=Proxy.getProxyClass(定义代理对象的类加载器,
要代理的目标对象的归属接口数组);
Object proxy = proxyClass.getConstructor(
new Class[] { InvocationHandler.class }).newInstance(
回调接口InvocationHandler);
第一种方式更加直接简便,并且隐藏了代理$Proxy0对象的结构。
JDK的动态代理会动态的创建一个$Proxy0的类,这个类继承了Proxy并且实现了要代理的目标对象的接口,但你不要试图在JDK中查找这个类,因为它是动态生成的。$Proxy0的结构大致如下所示:
public final class $Proxy0 extends Proxy implements 目标对象的接口1,接口2,…{
//构造方法
Public $Proxy0(InvocationHandler h){
……
}
}
从上面的类结构,你就可以理解为什么第二种创建代理对象的方法为什么要那么写了。
下面我们看一个具体的实例:
接口1:Mammal(哺乳动物)
public interface Mammal {
void eat(String food);
String type();
}
接口2:Primate(灵长类动物)
public interface Primate {
void think();
}
实现类:Monkey
public class Monkey implements Mammal, Primate {
@Override
public String type() {
String type = "哺乳动物";
System.out.println(type);
return type;
}
@Override
public void eat(String food) {
System.out.println("The food is " + food + " !");
}
@Override
public void think() {
System.out.println("思考!");
}
}
回调类:MyInvocationHandler
public class MyInvocationHandler implements InvocationHandler { private Object obj;
public MyInvocationHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Invoke method Before!");
Object returnObject = method.invoke(obj, args);
System.out.println("Invoke method After!");
return returnObject;
}
}
注意:这里我们使用构造方法将要代理的目标对象传入回调接口,当然你也可以用其他的方式,但无论如何,一个代理对象应该是与一个回调接口对应的。
运行程序:
// 第一种创建动态代理的方法
// Object proxy = Proxy.newProxyInstance(Monkey.class.getClassLoader(), // Monkey.class.getInterfaces(), new MyInvocationHandler(
// new Monkey()));
// 第二种创建动态代理的方法
Class> proxyClass = Proxy.getProxyClass(
Monkey.class.getClassLoader(),
Monkey.class.getInterfaces());
Object proxy = proxyClass.getConstructor(
new Class[] { InvocationHandler.class }).newInstance(
new MyInvocationHandler(new Monkey()));
Mammal mammal = (Mammal) proxy;
mammal.eat("香蕉");
mammal.type();
Primate primate = (Primate) proxy;
primate.think();
控制台输出:
Invoke method Before!
The food is 香蕉 !
Invoke method After!
Invoke method Before!
哺乳动物
Invoke method After!
Invoke method Before!
思考!
Invoke method After!
你可以看到动态代理成功了,在目标对象的方法调用前后都输出了我们打印的语句。其实Spring中对接口的动态代理,进而做诸如声明式事务的AOP操作也是如此,只不过代码会更加复杂。
我们用下面的图说明上面的执行过程:
我们看到目标对象的方法调用被Proxy拦截,在InvocationHandler中的回调方法中通过反射调用。这种动态代理的方式实现了对类的方法的运行时修改。
JDK的动态代理有个缺点,那就是不能对类进行代理,只能对接口进行代理,想象一下我们的Monkey如果没有实现任何接口,那么将无法使用这种方式进行动态代理(实际上是因为$Proxy0这个类继承了Proxy,JA V A的继承不允许出现多个父类)。但准确的说这个问题不应该是缺点,因为良好的系统,每一个类都是应该有一个接口的。
从上面知道$Proxy0是动态代理对象的所属类型,但由于这个类型根本不存在,我们如何鉴别一个对象是一个普通的对象还是动态代理对象呢?Proxy类中提供了isProxyClass(Class c)方法鉴别与此。
下面我们介绍一下InvocationHandler这个接口,它只有一个方法invoke()需要实现,这个方法会在目标对象的方法调用的时候被激活,你可以在这里控制目标对象的方法的调用,在调用前后插入一些其他操作(譬如:鉴权、日志、事务管理等)。Invoke()方法的后两个参数很好理解,一个是调用的方法的Method对象,另一个是方法的参数,第一个参数有些需要注意的地方,这个proxy参数就是我们使用Proxy的静态方法创建的动态代理对象,也就是$Proxy0的实例(这点你可以在Eclipse的断点调试中看到proxy的所属类型确实是$Proxy0)。由于$Proxy0在JDK中不是静态存在的,因此你不可以把第一个参数Object proxy强制转换为$Proxy0类型,因为你根本就无法从Classpath中导入$Proxy0。那么我们可以把proxy转为目标对象的接口吗?因为$Proxy0是实现了目标对象的所有的接口的,答案是可以的。但实际上这样做的意义不大,因为你会发现转换为目标对象的接口之后,你调用接口中的任何一个方法,都会导致invoke()的调用陷入死循环而导致堆栈溢出。如下所示:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Mammal mammal=(Mammal)proxy;
mammal.type();
……
}
这是因为目标对象的大部分的方法都被代理了,你在invoke()通过代理对象转换之后的接口调用目标对象的方法,依然是走的代理对象,也就是说当mammal.type()方法被激活时会立即导致invoke()的调用,然后再次调用mammal.type()方法,……从而使方法调用进入死循环,就像无尽的递归调用。
那么invoke()方法的第一个参数到底干什么用的呢?其实一般情况下这个参数都用不到,除
非你想获得代理对象的类信息描述,因为它的getClass()方法的调用不会陷入死循环。如下所示:
Class> c = proxy.getClass();
Method[] methods = c.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m.getName());
}
这里我们可以获得代理对象的所有的方法的名字,你会看到控制台输出如下信息:
eat
think
type
equals
toString
hashCode
我们看到proxy确实动态的把目标对象的所有的接口中的方法都集中到了自己的身上。
这里还要注意一个问题,那就是从Object身上继承的方法hashCode()等的调用也会导致陷入死循环,为什么getClass()不会呢?因为getClass()方法是final的,不可以被覆盖,所以也就不会被Proxy代理。但不要认为Proxy不可以对final的方法进行动态代理,因为Proxy面向的是Monkey的接口,而不是Monkey本身,所以即便是Monkey在实现Mammal、Primate 接口的时候,把方法都变为final的,也不会影响到Proxy的动态代理。
_______________________________________________________________________________ (4.)基于CGLIB的动态代理:
CGLIB是一个开源的动态代理框架,它的出现补充了JDK自带的Proxy不能对类实现动态代理的问题。CGLIB是如何突破限制,对类也能动态代理的呢?这是因为CGLIB内部使用了另一个字节码框架ASM,类似的字节码框架还有Javassist、BCEL等,但ASM被认为是性能最好的一个。但这类字节码框架要求你对JA V A的Class文件的结构、指令集都比较了解,CGLIB对外屏蔽了这些细节问题。由于CGLIB使用ASM直接操作字节码,因此效率要比Proxy高,但这里所说的效率是指代理对象的性能,在创建代理对象时,Proxy是要比CGLIB效率高的。
下面我们简单看一个CGLIB完成动态代理的例子。
目标类:Monkey
public class Monkey {
public String type() {
String type = "哺乳动物";
System.out.println(type);
return type;
}
public final void eat(String food) {
System.out.println("The food is " + food + " !");
}
public void think() {
System.out.println("思考!");
}
}
我们看到这个Monkey类有两点变化,第一点是没有实现任何接口,第二点是eat()方法是final的。
回调接口:
import https://www.sodocs.net/doc/1717863722.html,ng.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("******************");
Object o = proxy.invokeSuper(obj, args);
System.out.println("++++++++++++++++++");
return o;
}
}
运行程序:
import net.sf.cglib.proxy.Enhancer;
public class Cglib {
public static void main(String[] args) {
Monkey monkey = (Monkey) Enhancer.create(Monkey.class,
new MyMethodInterceptor());
monkey.eat("香蕉");
monkey.type();
monkey.think();
}
}
控制台输出:
The food is 香蕉 !
******************
哺乳动物
++++++++++++++++++
******************
思考!
++++++++++++++++++
你会发现eat()方法没有被代理,因为在它的前后没有输出MethodInterceptor中的打印语句。这是因为CGLIB动态代理的原理是使用ASM动态生成目标对象的子类,final方法不能被子类覆盖,自然也就不能被动态代理,这也是CGLIB的一个缺点。
我们看到CGLIB进行动态代理的编写过程与Proxy没什么太大的不同,Enhancer是CGLIB 的入口,通过它创建代理对象,同时为代理对象分配一个net.sf.cglib.proxy.Callback回调接口,用于执行回调。我们常用的是MethodInterceptor接口,这个接口继承自Callback接口,用于执行方法拦截。
MethodInterceptor接口中的intercept()方法中的参数分别为代理对象、被调用的方法的Method对象,方法的参数、CGLIB提供的方法代理对象,一般来说调用目标方法时我们使用最后一个参数,而不是JA V A反射的第二个参数,因为CGLIB使用ASM的字节码操作,代理对象的执行效率比反射机制更高。
(4-1.)关于Enhancer:
这个类的静态方法create()可以用于创建代理对象,CGLIB使用动态生成子类的方式完成动态代理,那么默认情况下,子类会继承父类的无参构造进行实例化,如果你想调用父类的其他构造器,可以使用create(Class[] argumentsType,Object[] arguments)这个重载方法分别指定构造方法的参数类型、传递参数。
但是一般情况下,我们会创建Enhancer的实例来完成动态代理,而不是使用静态方法create(),因为使用Enhancer的实例,你可以获取更多的功能。使用Enhancer实例的代码如下所示:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Monkey.class);
enhancer.setCallback(new MyMethodInterceptor());
Monkey monkey = (Monkey) enhancer.create();
monkey.eat("香蕉");
monkey.type();
monkey.think();
通过Enhancer的实例你可以设置是否使用缓存、生成策略等。
(4-2.)关于Callback:
除了MethodInterceptor以外,CGLIB还提供了一些内置的回调处理。
l net.sf.cglib.proxy.FixedValue
为提高性能,FixedV alue回调对强制某一特别方法返回固定值是有用的。
l net.sf.cglib.proxy.NoOp
NoOp回调把对方法调用直接委派到这个方法在父类中的实现,相当于不进行代理。
l https://www.sodocs.net/doc/1717863722.html,zyLoader
当实际的对象需要延迟装载时,可以使用LazyLoader回调。一旦实际对象被装载,它将被每一个调用代理对象的方法使用。
(4-3.)关于CallbackFilter:
Enhancer的setCallbacks(Callback[] callbacks)方法可以为代理对象设置一组回调器,你可以配合CallbackFilter为不同的方法使用不同的回调器。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Monkey.class);
enhancer.setCallbacks(new Callback[] { new MyMethodInterceptor(),
NoOp.INSTANCE });
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method arg0) {
// 方法type使用回调组中的第二个回调器
if (arg0.getName().equals("type"))
return 1;
else
return 0;
}
});
Monkey monkey = (Monkey) enhancer.create();
monkey.eat("香蕉");
monkey.type();
monkey.think();
这里我们指定type()方法使用第二个回调器(也就是什么也不做的NoOp,相当于不对type()方法进行代理),其余的使用第一个回调器。这也就是说CallbackFilter的accept()方法返回的是回调器的索引值。
CGLIB被Hibernate、Spring等很多开源框架在内部使用,用于完成对类的动态代理,Spring 中的很多XML配置属性的proxy-target-class,默认都为false,其含义就是默认不启用对目标类的动态代理,而是对接口进行动态代理。某些情况下,如果你想对Struts2的Action或者Spring MVC的Controller进行动态代理,你会发现默认Spring会报告找不到$Proxy0的xxx方法,这是因为一般我们都不会给控制层写一个接口,而是直接在实现类中写请求方法,这样JDK自带的Proxy是找不到这些方法的,因为他们不在接口中,此时你就要设置proxy-target-class=”true”,并引入CGLIB、ASM的类库,Spring的动态代理就可以正常工作了。
_______________________________________________________________________________ 5.Spring的AOP编程:
Spring的AOP底层通过动态代理(接口代理使用Proxy、类代理使用CGLIB)来做支持,有了前面的知识,Spring的AOP就比较好理解了,就是在运行时通过动态代理,动态的将某段代码织入到你的程序,从而在不影响原有的业务代码时增加了新的功能。
AOP涉及到如下几个概念:
l切面Aspect:切面就是一个关注点的模块化,譬如:事务管理、日志记录、权限管理都是所谓的切面。
l连接点Joinpoint:程序执行时的某个特定的点,在Spring中就是一个方法的执行。
l通知Advice:通知就是在切面的某个连接点上执行的操作,也就是事务管理、日志记录等的代码。
l切入点Pointcut:切入点是指描述某一类特定的连接点,也就是说指定某一类要织入通知的方法。
l目标对象Target:就是被AOP动态代理的目标对象。
(5-1.)启用注解风格的AOP:
要启用注解风格的AOP,需要配置以下的内容:
这个元素的proxy-target-class属性默认为false,也就是使用Proxy接口动态代理,当然你可以指定为true,强制使用CGLIB类动态代理。
目标对象Target:
@Service("myService")
public class MyServiceImpl implements IMyService {
@Override
public User m1(User u, String id) {
System.out.println("m1 executed!");
if (id == null || "".equals(id))
return u;
else
return new User("1", "Hello World!");
}
@Override
public User m2(User u, String id) {
System.out.println("m2 executed!");
if (id == null || "".equals(id))
return u;
else
return new User("2", "Hello World!");
}
}
动态代理 简单介绍 代理可分为静态代理和动态代理。静态代理在源代码级实现,而动态代理在运行时实现。 代理的几个概念: 抽象角色:声明真实对象和代理对象的共同接口。(就是接口) 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。(就是实现上述接口的代理,即代理对象)真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。(就是要代理的真正对象,即被代理对象,或称目标对象) 在使用JDK中的动态代理时,要注意目标对象必须实现接口。动态代理的几个概念: 动态代理类:实现了一系列的接口,而这些接口是在动态代理类被创建时指定的。 代理接口:就是由动态代理类所实现的接口。 代理实例:就是动态代理类的实例。 动态代理类和其实例可以由https://www.sodocs.net/doc/1717863722.html,ng.reflect.Proxy来创建。如要创造接口Foo的代理: InvocationHandler handler = new MyInvocationHandler(...); //创建动态代理类 Class proxyClass = Proxy.getProxyClass( Foo.class.getClassLoader(), new Class[] { Foo.class }); //创建动态代理类的实例 Foo f = (Foo) proxyClass. getConstructor(new Class[] { InvocationHandler.class }). newInstance(new Object[] { handler }); 或者使用(更简单,一步完成创建动态代理类的实现): Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[] { Foo.class }, handler); 实例演示 DynamicProxy.java
JA V A的反射机制与动态代理 李海峰(QQ:61673110)-Andrew830314@https://www.sodocs.net/doc/1717863722.html, 运行时类型信息(RunTime Type Information,RTTI)使得你在程序运行时发现和使用类型信息。RTTI主要用来运行时获取向上转型之后的对象到底是什么具体的类型。 1.Class对象: JAVA使用Class对象来执行RTTI。每个类都有一个Class对象,它用来创建这个类的所有对象,反过来说,每个类的所有对象都会关联同一个Class对象(对于数组来说,维数、类型一致的数组的Class对象才是相同的),每个对象的创建都依赖于Class对象的是否创建,Class对象的创建发生在类加载(https://www.sodocs.net/doc/1717863722.html,ng.ClassLoader)的时候。 https://www.sodocs.net/doc/1717863722.html,ng.Class类实现了Serializable、GenericDeclaration、Type、AnnotatedElement四个接口,分别实现了可序列化、泛型定义、类型、元数据(注解)的功能。 你可以把Class对象理解为一个类在内存中的接口代理(它代理了这个类的类型信息、方法签名、属性),JVM加载一个类的时候首先创建Class对象,然后创建这个类的每个实例的时候都使用这个Class对象。 Class只有一个私有的无参构造方法,也就是说Class的对象创建只有JVM可以完成。 如何验证同一个类的多个对象的Class对象是一个呢? Cf1 cf1 = new Cf1(); Class clazz = Cf1.class; System.out.println(cf1.getClass() == clazz); 我们知道==用来比较引用是否相等(也就是同一个引用),上面的输出语句结果是true。那么Class对象是否相等是JAVA对象中唯一可以使用==判断的。 如何获取Class对象: 1.所有的引用数据类型(类-类型)的类名、基本数据类型都可以通过.class方式获取其Class 对象(对于基本数据类型的封装类还可以通过.TYPE的方式获取其Class对象,但要注意.TYPE实际上获取的封装类对应的基本类型的Class对象的引用,那么你可以判断出int.class==Integer.TYPE返回true,int.class==Integer.class返回false!),通过这种方式不会初始化静态域,使用.class、.TYPE的方式获取Class对象叫做类的字面常量; 2.Class的forName(String name)传入一个类的完整类路径也可以获得Class对象,但由于使用的是字符串,必须强制转换才可以获取泛型的Class
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。其中LEAD/LEAD++ 、OpenC++ 、MetaXa和OpenJava等就是基于反射机制的语言。最近,反射机制也被应用到了视窗系统、操作系统和文件系统中。 反射本身并不是一个新概念,它可能会使我们联想到光学中的反射概念,尽管计算机科学赋予了反射概念新的含义,但是,从现象上来说,它们确实有某些相通之处,这些有助于我们的理解。在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。可以看出,同一般的反射概念相比,计算机科学领域的反射不单单指反射本身,还包括对反射结果所采取的措施。所有采用反射机制的系统(即反射系统)都希望使系统的实现更开放。可以说,实现了反射机制的系统都具有开放性,但具有开放性的系统并不一定采用了反射机制,开放性是反射系统的必要条件。一般来说,反射系统除了满足开放性条件外还必须满足原因连接(Causally-connected)。所谓原因连接是指对反射系统自描述的改变能够立即反映到系统底层的实
际状态和行为上的情况,反之亦然。开放性和原因连接是反射系统的两大基本要素。 Java中,反射是一种强大的工具。它使您能够创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代表链接。反射允许我们在编写与执行时,使我们的程序代码能够接入装载到JVM中的类的内部信息,而不是源代码中选定的类协作的代码。这使反射成为构建灵活的应用的主要工具。但需注意的是:如果使用不当,反射的成本很高。 二、Java中的类反射: Reflection 是 Java 程序开发语言的特征之一,它允许运行中的Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性。Java 的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息。 1.检测类: 1.1 reflection的工作机制 考虑下面这个简单的例子,让我们看看 reflection 是如何工作的。
Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在。灵活掌握Java反射机制,对大家以后学习框架技术有很大的帮助。 那么什么是Java的反射呢? 大家都知道,要让Java程序能够运行,那么就得让Java类要被Java虚拟机加载。Java类如果不被Java虚拟机加载,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了你所需要的那个类的已经被加载了。 Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射。 那么Java反射有什么作用呢? 假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。 Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。大家都用过Jcreator和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。 Class类 要正确使用Java反射机制就得使用https://www.sodocs.net/doc/1717863722.html,ng.Class这个类。它是Java反射机制的起源。当一个类被加载以后,Java虚拟机就会自动产生一个Class对象。通过这个Class对象我们就能获得加载到虚拟机当中这个Class对象对应的方法、成员以及构造方法的声明和定义等信息。 反射API ◆反射API用于反应在当前Java虚拟机中的类、接口或者对象信息 ◆功能 —获取一个对象的类信息. —获取一个类的访问修饰符、成员、方法、构造方法以及超类的信息. —检获属于一个接口的常量和方法声明. —创建一个直到程序运行期间才知道名字的类的实例. —获取并设置一个对象的成员,甚至这个成员的名字是 在程序运行期间才知道. —检测一个在运行期间才知道名字的对象的方法 利用Java反射机制我们可以很灵活的对已经加载到Java虚拟机当中的类信
泛型(Generic) —泛形的作用 JDK5中的泛形允许程序员在编写集合代码时,就限制集合的处理类型,从而把原来程序运行时可能发生问题,转变为编译时的问题,以此提高程序的可读性和稳定性(尤其在大型程序中更为突出)。 注意:泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。 泛形的基本术语,以ArrayList
————————————————————————————— 动态代理原理简析 一、概述 1.动态编译https://www.sodocs.net/doc/1717863722.html,pilationTask 动态编译想理解自己查API文档 2.反射被代理类主要使用Method.invoke(Object o,Object... args);对带有指定参数的指定对象调用由此Method 对象表示的底层方法。 3.类的加载URLClassLoader可以加载硬盘任意位置的.java文件。class.getClassLoader只能加载classPath目录下的类。 动态代理可以理解为动态生成发射代理的类。这其中可以动态增加逻辑操作。比如日志的打印,事物的处理等。spring的AOP操作也是动态代理的。 二、创建业务接口 假设我们有一个接口GrowAble可成长的。 1.package https://www.sodocs.net/doc/1717863722.html,; 2. 3.public interface GrowAble { 4. void growUp(); 5.} 一棵小树苗实现了这个接口 1.package https://www.sodocs.net/doc/1717863722.html,; 2.public class Tree implements GrowAble { 3. @Override 4. public void growUp() { 5. System.out.println('I am a tree , I'm grow up!'); 6. } 7. 8.} 这时我们想不在不改变源码的情况下想知道树长了多少这个操作? 我们需要一个转换接口。
————————————————————————————— 1.package https://www.sodocs.net/doc/1717863722.html,; 2.import https://www.sodocs.net/doc/1717863722.html,ng.reflect.Method; 3. 4.public interface InvactionHandle { 5. void invoke(Object o,Method m); 6.} 一个实现接口类。 01.package https://www.sodocs.net/doc/1717863722.html,; 02.import https://www.sodocs.net/doc/1717863722.html,ng.reflect.Method; 03.import java.util.Random; 04. 05.public class HeightInvactionHandle implements InvactionHandle { 06. @Override 07. public void invoke(Object c, Method m) { 08. try { 09. m.invoke(this.o); 10. System.out.println('这棵树长了' + new Random().nextInt(9527)+'米!!!' ); 11. } catch (Exception e) { 12. e.printStackTrace(); 13. } 14. } 15. private Object o; 16. public HeightInvactionHandle(Object o) { 17. super(); 18. this.o = o; 19. } 20.} 三、其他重要类 现在最重要的Proxy类了。把上述两个接口接口起来。 01.package https://www.sodocs.net/doc/1717863722.html,;
JAVA中的反射机制 一,先看一下反射的概念: 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。 反射是java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接。但是反射使用不当会成本很高! 看概念很晕的,继续往下看。 二,反射机制的作用: 1,反编译:.class-->.java 2,通过反射机制访问java对象的属性,方法,构造方法等; 这样好像更容易理解一些,下边我们具体看怎么实现这些功能。 三,在这里先看一下sun为我们提供了那些反射机制中的类: https://www.sodocs.net/doc/1717863722.html,ng.Class; https://www.sodocs.net/doc/1717863722.html,ng.reflect.Constructor; https://www.sodocs.net/doc/1717863722.html,ng.reflect.Field; https://www.sodocs.net/doc/1717863722.html,ng.reflect.Method; https://www.sodocs.net/doc/1717863722.html,ng.reflect.Modifier; 很多反射中的方法,属性等操作我们可以从这四个类中查询。还是哪句话要学着不断的查询API,那才是我们最好的老师。 四,具体功能实现:
1,反射机制获取类有三种方法,我们来获取Employee类型 [java]view plain copy print? 1.//第一种方式: 2.Class c1 = Class.forName("Employee"); 3.//第二种方式: 4.//java中每个类型都有class 属性. 5.Class c2 = Employee.class; 6. 7.//第三种方式: 8.//java语言中任何一个java对象都有getClass 方法 9.Employee e = new Employee(); 10.Class c3 = e.getClass(); //c3是运行时类 (e的运行时类是Employee) 2,创建对象:获取类以后我们来创建它的对象,利用newInstance: 1.Class c =Class.forName("Employee"); 2. 3.//创建此Class 对象所表示的类的一个新实例 4.Objecto = c.newInstance(); //调用了Employee的无参数构造方法. 3,获取属性:分为所有的属性和指定的属性: a,先看获取所有的属性的写法: 1.//获取整个类 2. Class c = Class.forName("https://www.sodocs.net/doc/1717863722.html,ng.Integer"); 3.//获取所有的属性? 4. Field[] fs = c.getDeclaredFields(); 5. 6.//定义可变长的字符串,用来存储属性 7. StringBuffer sb = new StringBuffer(); 8.//通过追加的方法,将每个属性拼接到此字符串中 9.//最外边的public定义 10. sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() +"{\n"); 11.//里边的每一个属性 12.for(Field field:fs){ 13. sb.append("\t");//空格 14. sb.append(Modifier.toString(field.getModifiers())+" ");//获得属性的修饰符,例如 public,static等等 15. sb.append(field.getType().getSimpleName() + " ");//属性的类型的名字 16. sb.append(field.getName()+";\n");//属性的名字+回车
一个例子让你了解Java反射机制 JAVA反射机制: 通俗地说,反射机制就是可以把一个类,类的成员(函数,属性),当成一个对象来操作,希望读者能理解,也就是说,类,类的成员,我们在运行的时候还可以动态地去操作他们. 理论的东东太多也没用,下面我们看看实践 Demo ~ Demo: 1.package cn.lee.demo; 2. 3.import https://www.sodocs.net/doc/1717863722.html,ng.reflect.Constructor; 4.import https://www.sodocs.net/doc/1717863722.html,ng.reflect.Field; 5.import https://www.sodocs.net/doc/1717863722.html,ng.reflect.InvocationTargetException; 6.import https://www.sodocs.net/doc/1717863722.html,ng.reflect.Method; 7.import https://www.sodocs.net/doc/1717863722.html,ng.reflect.Modifier; 8.import https://www.sodocs.net/doc/1717863722.html,ng.reflect.TypeVariable; 9. 10.public class Main { 11. /** 12. * 为了看清楚Java反射部分代码,所有异常我都最后抛出来给虚拟机处 理! 13. * @param args 14. * @throws ClassNotFoundException 15. * @throws InstantiationException 16. * @throws IllegalAccessException 17. * @throws InvocationTargetException 18. * @throws IllegalArgumentException 19. * @throws NoSuchFieldException 20. * @throws SecurityException 21. * @throws NoSuchMethodException 22. */
一.相关类及其方法: https://www.sodocs.net/doc/1717863722.html,ng.reflect.Proxy, Proxy 提供用于创建动态代理类和实例的静态方法. newProxyInstance() 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序 (详见api文档) https://www.sodocs.net/doc/1717863722.html,ng.reflect.InvocationHandler, InvocationHandler 是代理实例的调用处理程序实现的接口。 invoke() 在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。 (详见api文档) 二.源代码: 被代理对象的接口及实现类: package com.ml.test; public interface Manager { public void modify(); } package com.ml.test; public class ManagerImpl implements Manager { @Override public void modify() {
System.out.println("*******modify()方法被调用"); } } 业务代理类: package com.ml.test; import https://www.sodocs.net/doc/1717863722.html,ng.reflect.InvocationHandler; import https://www.sodocs.net/doc/1717863722.html,ng.reflect.Method; public class BusinessHandler implements InvocationHandler { private Object object = null; public BusinessHandler(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("do something before method"); Object ret = method.invoke(this.object, args); System.out.println("do something after method"); return ret; } }
深入理解Java反射机制 本文较为详细的分析了Java反射机制。分享给大家供大家参考,具体如下: 一、预先需要掌握的知识(java虚拟机) java虚拟机的方法区: java虚拟机有一个运行时数据区,这个数据区又被分为方法区,堆区和栈区,我们这里需要了解的主要是方法区。方法区的主要作用是存储被装载的类的类型信息,当java虚拟机装载某个类型的时候,需要类装载器定位相应的class文件,然后将其读入到java虚拟机中,紧接着虚拟机提取class 中的类型信息,将这些信息存储到方法区中。这些信息主要包括: 1、这个类型的全限定名 2、这个类型的直接超类的全限定名 3、这个类型是类类型还是接口类型 4、这个类型的访问修饰符 5、任何直接超接口的全限定名的有序列表 6、该类型的常量池 7、字段信息 8、方法信息 9、除了常量以外的所有类变量 10、一个到class类的引用 等等(读者可以参考《深入java虚拟机》这本书的叙述) Class类: Class类是一个非常重要的java基础类,每当装载一个新的类型的时候,java虚拟机都会在java堆中创建一个对应于新类型的Class实例,该实例就代表此类型,通过该Class实例我们就可以访问该类型的基本信息。上面说到在方法区中会存储某个被装载类的类型信息,我们就可以通过Class实例来访问这些信息。比如,对于上面说到的信息Class中都有对应的方法,如下:
1、getName();这个类型的全限定名 2、getSuperClass();这个类型的直接超类的全限定名 3、isInterface();这个类型是类类型还是接口类型 4、getTypeParamters();这个类型的访问修饰符 5、getInterfaces();任何直接超接口的全限定名的有序列表 6、getFields();字段信息 7、getMethods();方法信息 等等(读者可以自己参看jdk帮助文档,得到更多的信息) 二、java反射详解 反射的概念:所谓的反射就是java语言在运行时拥有一项自观的能力,反射使您的程序代码能够得到装载到JVM中的类的内部信息,允许您执行程序时才得到需要类的内部信息,而不是在编写代码的时候就必须要知道所需类的内部信息,这使反射成为构建灵活的应用的主要工具。 反射的常用类和函数:Java反射机制的实现要借助于4个类:Class,Constructor,Field,Method;其中class代表的是类对象,Constructor-类的构造器对象,Field-类的属性对象,Method -类的方法对象,通过这四个对象我们可以粗略的看到一个类的各个组成部分。其中最核心的就是Class类,它是实现反射的基础,它包含的方法我们在第一部分已经进行了基本的阐述。应用反射时我们最关心的一般是一个类的构造器、属性和方法,下面我们主要介绍Class 类中针对这三个元素的方法: 1、得到构造器的方法 Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的公共构造函数,Constructor[] getConstructors() -- 获得类的所有公共构造函数 Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(与接入级别无关) Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关) 2、获得字段信息的方法
前言,在Java运行时刻,能否知道一个类的属性方法并调用改动之?对于任意一个对象,能否知道他的所属类,并调用他的方法?答案是肯定的。这种动态的获取信息及动态调用方法的机制在Java中称为“反射”(reflection)。 Java反射机制主要提供以下功能: 在运行时判断任意一个对象所属的类; 在运行时构造任意一个类的对象; 在运行时判断任意一个类所具有的成员变量和方法; 在运行时调用任意一个对象的方法。 Reflection 是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例如Object)、实现之interfaces(例如Serializable),也包括fields和methods的所有信息,并可于运行时改变fields内容或调用methods。 一般而言,开发者社群说到动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。 在JDK中,主要由以下类来实现Java反射机制,这些类都位于 https://www.sodocs.net/doc/1717863722.html,ng.reflect包中: Class类:代表一个类; Field 类:代表类的成员变量(成员变量也称为类的属性); Method类:代表类的方法; Constructor 类:代表类的构造方法; Array类:提供了动态创建数组,以及访问数组的元素的静态方法; 例程DateMethodsTest类演示了Reflection API的基本作用,它读取命令行参数指定的类名,然后打印这个类所具有的方法信息,代码如下: Java代码 1.public class DateMethodsTest 2.{ 3. public static void main(String args[]) throws Exception 4. { 5. // 加载并初始化命令行参数指定的类 6. Class> classType = Class.forName("java.util.Date"); 7. // 获得类的所有方法 8. Method methods[] = classType.getDeclaredMethods(); 9. for (int i = 0; i < methods.length; i++) 10. { 11. System.out.println(methods[i].toString());
JAVA反射机制之Class类API实例介绍- AJava JAVA反射机制之Class类API实例介绍 核心提示:本文针对jdk6.0中https://www.sodocs.net/doc/1717863722.html,ng.Class类的API进行了简单的实例应用,例子简单易懂,比较适合入门级阅读。实例简介:/* *IncludeInnerClass类中包含了三种访问控制的内部类,并且以这三个内部类的引用对象和一个整型数作为属性,在外部类的方法中分别调用了内部 本文针对jdk6.0中https://www.sodocs.net/doc/1717863722.html,ng.Class类的API进行了简单的实例应用,例子简单易懂,比较适合入门级阅读。 实例简介:/* *IncludeInnerClass类中包含了三种访问控制的内部类,并且以这三个内部类的引用对象和一个整型数作为属性,在外部类的方法中分别调用了内部类的方法 *外部类有两个构造方法,一个默认构造方法,一个带一个整型参数的构造方法 *Class类的API大家可以直接查阅jdk手册
*getMethods()系列方法基本与getConstructors()系列方法类似,仅在后序文章中对getMethods()系列方法的一个特例做深入介绍,将涉及covariant return type 和bridge methods。 */ package classTest; import https://www.sodocs.net/doc/1717863722.html,ng.reflect.Field; import https://www.sodocs.net/doc/1717863722.html,ng.reflect.Modifier; import https://www.sodocs.net/doc/1717863722.html,ng.reflect.Constructor; import https://www.sodocs.net/doc/1717863722.html,ng.reflect.Method; class IncludeInnerClass implements Runnable{ class DefaultInnerClass{ int i; public DefaultInnerClass(){} DefaultInnerClass(int i){ this.i = i; } void innerMethod(){
Java中的类反射机制 一、反射的概念 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。其中LEAD/LEAD++ 、Open C++ 、Meta Xa和Open Java等就是基于反射机制的语言。最近,反射机制也被应用到了视窗系统、操作系统和文件系统中。 反射本身并不是一个新概念,它可能会使我们联想到光学中的反射概念,尽管计算机科学赋予了反射概念新的含义,但是,从现象上来说,它们确实有某些相通之处,这些有助于我们的理解。在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。可以看出,同一般的反射概念相比,计算机科学领域的反射不单单指反射本身,还包括对反射结果所采取的措施。所有采用反射机制的系统(即反射系统)都希望使系统的实现更开放。可以说,实现了反射机制的系统都具有开放性,但具有开放性的系统并不一定采用了反射机制,开放性是反射系统的必要条件。一般来说,反射系统除了满足开放性条件外还必须满足原因连接(Causally-connected)。所谓原因连接是指对反射系统自描述的改变能够立即反映到系统底层的实际状态和行为上的情况,反之亦然。开放性和原因连接是反射系统的两大基本要素。 Java中,反射是一种强大的工具。它使您能够创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代表链接。反射允许我们在编写与执行时,使我们的程序代码能够接入装载到JVM中的类的内部信息,而不是源代码中选定的类协作的代码。这使反射成为构建灵活的应用的主要工具。但需注意的是:如果使用不当,反射的成本很高。 二、Java类反射 Reflection 是Java 程序开发语言的特征之一,它允许运行中的Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性。Java 的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C 或者C++ 中就没有办法在程序中获得函数定义相关的信息。 1.检测类 1.1 reflection的工作机制 考虑下面这个简单的例子,让我们看看reflection 是如何工作的。 import https://www.sodocs.net/doc/1717863722.html,ng.reflect.*; public class DumpMethods { public static void main(String args[]) { try { Class c = Class.forName(args[0]); Method m[] = c.getDeclaredMethods(); for (int i = 0; i < m.length; i++) System.out.println(m[i].toString()); } catch (Throwable e) { System.err.println(e); }
JAVA的类反射机制 一、什么是反射: 反射的概念是由Smith 在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp 和面向对象方面取得了成绩。其中LEAD/LEAD++ 、OpenC++ 、MetaXa 和 OpenJava 等就是基于反射机制的语言。最近,反射机制也被应用到了视窗系统、操作系统和文件系统中。 反射本身并不是一个新概念,尽管计算机科学赋予了反射概念新的含义。在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。 二、什么是Java 中的类反射: Reflection 是Java 程序开发语言的特征之一,它允许运行中的Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。Java 的这一能力在实际应用中用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C 或者 C++ 中就没有办法在程序中获 得函数定义相关的信息。 Reflection 是Java 被视为动态(或准动态)语言的关键,允许程序于执行期Reflection APIs 取得任何已知名称之class 的內部信息,包括package、type parameters、superclass、implemented interfaces、inner classes, outer class, fields、constructors、methods、modifiers,並可于执行期生成instances、变更fields 內容或唤起methods。 三、Java 类反射中所必须的类: Java 的类反射所需要的类并不多,它们分别是:Field、Constructor、Method、Class、Object,下面我将对这些类做一个简单的说明。 Field 类:提供有关类或接口的属性的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)属性或实例属性,简单的理解可以把它看成一个封装反射类的属性的类。
java有着一个非常突出的动态相关机制:Reflection。这个字的意思是“反射、映象、倒影”,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods1。这种“看透class”的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。 这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例如Object)、实现之interfaces (例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。 目前好多框架都会用到java的反射机制。比如struts2,sping,hibernate。 如果我们不用struts2,自己写一个类似的功能也是可以实现的,比如浏览器通过HTTP发送数据,而这些数据都是字符串,我们接受到这些字符串时,可以通过反射去构造一个对象(通过拦截器做成框架的功能),这样就可以用对象的get和set方法了,而不用原始的getPeremter 方法。事实上,在struts2出来之前,我们又不想用struts1的ActionForm就做过这样项目。 一、Class object 的产生方式有以下几种。 1、运用getClass() 注:每个class 都有此函数 String str = "abc"; Class c1 = str.getClass(); 2、运用static method Class.forName()(最常被使用) Class c1 = Class.forName ("https://www.sodocs.net/doc/1717863722.html,ng.String"); Class c2 = Class.forName ("java.awt.Button"); 3、运用.class 语法 Class c1 = String.class; Class c2 = java.awt.Button.class; 4、运用primitive wrapper classes的TYPE 语法 Class c1 = Integer.TYPE; Class c2 = Long.TYPE; 二、Java类反射中的主要方法 对于以下三类组件中的任何一类来说-- 构造函数、字段和方法-- https://www.sodocs.net/doc/1717863722.html,ng.Class 提供四种
动态代理实现Authorization(授权) * https://www.sodocs.net/doc/1717863722.html,ng.reflect包中的 Proxy和InvocationHandler接口提供了创建(指定类[接口更准确些]的)动态代理类的能力。 我们知道,对象是类的实例,一般使用内存来模拟对象,对象是依据类为模板来创建的,创建时使用new来分配一块内存区(其布局 参考相应类的内存布局),为变量做一些赋值便是对象的初始化了。我们知道通常类是设计时的产物,在设计时我们编写对象的模板(即——类),运行时 产生类的实例。类所处的文件是 .java文件——源文件,之后编译为jvm-----java虚拟机可解释执行的.class字节码文件,在类解析 过程中这些.class文件由类加载器加载到虚拟机(可实现自己的类加载器来加载处于特定路径下的类,或加载用某种加密算法加密过的类文件---这样 便于进行安全控制——具体描述参考(Core java ——Java核心卷二))。在遇到创建对象的指令时使用加载的类来创建对象内存 空间。动态代理是不用创建类文件(当然也不用创建java源文件),就能在虚拟机中构造出类文件区域来(相当于使用Proxy类来 创建一块类内存区域,该区域中的内容相当于加载某个.class文件产生的区域;比如我们在使用DOM技术时,从一个XML文件构造一个 DOM内存表示,它是XML文件的内存表示,但我们也可以直接使用DOM API在内存中构建一个dom树,最终结果就是一个内存DOM树,你不用关心 这个dom树是来自于xmL文件还是直接的运行时构造)。 * ** 关于代理设计模式,这里不再敷述。代理和被代理类(目标类)实现共同的接口。我们在调用时不需区别它是否是真正的目标对象。 代理会转发请求到目标对象的。比如互联网上的代理服务器,我们不必关心它是不是代理,就当它不存在一样。对客户调用端 他是不关心具体是谁来提供这个服务功能;在服务端选择使用代理的原因可能是:安全,日志,防火墙等。就是说代理可提供一些 非功能性功能,比如缓存功能____来加速服务的响应的速度。在面向方面技术(AOP)中这些非功能性需求被称作“方面”他们横切于功能类 的实现中,比如:某个SubServerImpl(子服务实现)中的某个服务方法: subServer.doService(){ //在日志输出中常有类似的代码出现于多个类实现中 loger.log("