反射和动态代理

2017/02/05 Java

反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.

三种获取Class对象的方式

  1. 通过实例获取
  2. 任意数据类型都具备一个class静态属性,看上去要比第一种方式简单.
  3. 将类名作为字符串传递给Class类中的静态方法forName即可
// 方式1
Person p = new Person();
Class c = p.getClass();

Person p2 = new Person();
Class c2 = p2.getClass();

System.out.println(p == p2);// false
System.out.println(c == c2);// true 所有字节码文件是一份

// 方式2
Class c3 = Person.class;
// int.class;
// String.class;
System.out.println(c == c3);// true

// 方式3
// ClassNotFoundEfieldNameception
Class c4 = Class.forName("cn.fieldNamepress.Person");
System.out.println(c == c4);// true

第三种和前两种的区别

前两种你必须明确Person类型。
后面是你我这种类型的字符串就行.这种扩展更强.我不需要知道你的类.我只提供字符串,按照配置文件加载就可以了

九个预定义Class实例对象

  • byte.class
  • int.class
  • short.class
  • long.class
  • float.class
  • double.class
  • char.class
  • boolean.class

  • void.class

判断class是否原始类型或者数组

  • 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象
  • 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
  • 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
String str = "abc";
Class classStr = str.getClass();
// 是否原始类型
System.out.println(classStr.isPrimitive());// false
System.out.println(int.class.isPrimitive());// true
System.out.println(int.class == Integer.class);// false
//public static final Class<Integer>	TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
System.out.println(int.class == Integer.TYPE);// true 九个预定义class实例对象都有对应
System.out.println(int[].class.isPrimitive());// false
// 是否数组
System.out.println(int[].class.isArray());// true

反射的使用

通过反射获取构造方法并使用

  • public Constructor[] getConstructors():所有公共构造方法
  • public Constructor[] getDeclaredConstructors():所有构造方法
// 获取单个构造方法
// public Constructor<T> getConstructor(Class<?>... parameterTypes)
// 参数表示的是:你要获取的构造方法的构造参数个数及数据类型的class字节码文件对象
Constructor con = c.getConstructor();// 返回的是构造方法对象

// Person p = new Person();
// System.out.println(p);
// public T newInstance(Object... initargs)
// 使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
Object obj = con.newInstance();

// 获取带参构造方法对象
// public Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor con = c.getConstructor(String.class, int.class,
		String.class);

con.setAccessible(true);// 值为true则指示反射的对象在使用时应该取消Java语言访问检查。

// 通过带参构造方法对象创建对象
// public T newInstance(Object... initargs)
Object obj = con.newInstance("林青霞", 27, "北京");

// 泛型构造方法 省去强制类型转换
Constructor<String> constructor1 = String.class.getConstructor(StringBuffer.class);
String str2 = constructor1.newInstance(new StringBuffer("abc"));

通过反射获取成员变量并使用

  • Field[] fields = c.getFields();
  • Field[] fields = c.getDeclaredFields();
Class c = Class.forName("cn.itcast_01.Person");

// 通过无参构造方法创建对象
Constructor con = c.getConstructor();
Object obj = con.newInstance();
System.out.println(obj);

// 获取单个的成员变量
// 获取address并对其赋值
Field addressField = c.getField("address");
// public void set(Object obj,Object value)
// 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
addressField.set(obj, "北京"); // 给obj对象的addressField字段设置值为"北京"

// 获取name并对其赋值
// NoSuchFieldEfieldNameception
Field nameField = c.getDeclaredField("name");
// IllegalAccessEfieldNameception
nameField.setAccessible(true);
nameField.set(obj, "林青霞");

// 获取age并对其赋值
Field ageField = c.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(obj, 27);

通过反射获取成员方法并使用

  • Method[] methods = c.getMethods(); // 获取自己的包括父亲的公共方法
  • Method[] methods = c.getDeclaredMethods(); // 获取自己的所有的方法
// 获取字节码文件对象
Class c = Class.forName("cn.itcast_01.Person");

Constructor con = c.getConstructor();
Object obj = con.newInstance();

// 获取单个方法并使用
// public void show()
// public Method getMethod(String name,Class<?>... parameterTypes)
// 第一个参数表示的方法名,第二个参数表示的是方法的参数的class类型
Method m1 = c.getMethod("show");
// obj.m1(); // 错误
// public Object invoke(Object obj,Object... args)
// 返回值是Object接收,第一个参数表示对象是谁,第二参数表示调用该方法的实际参数
m1.invoke(obj); // 调用obj对象的m1方法

// public void method(String s)
Method m2 = c.getMethod("method", String.class);
m2.invoke(obj, "hello");

