c——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
反射
类的加载及类加载器
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三个步骤来实现对类进行初始化
加载:
将class文件读入内存,并为之建立一个Class对象
任何类被使用时,系统都会建立一个Class对象
连接:
验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始值
解析 将类的二进制数据中的符号引用替换为直接引用
初始化:按顺序进行初始化
类的加载时机:
- 创建类的实例时
- 访问类的静态变量或为其赋值时
- 调用类的静态方法时
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象时
- 初始化某个类的子类时
- 直接使用java.exe命令来运行某个主类时
类加载器:负责将class文件加载到内存中,并为之生成对应的Class对象
类加载器的组成:
Bootstrap ClassLoader 根类加载器(负责核心类的加载)
Extension ClassLoader 扩展类加载器(负责扩展目录下的jar包加载)
System ClassLoader 系统类加载器(负责在JVM启动时,加载来自java命令的class文件和classpath环境变量指定的jar包and类路径)
反射
在运行状态中对任意类都能知道该类的所有属性和方法,对于任意对象,都能调用他的任意方法和属性.这种功能就叫反射
实现方式:先要获取该类的字节码文件对象,然后通过它去使用成员变量,构造方法,成员方法
获取class文件对象方法:
- Object类中的getClass方法
- 数据类型的静态属性class
- Class类中的静态方法 Class forName(String className) 这里要写类的全路径
实际开发中常用方法C
反射的使用
直接上代码+注释
package javareflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 代码演示用需要用到的Person类
*/
public class Person {
//注意这里的修饰符
private String name;
int age;
public String address;
//注意这里没有public
Person(String name, int age) {
this.name = name;
this.age = age;
}
private Person(String name) {
this.name = name;
}
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public Person() {
}
public void show(){
System.out.println("show");
}
public void method(String s){
System.out.println("method"+s);
}
public String getString(String s,int i){
return s+":"+i;
}
private void function(){
System.out.println("function");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
/**
* Created by mo on 15/11/21.
* 反射:通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法
*
* 首先要先得到class文件对象即Class类对象
* Class类:
* 成员变量 Field
* 构造方法 Constructor
* 成员方法 Method
* 再通过上面三个类的对象来调用
*
* 获取class文件对象方法:
* A:Object类中的getClass方法
* B:数据类型的静态属性class
* C:Class类中的静态方法 Class forName(String className) 这里要写类的全路径 会抛出ClassNotFoundException
* 实际开发中常用方法C
*/
public class ReflectDemo {
public static void main(String[] args) throws Exception {
//方法A Object类中的getClass方法
Person p = new Person();
Class c = p.getClass();//获取class文件对象
// Person p2 = new Person();
// Class c2 = p2.getClass();
//
// System.out.println(p == p2); //false 对象是不同的
// System.out.println(c == c2); //true 字节码是一样的
//方法B 数据类型的静态属性class
Class c3 = Person.class;
//方法C Class类中的静态方法 forName(这里要传入类的全路径)
// Class c4 = Class.forName("Person"); //报错
Class c4 = Class.forName("javareflect.Person");//我这里Person在javareflect包下
//通过反射获取构造方法并使用
Constructor[] constructor = c4.getConstructors(); //获取公共构造方法
//遍历
for (Constructor cons:constructor){
System.out.println(cons);
}
/*
输出结果如下
public javareflect.Person()
public javareflect.Person(java.lang.String,int,java.lang.String)
为什么只输出了2个构造?因为未获取到的那个构造没有用public修饰
*/
System.out.println("----------------------");
//获取所有构造方法,包括私有
Constructor[] allConstructor = c4.getDeclaredConstructors();
for (Constructor cons:allConstructor){
System.out.println(cons);
}
// 输出结果比上面多了两行: javareflect.Person(java.lang.String,int) private javareflect.Person(java.lang.String)
System.out.println("----------------------");
//获取单个构造 getConstructor(Class<?>... parameterTypes)
//参数表示的是你要获取的构造方法的参数个数及数据类型的class字节码文件对象
//获取无参构造
Constructor constructor1 = c4.getConstructor();
//获取带参构造 参数列表不能错,参数顺序也不能错
Constructor constructor2 = c4.getConstructor(String.class,int.class,String.class);
//通过Constructor对象创建Person类的实例
Object person1 = constructor2.newInstance("mo",18,"北京");
System.out.println(person1);
//获取私有构造方法并使用
Constructor constructor3 = c4.getDeclaredConstructor(String.class);
constructor3.setAccessible(true);//值为true表示反射的对象在使用时取消Java语言访问检查
Object person2 = constructor3.newInstance("xi");
System.out.println(person2);
System.out.println("----------------------");
//通过反射获取成员变量并使用
//获取所有成员变量
Field[] fields = c4.getDeclaredFields(); //获取公共的成员变量用getFields()方法
for (Field x : fields){
System.out.println(x);
}
System.out.println("----------------------");
//获取单个成员变量 并赋值
Field nameField = c4.getDeclaredField("name");//公共成员变量用getField
nameField.setAccessible(true);
//赋值 String类型用set(Object obj,Object value) 给obj对象的name变量赋值为value
//其他类型用setLong() setInt()等等
Object p1 = constructor1.newInstance();//先创建Obj对象,通过之前获取的无参构造
nameField.set(p1,"xixi");
System.out.println(p1);
System.out.println("----------------------");
//通过反射获取成员方法并使用
Method[] methods = c4.getDeclaredMethods(); //getMethods()会获取到自己和父类的公共方法 getDeclaredMethods()只获取自己的方法
for (Method m:methods){
System.out.println(m);
}
System.out.println("----------------------");
//获取单个方法 Method getMethod(String name,Class<?>... parameterTypes) 参数name用于指定所需方法的简称
Method methodShow = c4.getDeclaredMethod("show");
//Object invoke(Object obj,Object... args) 调用方法 返回值Object obj-调用方法的对象 args-方法调用的参数
methodShow.invoke(p1);
System.out.println("----------------------");
Method methodFunc = c4.getDeclaredMethod("function");
methodFunc.setAccessible(true);
methodFunc.invoke(p1);
System.out.println("----------------------");
Method methodGet = c4.getDeclaredMethod("getString",String.class,int.class);
Object objStr = methodGet.invoke(p1,"abc",1); //注意这里是拿Object接收,因为返回值是Object
System.out.println(objStr);
System.out.println("----------------------");
}
}
反射案例
案例1:通过反射运行配置文件的内容
import java.io.FileReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* Created by mo on 15/11/22.
* 反射应用
* 通过配置文件运行类中的方法
* 需要配置文件配合使用 这里用settings.txt
* 里面有两个键className methodName
*
* 需要解决的问题:日常中经常有对类进行改动,如果不配置文件,每改一次代码就大规模改一次
*/
public class ReflectTest {
public static void main(String[] args) throws Exception {
//加载配置文件中的键值对
Properties prop = new Properties();
FileReader fr = new FileReader("/Users/mo/IdeaProjects/hello world/JavaPractice/src/javareflect/settings.txt");
prop.load(fr);
fr.close();
//获取数据
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
//反射
Class c = Class.forName(className);
Constructor cons = c.getDeclaredConstructor();
Object obj = cons.newInstance();//创建对象
Method m = c.getDeclaredMethod(methodName);
m.invoke(obj);
/*
这样 需要用哪个类哪个方法 就在配置文件里修改就行
*/
}
}
案例2:通过反射越过泛型检查
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
/**
* Created by mo on 15/11/22.
* 反射越过泛型检查
*
* 往一个已知的ArrayList<Integer> 对象中添加一个字符串
* 注意:泛型只是在编译的时候检查,在底层原码中仍然还是默认Object类型
*/
public class ReflectTest2 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
ArrayList<Integer> arr = new ArrayList<>();
Class c = arr.getClass();
Method m = c.getMethod("add",Object.class);
m.invoke(arr,"hello");//调集合的add方法
System.out.println(arr);
}
}
案例3:通过反射给任意的一个对象的任意的属性赋值为指定的值
import java.lang.reflect.Field;
/**
* Created by mo on 15/11/22.
*
* 通过反射写一个通用的设置某个对象的某个属性为指定值
*/
public class ReflectTest3 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
//测试
Person obj = new Person("mo",22,"bj");
System.out.println(obj);
setProperty(obj,"name","xi");
System.out.println(obj);
}
public static void setProperty(Object obj, String propertyName, Object value) throws NoSuchFieldException, IllegalAccessException {
//获取字节码文件
Class c = obj.getClass();
//获取变量
Field filed = c.getDeclaredField(propertyName);
//取消访问检查
filed.setAccessible(true);
//设置值
filed.set(obj,value);
}
}
动态代理
动态代理:通过反射生成一个代理
Java中提供了java.lang.reflect.Proxy类和InvocationHandler接口来生成动态代理对象,但只能对接口做代理.日常开发中用更强大的代理框架如cglib等
代码演示:
package javareflect.Test4;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created by mo on 15/11/22.
*/
public class UserDaoDemo {
public static void main(String[] args) {
UserDao ud = new UserDaoImpl();
ud.add();
ud.del();
ud.update();
ud.find();
System.out.println("------------------------------");
//这里方法可以随便调用,事实上,在每个操作执行前应该查看该用户是否有权限进行此操作,并且在操作后留下记录
//不修改以前写好的代码 防止出错,所以新创建个实现类,然后增加功能
UserDao ud2 = new UserDaoImpl2();
ud2.add();
ud2.del();
ud2.update();
ud2.find();
System.out.println("------------------------------");
/*
此时如果又有一个相似的类,也需要权限校验和日志记录功能,难道我又要重写一个实现类吗?
明显,我们应该把权限校验和日志记录功能提取出来,单独做成一个中介,那么这里就要用到动态代理的知识点
Proxy类中的这个方法可以创建动态代理对象:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
InvocationHandler里有一个方法需要实现:
Object invoke(Object proxy,Method method,Object[] args)
要先实现InvocationHandler接口
*/
//对ud对象做动态代理
myInvocationHandler myHandler = new myInvocationHandler(ud);
UserDao proxyUserDao = (UserDao) Proxy.newProxyInstance(ud.getClass().getClassLoader(), ud.getClass().getInterfaces(),myHandler);//生成代理并强转为UserDao类型
proxyUserDao.add();
proxyUserDao.del();
proxyUserDao.find();
proxyUserDao.update();
System.out.println("------------------------------");
//这样,如果别的类也需要这俩方法,只需在这里把ud对象换为该类对象即可
}
}
/**
* 这是做动态代理前要做的InvocationHandler接口实现类
* 在接口的invoke方法里实现新增的功能,并通过传入该方法的Method对象调用目标对象的方法
*/
class myInvocationHandler implements InvocationHandler{
private Object target;//对目标对象target做代理
public myInvocationHandler(Object obj){
target = obj;
}
/*
proxy - 在其上调用方法的代理实例
method - 对应于在代理实例上调用的接口方法的 Method 实例。
args - 包含传入代理实例上方法调用的参数值的对象数组。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("权限校验");
//通过反射Method调用方法,传入调用方法的对象和参数
Object result = method.invoke(target,args);//这是实际上就表示add del update find方法
System.out.println("日志记录");
return result;//返回的是代理对象,这个对象就具有了权限校验和日志记录功能
}
}
/**
* 这是通过创建新的类实现 权限校验和日志记录 功能
*/
class UserDaoImpl2 implements UserDao{
@Override
public void add() {
System.out.println("权限校验");
System.out.println("增加");
System.out.println("日志记录");
}
@Override
public void del() {
System.out.println("权限校验");
System.out.println("删除");
System.out.println("日志记录");
}
@Override
public void update() {
System.out.println("权限校验");
System.out.println("修改");
System.out.println("日志记录");
}
@Override
public void find() {
System.out.println("权限校验");
System.out.println("查询");
System.out.println("日志记录");
}
}
/**
* Created by mo on 15/11/22.
* 用户操作接口
*/
public interface UserDao {
void add();
void del();
void update();
void find();
}
/**
* Created by mo on 15/11/22.
* 用户操作实现类
*/
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("增加");
}
@Override
public void del() {
System.out.println("删除");
}
@Override
public void update() {
System.out.println("修改");
}
@Override
public void find() {
System.out.println("查询");
}
}