动态语言通常指那些在运行时刻可以引入新的函数,已有的函数可以被删除,java从某些方面来说可以实现这样的动态性,就是有一点麻烦,先来看看可以实现"某些动态"功能的反射,主要是获得类的描述信息,并且可以直接方法或者修改字段等,如果要实现真正的动态,得用一些字节库,修改字节
反射可以获得类型的自身信息,一般一个类会包含以下元素
1.所属的package
2.父类信息,限定符等等
3.注解
4.字段
5.方法
6.构造方法
参考:
一个class文件,在载入之后,会将这个类的信息存在方法区中,包括类名,直接朝超类,访问修饰符,常量池,字段信息,方法信息,另外也会在堆上创建一个class类的实例,来通过它方法区中的信息
关于class文件的载入,可以参考我的分类中类加载的分类
关于java字节码文件的信息,可以参考class文件和java语言规范的分类
通常来说,实例在年轻代上,类信息在永久区上,不过他们的结构是非常类似的,对于
gc来说也是一样的(据说java7之内会考虑去掉方法区),Class<List> clazz=List.class;
String[] array=new String[10]; //数组也是类
Class<? extends List> subClass=ArrayList.class.asSubclass(clazz);
/*
* 获得类信息,包括类名/是否接口/
* cast方法类似于(ArrayList)list,向下转型
* asSubClass类似于向上转型
*
*/
System.out.println(subClass.getCanonicalName()+","+
array.getClass().getCanonicalName()+","+array.getClass().getName());
System.out.println(subClass.getSimpleName()+","+array.getClass().getName());
System.out.println(subClass.isInterface());
System.out.println("isPublic:"+(subClass.getModifiers()&Modifier.PUBLIC)+
",isStatic:"+(subClass.getModifiers()&Modifier.STATIC)+
",isSyn:"+(subClass.getModifiers()&Modifier.SYNCHRONIZED));
输出============================================
java.util.ArrayList,java.lang.String[],[Ljava.lang.String;
ArrayList,[Ljava.lang.String;
false
isPublic:1,isStatic:0,isSyn:0
Modifier,修饰符,用于指示一个类的成员的访问策略。就是指示一个类的某个方法,或者字段,更或者是成员类是否可以被外部哪些成员类访问。完整的信息有:
反射的Method、Field、Constructor都能够获得该modifier值,判断访问权限以及其他修饰符。可以通过getModifiers()获得:
它是一个类似二进制位的形式,所以(比如)要判断一个方法是private,不是static,那么将会这些写:
int ms=m.getModifiers();
System.out.println(((ms & Modifier.PRIVATE)==Modifier.PRIVATE) && ((ms & Modifier.STATIC)==Modifier.STATIC));
public class ClassReflect implements SimpleInterface{
public ClassReflect(){
}
public ClassReflect(String a){
}
public ClassReflect(String[] a,int b){
}
}
/*
* 返回类本身所有构造器的Constructor对象,包括私有的,除了父类
* getConstructor返回的是类及其父类公有的构造函数
* 类似的还有Field和Method
*/
Constructor<ClassReflect>[] con=
(Constructor<ClassReflect>[]) ClassReflect.class.getDeclaredConstructors();
System.out.println("这个类有"+con.length+"个构造函数,");
for(Constructor c:con){
if(c.getParameterTypes().length==0)
System.out.println("使用构造函数"+c+"创建实例"+c.newInstance());
if(c.getParameterTypes().length==1)
System.out.println("使用构造函数"+c+
"创建实例"+c.newInstance(new String[]{"hello world"}));
if(c.getParameterTypes().length==2)
System.out.println("使用构造函数"+c+
"创建实例"+c.newInstance(new String[]{"hello world"},2));
}
输出============================================
这个类有3个构造函数,
使用构造函数public org.bhsc.reflect.ClassReflect(java.lang.String[],int)创建实例org.bhsc.reflect.ClassReflect@1901437
使用构造函数public org.bhsc.reflect.ClassReflect(java.lang.String)创建实例org.bhsc.reflect.ClassReflect@1f6226
使用构造函数public org.bhsc.reflect.ClassReflect()创建实例org.bhsc.reflect.ClassReflect@64ea66
/**
* Method类用于获取类中的方法的信息以及访问这些方法的能力. */
public class MethodTest {
public static void main(String args[]) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
Class<C1> clazz=C1.class;
C1 c=clazz.newInstance();
Method[] ms=clazz.getDeclaredMethods();
for(Method m:ms){
/*
* 获得该方法的参数列表
*/
Class[] cs=m.getParameterTypes();
System.out.println("Method is :"+m.getName());
/*
* 注解的默认值
*/
System.out.println(m.getDefaultValue());
/*
* 获取包括泛型在内的异常
*/
Type[] ts=m.getGenericExceptionTypes();
for(Type t:ts){
System.out.println("GenericException is :"+t.toString());
}
//获取普通的异常
Class[] cc=m.getExceptionTypes();
for(Class cw:cc){
System.out.println(cw.getCanonicalName());
}
/*
* 方法的参数个数
*/
if(cs.length==2){
if(cs[0].getCanonicalName().equals("int")
&& cs[1].getCanonicalName().equals("java.lang.Object[]")){
//如果是静态方法,可以不用传c
Object o=m.invoke(c, 123,new Object[]{"2"});
System.out.println("THE return Vlue is:"+o);
}
}
}
}
}
/**
* Field用于获取类中的字段信息以及访问这些字段的能力。
* 包括各种getXXX、setXXX方法,或者是直接的get(obj),set(obj,value)方法
*
*/
public class FieldTest {
public static void main(String[] args) throws InstantiationException, Exception {
Class<C1> clazz=C1.class;
C1 c1=clazz.newInstance();
/*
* Declared字符串获取所有的元素,不然就是获取公有元素
*/
Field[] fs=clazz.getDeclaredFields();
for(Field field:fs){
field.setAccessible(true);
// hard code,不知道有没有更好的办法?
if (fie.getType().getCanonicalName().equals("int")
|| fie.getType().getCanonicalName().equals("java.lang.Integer"))
fie.setInt(entery, Integer.valueOf(param.getFieldVaule().toString()));
else if (fie.getType().getCanonicalName().equals("boolean")
|| fie.getType().getCanonicalName().equals("java.lang.Boolean"))
fie.setBoolean(entery, Boolean.valueOf(param.getFieldVaule().toString()));
else if (fie.getType().getCanonicalName().equals("String")
|| fie.getType().getCanonicalName().equals("java.lang.String"))
fie.set(entery, param.getFieldVaule().toString());
else if (fie.getType().getCanonicalName().equals("long")
|| fie.getType().getCanonicalName().equals("java.lang.Long"))
fie.setLong(entery, Long.valueOf(param.getFieldVaule().toString()));
resultString = "ok," + fie.get(entery);
/*
* field.get(object)获得该对象的field字段
*/
System.out.println("Field is:"+field.getName()+"\t"
+field.toGenericString()+"\t"+field.get(c1));
}
}
}
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoHelloWord {
public String value() default "hello";
}
@AnnoHelloWord
public class AnnoTest {
public AnnoTest(String name) {
this.name = name;
}
@AnnoHelloWord("你好")
private String name;
@Override
@AnnoHelloWord
public String toString() {
System.out.println(this.name);
return this.name;
}
public static void main(String[] args) {
AnnoTest obj = new AnnoTest("nihao");
Method[] ms = AnnoTest.class.getMethods();
for (Method m : ms) {
// 该类是否使用了注解
if (m.isAnnotationPresent(AnnoHelloWord.class)) {
// 获取注解
AnnoHelloWord hw = m.getAnnotation(AnnoHelloWord.class);
// System.out.println(hw.value());
try {
System.out.println(hw.value() + " before...");
m.invoke(obj, new Object[] {});
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
参考Java中的泛型
Java 5对Java类文件的格式做了修订,添加了Signature属性,用来包含不在JVM类型系统中的类型信息。比如以java.util.List接口为例,在其类文件中的Signature属性的声明是<E:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/Collection<TE;>;; ,这就说明List接口有一个类型参数E。在运行时刻,JVM会读取Signature属性的内容并提供给反射API来使用
数组的特殊处理使用 java.lang.reflect.Array 类提供的静态方法的集合。该类中的方法使您能够创建新数组,获得数组对象的长度,读和写数组对象的索引值
public static void main(String[]args){
Class<?>classType=Class.forName("java.lang.String");
Object array=Array.newInstance(classType,10);
Array.set(array,5,"hello");
String s=(String)Array.get(array,5);
System.out.println(s);
}
Java中的私有方法从封装的角度来说,是不允许直接调用的,但是java还是提供了”后门“,让我们调用私有方法。
1 Class c = Class.forName("hidden.HiddenClass");
2
3 // Object obj = c.newInstance(); // IllegalAccessExcetion
4 Constructor constructor = c.getDeclaredConstructor();
/**
*如果注释掉下面的方法,则不能调用
**/
5 constructor.setAccessible(true);
6 Object obj = constructor.newInstance(); // new HiddenClass()
1 Method method = c.getDeclaredMethod("invisible");
2 method.setAccessible(true);
3 method.invoke(obj); // call HiddenClass.invisible()
那么setAccessible到底是什么意思呢?字面上看起来,好像是将private变为public,其实不然。
AccessibleObject是Constructor、Field、Method三个类的基类:
先看下JDK中的说明:
它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。 在反射对象中设置 accessible 标志允许具有足够特权的复杂应用程序(比如 Java Object Serialization(两个特殊的私有方法) 或其他持久性机制)以某种通常禁止使用的方式来操作对象。
看了它的说明就可以明白了,setAccessible是启用和禁用访问安全检查的开关,并不是true的时候提升到public了。
由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
有的时候这将会成为一个安全隐患,为此,我们可以启用java.security.manager来判断程序是否具有调用setAccessible()的权限。默认情况下,内核API和扩展目录的代码具有该权限,而类路径或通过URLClassLoader加载的应用程序不拥有此权限。例如:当我们以这种方式来执行上述程序时将会抛出异常
1.在实际项目中,我们会使用很多其它第三方的包,有的时候是通过修改源代码完成你想要的功能,有的时候,是因为第三方包中仅仅因为某几个方法的访问属性被设置为private,或者只要修改private的字段值即可.这个时候,用这种反射的方法就可以很容易实现了.
2.另外一个场景就是从系统架构层来考虑数据封装.例如系统有一些元数据类,99%的情况下,我们只是提供get方法供其它应用层获得字段的值,如果把修改的set方法也提供出去,那么可能会影响到系统的可维护性.而在系统运行期间,又很难避免的要修改这些元数据的值.这种情况下,也可以通过这种反射的方式来实现.
《反射的性能 》
在我本机测试10E次,直接访问平均每次访问4ns,普通反射800ns,排除安全检查的访问38ns
参考之前的《静态代理与动态代理、CGLIB 》
简单的说,一般的代理模式都是为代理对象专门写一个类或者使用模板模式,这两个的缺点就是代理类太多,并且经常需要修改。
接口:
public interface Animal {
void eat(String food);
String type();
}
public interface Primate {
void think();
}
实现类:
public class Monkey implements Animal,Primate{
public void eat(String food) {
System.out.println("the food is "+food+" !");
}
public String type() {
String type="哺乳动物";
System.out.println(type);
return type;
}
public void think() {
System.out.println("思考");
}
}
在Java中除了动态的修改字节码,是很难在某个方法调用之前知道即将调用这个方法的,因此动态代理实用了另一种方式,即提供接口(可以多个),动态的创建一个该接口的实现类,他只做1件事情:调用接口的方法之时调用Handler去处理该方法调用,应此在Handler中就可以很方面的去代理真正的代理对象
public class DynamicProxy {
public static void main(String args[]) throws {
/*
* 使用第一种方法来创建代理对象
*/
Object proxy=Proxy.newProxyInstance(
Monkey.class.getClassLoader(),
Monkey.class.getInterfaces(),
new AnimalInvocationHandler(new Monkey()));
/*
* 代理对象调用的时候,会调用回调的invoke方法,Method.args是调用invoke的时候传入的
*/
Animal animal=(Animal)proxy;
animal.eat("橡胶");
animal.type();
/*
*第2中方式,动态创建了代理对象
*这个对象的构造函数必须接受一个Handler的参数,看下面的
*getConstructor方法就可以明白
*/
Class<?> proxClass=Proxy.getProxyClass(
Monkey.class.getClassLoader(),
Monkey.class.getInterfaces());
proxy=proxClass.getConstructor(new
Class[]{InvocationHandler.class}).
newInstance(new AnimalInvocationHandler(
new Monkey()));
animal=(Animal)proxy;
animal.eat("香蕉");
animal.type();
Primate pre=(Primate)proxy;
pre.think();
}}
很明显,这种方式的缺点就是不能代理类
作为一个ORM框架,我们已经习惯了使用Hibernate来查询,直接返回我们的DO对象
<resultMap id="trsRuleMap" class="trsRuleDO">
<result property="id" column="ID"/>
<result property="applicationName" column="APPLICATION_NAME"/>
<result property="groupName" column="GROUP_NAME"/>
<result property="type" column="TYPE"/>
<result property="gmtCreate" column="GMT_CREATE"/>
<result property="gmtModified" column="GMT_MODIFIED"/>
<result property="operator" column="OPERATOR"/>
<result property="status" column="STATUS"/>
</resultMap>
<select id="QaTrsRuleDAO.getTrsRuleDOByQuery" resultMap="trsRuleMap">
select ID, APPLICATION_NAME, GROUP_NAME, TYPE, CONTENT, VERSION, MD5, GMT_CREATE, GMT_MODIFIED, OPERATOR, STATUS, testscript
from trs_rules ...
</select>
在使用的时候,我们会配置这么一个Map,然后在sql中直接使用
在Hibernate中就要使用反射
1、根据查询条件构造PreparedStament语句,该语句返回指定字段的值;
2、Hibernate通过读取配置文件得到trsRuleDO类的属性列表list(是一个String数组)和列的对应关系;
3、创建trsRuleDO所属类的Class对象c;c=trsRuleDO.getClass();
4、构造一个for循环,循环的次数为list列表的长度;
4.1、读取list[i]的值,然后构造对应该属性的set方法;
4.2、判断list[i]的类型XXX,调用PreparedStament语句中的getXXX(i),进而得到i出字段的值;
4.3、将4.2中得到的值作为4.1中得到的set方法的参数,这样就完成了一个字段像一个属性的赋值,如此循环即可;
联系客服