一.反射的概念及作用
1.概念:通过Java反射机制,可以访问程序中已加载到JVM中的Java对象的描述,实现访问、检测和修改描述Java对象本身信息的功能。在java.lang.reflect包中提供了对该功能的支持。
2.作用:
1.反编译:.class-->.java
2.通过反射机制访问java对象的属性,方法,构造方法等;
3.sun为我们提供了那些反射机制中的类:
java.lang.Class;
java.lang.reflect.Constructor;
java.lang.reflect.Field;
java.lang.reflect.Method;
java.lang.reflect.Modifier;
面试题:如何获取类的字节码文件对象,并且有几种方式呢?
1)Object类中的getClass()方法,表示正在运行的那个类:Class类
2)数据类型的class属性 举例:String.class,Student.class
3)Class类中的特有方法:forName(String className):(重点,数据库加载驱动:Drivers)
public static Class<?> forName(String className):获取字节码文件对象(参数是一个字符串...,字符串的内容是一个类的全路径名称)
最常用的方式是方式3)
package reflect;
public class ReflectDemo {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//方式一:创建Person对象
Person p1 = new Person();
Class c1 = p1.getClass();
//创建对象
Person p2 = new Person();
Class c2 = p2.getClass();
System.out.println(p1==p2);//false
System.out.println(c1==c2);//true
//方式二:通过数据类型的Class属性
Class c3 = Person.class ;
System.out.println(c3==c1);//true
//方式三:参数不是一个类名,而是类的全路径名称
// 获取一个类中的构造方法,首先找到这个类的class文件对象,通过反射
Class c4 = Class.forName("reflect.Person");
System.out.println("c4:"+c4); //c4:class reflect.Person
System.out.println(c4==c1);
}
}
4.通过反射机制得到某个类的构造器,然后调用该构造器创建该类的一个实例
1)获取构造器
package reflect;
import java.lang.reflect.Constructor;
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//获取一个类中的构造方法,首先找到这个类的class文件对象,通过反射
Class c = Class.forName("reflect.Person");
//1.public Constructor<?>[] getDeclaredConstructors():获取的是当前字节码文件对象中所有的构造方法
Constructor[] con = c.getDeclaredConstructors();
for(Constructor constructor:con){
System.out.println(constructor);
}
System.out.println("-----------");
/**
* 输出:private reflect.Person(java.lang.String,int,java.lang.String)
* reflect.Person(java.lang.String,int)
* public reflect.Person()
*
* */
//2.public Constructor<?>[] getConstructors():获取的是当前字节码文件对象中所有的公共的构造方法
Constructor[] publicCon= c.getConstructors();
for(Constructor constructor:publicCon){
System.out.println(constructor);
}
System.out.println("-----------");
/**
* public reflect.Person()
* */
}
}
2)获取单个的构造器并创建一个实例
package reflect;
import java.lang.reflect.Constructor;
/**
* 之前创建对象:
* 给对象的成员属性赋值
* Person p = new Person("韩庚",20,"北京")
* System.out.println(p) ;
*
* 下面通过反射获取构造方法并且给成员属性赋值
*2017年12月13日
*/
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
// 获取Person类的字节码文件对象
Class c = Class.forName("reflect.Person");
//通过反射获取指定的构造方法getConstructor(Class ...parameterTyps)
Constructor con = c.getConstructor(String.class,int.class,String.class);
//创建构造器的实例对象,来给他指定的字节码文件对象里面的成员变量赋值
Object obj = con.newInstance("韩庚",20,"北京");//实际参数-->通过反射--->Person.class---->getConstructor()
System.out.println(obj);
}
}
3).获取单个的私有构造器并创建实例
在给私有构造器创建实例之前要先调用public void setAccessible(boolean flag):在访问的时候取消java语言访问检查(强制性),参数的值为true则表示反射的对象在使用时应该取消java语言的访问检查。
package reflect;
import java.lang.reflect.Constructor;
/**
*通过反射获取私有构造器并赋值
*
*2017年12月14日
*/
public class ReflectDemo4 {
public static void main(String[] args) throws Exception {
// 获取Person类的字节码文件对象
Class c1 = Class.forName("reflect.Person");
//通过反射获取指定的构造方法getConstructor(Class ...parameterTyps)
Constructor con = c1.getDeclaredConstructor(String.class);
//public void setAccessible(boolean flag):在访问的时候取消java语言访问检查(强制性)
//参数的值为true则表示反射的对象在使用时应该取消java语言的访问检查
con.setAccessible(true);//在给构造器创建实例对象前应该取消检查
//为构造器创建实例对象
Object obj = con.newInstance("韩庚");
System.out.println(obj);
}
}
4.通过反射机制得到某个类的成员变量并使用
1)获取字节码文件对象(forName())
2)获取构造器对象(getConstructor())通过无参构造
3)创建构造器实例(newInstance)
4)通过反射获取单个成员变量并赋值(c.getField("address"))
5)调用set()方法赋值
package reflectfield;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* 通过反射获取成员变量并使用
* 成员变量--->Field
*2017年12月14日
*/
public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 1)通过反射获取字节码文件对象
Class c = Class.forName("reflectfield.Person");
/*//2)获取所有的公共的成员变量public Field[] getFields():所有的公共的可访问的字段,返回的是Field对象数组
Field[] fields = c.getFields();
//Field[] fields = c.getDeclaredFields() ;
for(Field f : fields){
System.out.println(f);
}*/
/**
* 结果:public java.lang.String reflectfield.Person.address
* */
//获取到这个Field对象之前,还获取构造器对象(通过无参构造创建Person类的实例)
Constructor con = c.getConstructor() ;
//创建构造器实例
Object obj = con.newInstance() ;//Person的实例对象
//System.out.println(obj);
//通过反射获取单个成员变量并赋值
Field addressFiled = c.getField("address");
addressFiled.set(obj, "北京");
System.out.println(obj); //Person [name=null, age=0, address=北京]
System.out.println("----------------");
//给name赋值并使用(private)
Field nameField = c.getDeclaredField("name");
//给name属性设置值
nameField.setAccessible(true);
nameField.set(obj, "韩庚");
System.out.println(obj);
System.out.println("----------------");
//给age赋值并使用(default)
Field ageField = c.getDeclaredField("age");
//ageField.setAccessible(true);
ageField.set(obj, 18);
System.out.println(obj);
}
}
5.通过反射得到某个类的成员方法并使用
package reflectmethod;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ReflectDemo {
public static void main(String[] args) throws Exception {
//反射获取字节码文件对象
Class c = Class.forName("reflectmethod.Person");
//获取成员方法(Method)
//public Method[] getMethods():获取当前该字节码文件对象(Person.class)中自己本身以及它父类中所有的公共成员方法
//public Method[] getDeclaredMethods():获取当前字节码文件对象本身所有的成员方法
Method[] methods = c.getDeclaredMethods();
for(Method m : methods){
System.out.println(m);
}
System.out.println("-------------------------");
//获取构造器对象,通过构造器创建当前字节码文件的实例对象
Constructor con = c.getConstructor();
Object obj = con.newInstance(); //Person对象
//获取单个成员方法
Method m1 = c.getMethod("show");
/**
* public Object invoke(Object obj, Object... args)
* 参数1:表示当前针对哪个实例对象进行方法的调用
* 参数2:当前调用该方法的时候里面传递的实际参数
*/
m1.invoke(obj) ;
System.out.println("-------------------------");
//调用getString()方法
Method m2 = c.getMethod("getString", String.class,int.class);
Object objString = (String)m2.invoke(obj, "hello",100) ;
System.out.println(objString);
System.out.println("-------------------------");
//调用私有的fun()
Method m3 = c.getDeclaredMethod("fun");
m3.setAccessible(true);//取消Java语言的访问检查
m3.invoke(obj);
}
}
练习 给ArrayList<Integer>的一个对象,在这个集合中添加一个字符串数据
已经给定了对象,直接用对象代调用getClass()方法获取字节码文件对象,用字节码文件对象调用方法,不用获取构造器并且通过构造器创建当前字节码文件的实例对象
package reflectest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ArrayListTest {
/**
* 给ArrayList<String>的一个对象,我想在这个集合中添加一个字符串数据,如何实现呢?
*
* 反射的应用:
* 1)在集合中应用
* 2)用来反射区读取配置文件来加载里面的内容(MySQL,Oracle等等,SQLServer,MangoDB)
* @param args
* @throws SecurityException
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public static void main(String[] args) throws
NoSuchMethodException,
SecurityException,
IllegalAccessException,
IllegalArgumentException,
InvocationTargetException {
//创建ArrayList对象
ArrayList<String> arry = new ArrayList<String>();
//利用反射类给集合中添加字符串
//获取ArrayList类的字节码文件
Class c = arry.getClass();
//通过反射获取公共访问的方法
Method m = c.getMethod("add", Object.class);
//调用底层invoke(当前字节码文件对象的实例,实际参数)
m.invoke(arry, "hello");
m.invoke(arry, "world");
m.invoke(arry, "java");
System.out.println(arry);
}
}
6.反射的应用-设置配置文件
需要创建一个.txt文件,以键值对的方式写内容
package reflectest;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
public class Test {
public static void main(String[] args) throws
IOException, ClassNotFoundException,
NoSuchMethodException, SecurityException,
InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
//文本文件是一种键值对象的形式
//要将文本文件中的内容加载到属性集合类中
//创建属性集合类对象
Properties prop = new Properties();
FileReader fr = new FileReader("class.txt");
//属性集合类的加载方法:load()
prop.load(fr);
fr.close();
//通过键获得值
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
//将反射应用到配置文件中
Class c = Class.forName(className);
//通过反射获取构造器对象,并通过构造器对象创建该类的实例
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//获取成员方法
Method m = c.getMethod(methodName);
m.invoke(obj);
}
}
package reflectest;
public class Student {
public void show(){
System.out.println("student study!");
}
}
练习:1:写一个方法, public void setProperty(Object obj, String propertyName, Object value){}, 此方法可将obj对象中名为propertyName的属性的值设置为value
package reflectest;
import java.lang.reflect.Field;
/**
* 1:写一个方法,
* public void setProperty(Object obj, String propertyName, Object value){},
* 此方法可将obj对象中名为propertyName的属性的值设置为value
*/
public class Tool {
public void setProperty(Object obj, String propertyName, Object value) throws
NoSuchFieldException, SecurityException,
IllegalArgumentException, IllegalAccessException{
//获取类的字节码文件对象
Class c = obj.getClass();
//获取Field对象
Field field = c.getDeclaredField(propertyName);
防止出现 IllegalAccessException:设置Java语言取消访问检查
field.setAccessible(true);
field.set(obj, value);
}
}
package reflectest;
public class ToolDemo {
public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
//创建Person类对象
Person p = new Person();
// 创建工具类对象
Tool tool = new Tool();
//设置name属性
tool.setProperty(p, "name", "韩庚");
tool.setProperty(p, "age", 18);
System.out.println(p);
}
}
class Person{
private String name;
private int age;
@Override
public String toString() {
return name + "----" + age ;
}
}
二.java的动态代理机制
在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler(interface)、另一个则是Proxy(class),这一个类和接口是实现我们动态代理所必须用到的。下面是javaAPI文档对这两者的描述:
InvocationHandler:
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代我们所代理的那个真实对象
method: 指代的是我们所要调用真实对象的某个方法的Method对象
args: 指代的是调用真实对象某个方法时接受的参数
先了解完Proxy,下面可以通过实例加深了解
Proxy:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException这个方法的作用就是得到一个动态的代理对象,其接收三个参数,
loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
package reflectHander;
public interface UserDao {
//定义四个方法
//增
public abstract void add() ;
//删
public abstract void delete() ;
//update:修改
public abstract void update() ;
//查
public abstract void serach() ;
}
接着,定义一个类来实现这个接口,这个类就是我们真实的对象
package reflectHander;
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("增加功能");
}
@Override
public void delete() {
System.out.println("删除功能");
}
@Override
public void update() {
System.out.println("修改功能");
}
@Override
public void serach() {
System.out.println("查询功能");
}
}
下一步,我们就定义一个动态代理类,前面说过每一个动态代理必须实现
InvocationHandler 这个接口
package reflectHander;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler{
//这个是我们要代理的真实对象
private Object target;
//构造方法:给我们要代理的对象赋初值
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 权限校验
System.out.println("权限校验");
//当代理对象调用真实对象的方法时,其自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object result = method.invoke(target, args) ;
//日志记录
System.out.println("日志记录");
return result; //返回值就是代理对象
}
}
最后来看一下测试类
package reflectHander;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
// 创建用户操作的对象(要代理的真实对象)
UserDao ud = new UserDaoImpl();
// ud.add();
// ud.delete();
// ud.update();
// ud.serach();
System.out.println("--------");
//给ud设置代理对象
//要代理哪个真实对象,就将该对象作为参数传进去,最后是通过该真实对象来调用其方法的
MyInvocationHandler handler = new MyInvocationHandler(ud) ;
UserDao proxy = (UserDao) Proxy.newProxyInstance(ud.getClass().getClassLoader(), ud.getClass().getInterfaces(),handler);
proxy.add();
proxy.delete();
proxy.update();
proxy.serach();
}
}