// public String getString(String s, int i)
Method m3 = c.getMethod("getString", String.class, int.class);
Object objString = m3.invoke(obj, "hello", 100);
// String s = (String)m3.invoke(obj, "hello",100);
// System.out.println(s);

// private void function()
Method m4 = c.getDeclaredMethod("function");
m4.setAccessible(true);
m4.invoke(obj);
Method对象的invoke()方法的第一个参数为null

说明该Method对象对应的是一个静态方法

jdk1.4和jdk1.5的invoke方法的区别
  • Jdk1.5:public Object invoke(Object obj,Object… args)
  • Jdk1.4:public Object invoke(Object obj,Object[] args)
    • 即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数
      • 调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。
反射调用可变参数

通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?
按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?
jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。
所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“fieldNamefieldNamefieldName”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。

public static void main(String[] args) throws EfieldNameception{
	Class clazz = Class.forName("TestArrayArguments");
	Method mMain = clazz.getMethod("main", String[].class);
	mMain.invoke(null,new Object[]{new String[]{"aaa","bbb"}});// 方式1
	mMain.invoke(null,(Object)new String[]{"aaa","bbb"});// 方式2 编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
}
class TestArrayArguments {
	public static void main(String [] args)
	{
		for(String arg:args)
		{
			System.out.println(arg);
		}
	}
}
Arrays.asList()问题

int[]无法转换成Object[],会被JVM当作一个Object处理,而String[]可以转换,当作Object[]处理

int [] a1 = new int[]{1,2,3};
Integer [] a2 = new Integer[]{1,2,3};
String [] a4 = new String[]{"a","b","c"};
System.out.println(Arrays.asList(a1));// [[I@15db9742]
System.out.println(Arrays.asList(a2));// [1, 2, 3]
System.out.println(Arrays.asList(a4));// [a, b, c]

通过反射获取泛型参数类型

public static void applyVector(Vector<Date> v){
}
Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);
Type[] types = applyMethod.getGenericParameterTypes();// 获取泛型类型列表
ParameterizedType pType = (ParameterizedType)types[0];// 获取第一个泛型类型参数的参数化类型
System.out.println(pType.getRawType());//class java.lang.Vector 获取原始类型
System.out.println(pType.getActualTypeArguments()[0]);//class java.lang.Date 获取参数化类型

通过反射获取注解并使用

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface MyAnnotation {
    String color() default "blue"; //String类型并指定默认值
    String value();//特殊字段value
    int[] arrayAttr() default {3,4,4};//数组类型字段
    TrafficLamp lamp() default TrafficLamp.RED;//枚举类型
    MetaAnnotation annotationAttr() default @MetaAnnotation("test");//注解类型
    Class classValue() default String.class;//class类型
}
public @interface MetaAnnotation {
    String value();
}
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;}
}
@MyAnnotation(annotationAttr=@MetaAnnotation("test"),color="red",value="abc",arrayAttr=1)//数组类型只有一个值可以省略花括号
public class AnnotationTest {
    @MyAnnotation("xyz")//只有value字段可以省略字段名和等号
    public static void main(String[] args) throws Exception{
        if(AnnotationTest.class.isAnnotationPresent(MyAnnotation.class)){
            MyAnnotation annotation = (MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
            System.out.println(annotation.color());// red
            System.out.println(annotation.value());// abc
            System.out.println(annotation.arrayAttr()[0]);// 1
            System.out.println(annotation.lamp().nextLamp().name());// GREEN
            System.out.println(annotation.annotationAttr().value());// test
            System.out.println(annotation.classValue().getName());// java.lang.String
        }
    }
}

数组反射

Array类

  • Array.getLength(arrays) 获取长度
  • Array.get(arrays, i) 获取指定索引元素
private static void printObject(Object obj) {
	Class clazz = obj.getClass();
	if(clazz.isArray()){
		int len = Array.getLength(obj);
		for(int i=0;i<len;i++){
			System.out.println(Array.get(obj, i));//
		}
	}else{
		System.out.println(obj);
	}
}

怎么得到数组中的元素类型

似乎没有办法直接得到,需要取出每个元素对象,然后再对各个对象进行判断,因为其中每个具体元素的类型都可以不同,例如Object[] fieldName = new Object[]{“abc”,Integer.MafieldName}。
通过获取元素后判断类型if(field.getType() == String.class)

内省

内省IntroSpector是对JavaBean进行操作的一些API

private static void setProperties(Object objClass, String propertyName,
		Object value) throws IntrospectionEfieldNameception,IllegalAccessEfieldNameception, InvocationTargetEfieldNameception {
	PropertyDescriptor propertyDescriptor = new PropertyDescriptor(propertyName,objClass.getClass());
	Method methodSet = propertyDescriptor.getWriteMethod();
	methodSet.invoke(objClass,value);
}

private static Object getProperty(Object objClass, String propertyName)
		throws IntrospectionEfieldNameception, IllegalAccessEfieldNameception,InvocationTargetEfieldNameception {

	/*PropertyDescriptor pd = new PropertyDescriptor(propertyName,objClass.getClass());
	Method methodGet = pd.getReadMethod();
	Object retVal = methodGet.invoke(objClass);*/

	BeanInfo beanInfo =  Introspector.getBeanInfo(objClass.getClass());
	PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
	Object retVal = null;
	for(PropertyDescriptor pd : pds){
		if(pd.getName().equals(propertyName))
		{
			Method methodGet = pd.getReadMethod();
			retVal = methodGet.invoke(objClass);
			break;
		}
	}
	return retVal;
}

apache开发工具包

  • import org.apache.commons.beanutils.BeanUtils;
  • import org.apache.commons.beanutils.PropertyUtils;
//private int filedName; getFiledName(); setFiledName();
BeanUtils.getProperty(objClass, "fieldName").getClass().getName();// java.lang.String 这的类型是String,免去了web开发中的类型转换
BeanUtils.setProperty(objClass, "fieldName", "9");// 这里的参数也是String类型

//private Date birthday; getBirthDay(); setBirthDay();
BeanUtils.setProperty(objClass, "birthday.time", "111");// 支持级联操作Data.time
BeanUtils.getProperty(objClass, "birthday.time");

/*
//java7的新特性
Map map = {name:"john",age:18};
BeanUtils.setProperty(map, "name", "smith");
*/


PropertyUtils.setProperty(objClass, "fieldName", 9);// 这里的参数是int类型,区别于BeansUtils
PropertyUtils.getProperty(objClass, "fieldName").getClass().getName();// java.lang.Integer 这里的返回值是Integer类型,区别于BeansUtils

注意

用struts的迭代标签不能迭代出枚举元素的属性,而用jstl的迭代标签则可以。
采用BeanUtils去获取带有抽象方法的枚举类的成员对象的属性时,会出现错误,要自己用内省加暴力反射方式才可以获取。
主要原因是枚举类的抽象子类不是public类型的。

Object bean = Sex.NONE;
BeanInfo beanInfo = null;
try {
	beanInfo = Introspector.getBeanInfo(bean.getClass());
} catch (Exception e1) {
	e1.printStackTrace();
}

PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor property:properties)
{
	if(property.getName().equals("title"))
	{
		Method method = property.getReadMethod();
		method.setAccessible(true);
		Object retVal;
		try {
			retVal = method.invoke(bean, null);
			System.out.println(retVal);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}	

动态代理

  • 代理:本来应该自己做的事情,却请了别人来做,被请的人就是代理对象。
  • 动态代理:在程序运行过程中产生的这个对象
    • 而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理
  • JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib

JVM动态代理

  • JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
  • JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
    • 源对象的类必须自己定义时就实现接口,从该类的祖辈类上继承的接口是无效的。

动态代理的工作原理图

public interface Advice {
	void beforeMethod(Method method);
	void afterMethod(Method method);
}
public class MyAdvice implements Advice {
	long beginTime = 0;
	public void afterMethod(Method method) {
		// TODO Auto-generated method stub
		System.out.println("权限校验");		
		long endTime = System.currentTimeMillis();
		System.out.println(method.getName() + " running time of " + (endTime - beginTime));
	}

	public void beforeMethod(Method method) {
		// TODO Auto-generated method stub
		System.out.println("日志记录");
		beginTime = System.currentTimeMillis();
	}
}
// 实现接口方式
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {
	private Object target; // 目标对象
	private Advice advice; // 建议系统功能

	public MyInvocationHandler(Object target,Advice advice) {
		this.target = target;
		this.advice = advice;
	}

	@Override
	public Object invoke(Object proxyObject, Method method, Object[] args)
			throws Throwable {
		advice.beforeMethod(method);
		Object result = method.invoke(target, args);
		advice.afterMethod(method);
		return result; // 返回结果
	}
}
// 匿名内部类方式
public static Object getProxy(final Object target,final Advice advice) {
	Object proxy = Proxy.newProxyInstance(
			target.getClass().getClassLoader(),
			target.getClass().getInterfaces(),
			new InvocationHandler(){
				public Object invoke(Object proxy, Method method, Object[] args)
						throws Throwable {
					advice.beforeMethod(method);
					Object retVal = method.invoke(target, args);
					advice.afterMethod(method);
					return retVal;
				}
			}
		);
	return proxy;
}
UserDao userDao = new UserDaoImpl();
userDao.add();
userDao.delete();
userDao.update();
userDao.find();
// 我们要创建一个动态代理对象
// Proxy类中有一个方法可以创建动态代理对象
// public static Object newProxyInstance(ClassLoader loader,Class<?>[]
// interfaces,InvocationHandler h)
// 我准备对userDao对象做一个代理对象
Advice advice = new MyAdvice();
MyInvocationHandler handler = new MyInvocationHandler(userDao,advice);
UserDao proxyObject = (UserDao) Proxy.newProxyInstance(userDao.getClass()
		.getClassLoader(), userDao.getClass().getInterfaces(), handler);
proxyObject.add();
proxyObject.delete();
proxyObject.update();
proxyObject.find();

StudentDao sd = new StudentDaoImpl();
MyInvocationHandler handler2 = new MyInvocationHandler(sd,advice);
StudentDao proxyObject2 = (StudentDao) Proxy.newProxyInstance(sd.getClass()
		.getClassLoader(), sd.getClass().getInterfaces(), handler2);
proxyObject2.login();
proxyObject2.regist();

final ArrayList target = new ArrayList();			
Collection proxyArrayList = (Collection)getProxy(target,new MyAdvice());
proxyArrayList.add("1");
proxyArrayList.add("2");
proxyArrayList.add("3");
System.out.println(proxyArrayList.size());
System.out.println(proxyArrayList.getClass().getName());

动态类的实例对象的getClass()方法返回正确结果$Proxy0

调用调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。

// 检查类是否是代理类
proxy.isProxyClass();//true

CGlib动态代理

CGlib对没有实现接口的类生成子类代理对象

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CGlibProxyFactory<T> implements MethodInterceptor {
    public T target;// 代理目标对象
    private Advice advice;// 增强

    public T getProxy(final T target, final Advice advice) {
        this.target = target;
        this.advice = advice;

        Enhancer enhancer = new Enhancer();
        // 继承自super class并重写所有非final方法
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return (T) enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        advice.before();
        Object result = method.invoke(target, args);
        advice.after();
        return result;
    }
}

简易实现spring aop

public class BeanFactory {
	Properties props = new Properties();
	public BeanFactory(InputStream ips){
		try {
			props.load(ips);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public Object getBean(String name){
		String className = props.getProperty(name);
		Object bean = null;
		try {
			Class clazz = Class.forName(className);
			bean = clazz.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		} 
		if(bean instanceof ProxyFactoryBean){
			Object proxy = null;
			ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
			try {
				Advice advice = (Advice)Class.forName(props.getProperty(name + ".advice")).newInstance();
				Object target = Class.forName(props.getProperty(name + ".target")).newInstance();
				proxyFactoryBean.setAdvice(advice);
				proxyFactoryBean.setTarget(target);
				proxy = proxyFactoryBean.getProxy();
			} catch (Exception e) {
				e.printStackTrace();
			}
			return proxy;
		}
		return bean;
	}
}
public class ProxyFactoryBean {

	private Advice advice;
	private Object target;
	
	public Advice getAdvice() {
		return advice;
	}

	public void setAdvice(Advice advice) {
		this.advice = advice;
	}

	public Object getTarget() {
		return target;
	}

	public void setTarget(Object target) {
		this.target = target;
	}

	public Object getProxy() {
		Object proxy3 = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				target.getClass().getInterfaces(),
				new InvocationHandler(){
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						advice.beforeMethod(method);
						Object retVal = method.invoke(target, args);
						advice.afterMethod(method);
						return retVal;						
					}
				}
			);
		return proxy3;
	}
}
public interface Advice {
	void beforeMethod(Method method);
	void afterMethod(Method method);
}
public class MyAdvice implements Advice {
	long beginTime = 0;
	public void afterMethod(Method method) {
		// TODO Auto-generated method stub
		System.out.println("权限校验");		
		long endTime = System.currentTimeMillis();
		System.out.println(method.getName() + " running time of " + (endTime - beginTime));
	}

	public void beforeMethod(Method method) {
		// TODO Auto-generated method stub
		System.out.println("日志记录");
		beginTime = System.currentTimeMillis();
	}
}
public static void main(String[] args) throws Exception {
	// TODO Auto-generated method stub
	InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
	Object bean = new BeanFactory(ips).getBean("test");
	System.out.println(bean.getClass().getName());
	// java.util.ArrayList
	// $Proxy1
}
#test=java.util.ArrayList
test=cn.xpress.aopframework.ProxyFactoryBean
test.advice=cn.xpress.MyAdvice
test.target=java.util.ArrayList

以上概念总结于传智播客Java基础课程

Search

    Post Directory