反射概述
- Java反射
- Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。
Java反射理解
- 关于动态语言:
- 动态语言:是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang
- 静态语言:与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++
- Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
- 反射的优缺点
- 优点:在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象
- 缺点:
- 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射
- 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题
- 反射的用途:
- 反编译:.class–>.java
- 通过反射机制访问java对象的属性,方法,构造方法等
- 当我们在使用IDE,比如Ecplise时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射
- 反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象
获取Class类
- 前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高。实例:
Class clazz = String.class;
- 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象。实例:
Class clazz = “String”.getClass();
- 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,会抛出编译时异常ClassNotFoundException。该方法最常用。实例:
Class clazz = Class.forName(“java.lang.String”);
- 类加载器xxxClassLoader.loadClass()传入类路径获取:
ClassLoader cl = this.getClass().getClassLoader(); Class clazz4 = cl.loadClass(“类的全类名”); //通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行
Class常用方法
Class常用方法
boolean isInstance(Object obj)
:判断是否为某个类的实例
通过Class对象创建实例
- 使用Class对象的newInstance()方法来创建Class对象对应类的实例
Class<?> c = String.class; String str = (String)c.newInstance();
- 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建对象,这种方法可以用指定的构造器构造类的实例
//获取String的Class对象 Class<?> c = String.class; //通过Class对象获取指定的Constructor构造器对象 Constructor constructor=c.getConstructor(String.class); //根据构造器创建实例: Object obj = constructor.newInstance(“hello reflection”);
通过Class获取构造器
-
批量获取的构造器:
public Constructor[] getConstructors()
:所有"公有的"构造方法public Constructor[] getDeclaredConstructors()
:获取所有的构造方法(包括私有、受保护、默认、公有)
-
单个构造器获取的方法:
public Constructor getConstructor(Class... parameterTypes)
:获取单个的"公有的"构造方法:public Constructor getDeclaredConstructor(Class... parameterTypes)
:获取"某个构造方法"可以是私有的,或受保护、默认、公有
import java.util.Objects; public class Student { private String name; public int age; private String id; public Student(String name) { System.out.println("String形参构造器"); } public Student(String name,int age) { System.out.println("String、int形参构造器"); } public Student() { System.out.println("空参构造器"); } private Student(char c){ System.out.println("空参char构造器"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && Objects.equals(name, student.name) && Objects.equals(id, student.id); } @Override public int hashCode() { return Objects.hash(name, age, id); } public static void main(String[] args){ for(String s:args){ System.out.println(s); } } }
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class student = Class.forName("com.neu.fanshetest.Student"); //获取所有公共构造器 Constructor[]constructors = student.getConstructors(); for(Constructor c:constructors){ System.out.println(c); } //获取所有构造器 Constructor[]constructors1 = student.getDeclaredConstructors(); for(Constructor c:constructors1){ System.out.println(c); } //得到一个形参为String构造器 Constructor c1 = student.getConstructor(String.class); //调用该构造器造一个Student实例对象 Student student1 = (Student) c1.newInstance("123"); }
通过Class获取属性
- 批量获取属性:
Field[] getFields()
:获取所有的public属性Field[] getDeclaredFields()
:获取所有属性,包括:私有、受保护、默认、公有
- 获取单个属性:
public Field getField(String fieldName)
:获取某个public属性public Field getDeclaredField(String fieldName)
:获取某个字段(可以是私有的)
- 设置字段的值:
- 通过Field类,
public void set(Object obj,Object value)
:参数说明:1.obj:要设置的字段所在的对象;value:要为字段设置的值
Class student = Class.forName("com.neu.fanshetest.Student"); Field[] fields = student.getFields(); for(Field f:fields){ System.out.println(f); } Field[] fields1 = student.getDeclaredFields(); for(Field f:fields1){ System.out.println(f); } //获取公有的,名为age的属性 Field field1 = student.getField("age"); Object o = student.getConstructor().newInstance(); field1.set(o,123); Student student1 = (Student) o; System.out.println(student1.getAge());
- 通过Field类,
通过Class获取方法
-
批量获取方法:
public Method[] getMethods()
:获取所有"公有方法";(包含了父类的方法也包含Object类)public Method[] getDeclaredMethods()
:获取所有的成员方法,包括私有的(不包括继承的)
-
获取单个方法:
public Method getMethod(String name,Class<?>... parameterTypes)
:参数:name : 方法名;Class … : 形参的Class类型对象public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
-
调用方法:通过Method对象,
public Object invoke(Object obj,Object... args)
:obj : 要调用方法的对象;args:调用方式时所传递的实参Method[] methods = student.getMethods(); for(Method m:methods){ System.out.println(m); } Method method = student.getMethod("setId", String.class); Object o = student.getConstructor().newInstance(); //method.setAccessible(true);//私有方法要调用它解除私有限定 method.invoke(o,"123456"); //如果是静态方法,前一个参数可以为null Student student1 = (Student) o; System.out.println(student1.getId()); //反射main方法 Method method1 = student.getMethod("main",String[].class); //main是静态的,可以用null method1.invoke(null,(Object)new String[]{"a","b"});
使用反射越过泛型检查
ArrayList<String> list = new ArrayList<>();
strList.add("AAA");
strList.add("BBB");
//获取ArrayList的Class对象,反向的调用add()方法,添加数据
Class listClass = list.getClass(); //得到 strList 对象的字节码 对象
//获取add()方法
Method method = listClass.getMethod("add", Object.class);
//调用add()方法
method.invoke(list, 10);
//遍历集合
for(Object obj : list){
System.out.println(obj);
}
使用反射读取配置文件
//文件内容
className = com.neu.fanshetest.Student
methodName = getAge
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
//通过配置文件和反射来决定程序的运行
public class Demo {
public static void main(String[] args) throws Exception {
//通过反射获取配置文件中指定类的Class对象
Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student"
//获取配置文件指定方法
Method m = stuClass.getMethod(getValue("methodName"));//show
//调用配置文件指定方法
m.invoke(stuClass.getConstructor().newInstance());
}
//此方法接收一个key,在配置文件中获取相应的value
public static String getValue(String key) throws IOException{
Properties pro = new Properties();//获取配置文件的对象
FileReader in = new FileReader("pro.txt");//获取输入流
pro.load(in);//将流加载到配置文件对象中
in.close();
return pro.getProperty(key);//返回根据key获取的value值
}
}
反射的重要应用:动态代理
-
静态代理:
- 静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
- 从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
- 静态代理实现步骤:
- 定义一个接口及其实现类;
- 创建一个代理类同样实现这个接口
- 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情
//定义发送短信接口 public interface SmsService { String send(String message); }
//实现发送短信接口的类 public class SmsServiceImpl implements SmsService { public String send(String message) { System.out.println("send message:" + message); return message; } }
//创建一个代理发送短信的类,其中被代理的类是它的属性 public class SmsProxy implements SmsService { private final SmsService smsService; public SmsProxy(SmsService smsService) { this.smsService = smsService; } @Override public String send(String message) { //调用方法之前,我们可以添加自己的操作 System.out.println("before method send()"); smsService.send(message); //调用方法之后,我们同样可以添加自己的操作 System.out.println("after method send()"); return null; } }
public class Main { public static void main(String[] args) { SmsService smsService = new SmsServiceImpl(); //被代理的类注入代理类 SmsProxy smsProxy = new SmsProxy(smsService); smsProxy.send("java"); //输出 //before method send() //send message:java //after method send() } }
-
动态代理
- 静态代理有很大的局限性。比如,当我们不仅需要发短信,还有彩信、微信消息,都需要同样的操作时,据需要重写代理方法了。我们希望能够不重写的同时又能满足不同需求,这就需要在运行时确定方法,动态代理正是实现这一需求。
- 介绍两两种动态代理:JDK动态代理、CGLIB 动态代理机制
JDK动态代理
- 步骤:
- 定义一个接口及其实现类;
- 自定义代理类实现
InvocationHandler
接口,并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑; - 通过
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法创建代理对象;
- Proxy类
Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
:参数说明,loader
:类加载器,用于加载代理对象;interfaces
: 被代理类实现的一些接口;h : 实现了InvocationHandler
接口的对象;该方法会创建一个代理对象。loder和interfaces基本就是决定了这个类到底是个怎么样的类。而h决定了这个实例到底是多了什么功能
- InvocationHandler接口:
Object invoke(Object proxy, Method method, Object[] args)
:接口中只有这一个方法,参数说明,proxy
:动态生成的代理类;method
: 与代理类对象调用的方法相对应;args
: 当前 method 方法的参数。通过newProxyInstance创建的对象调用接口方法实际上会调用invoke方法
//短信功能接口
public interface MsService {
void sendMessage();
void sendWeChatMessage();
}
//实现短信功能接口的类,把它可以理解成一个无情的短信发送机器
public class MessageMachine implements MsService{
public void sendMessage(){
System.out.println("Machine send message");
}
public void sendWeChatMessage(){
System.out.println("Machine send WeChat");
}
}
//现在,光发送短信不行,我们还要扩展写功能
//这时就需要代理类了
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ProxyMessage implements InvocationHandler {
private final Object target;
public ProxyMessage(Object target){
this.target = target;
}
public void start(){
System.out.println("proxy start");
}
public void finish(){
System.out.println("proxy stop");
}
//在代理类中实际的操作放到重写的invoke中
//我们在原来光发送信息的基础上加一下操作
public Object invoke(Object proxy, Method method, Object[] args)
throws InvocationTargetException, IllegalAccessException{
start();
Object result = method.invoke(target,args);
finish();
return result;
}
}
//代理类有了,还需要获取代理实例
//这里用一个工厂模式封装,核心是Proxy.newProxyInstance()
import java.lang.reflect.Proxy;
public class MachineFactory {
public static Object getProxy(Object target){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new ProxyMessage(target));
}
}
//然后就可以使用了
public static void main(String[] args) {
MsService m = new MessageMachine();
//这里注意,我们的代理对象实际上是一个接口实例
//因为在被代理后生成的对象,是通过MsService接口的字节码增强方式创建的类而构造出来的。
//它是一个临时构造的该接口实现类(这个实现类为JVM命名,有自己的命名规则,他不是我们定义的)
//的对象,他可转换成形参interfaces中的任意一个接口,根据形参h决定增加的功能
MsService msService = (MsService)MachineFactory.getProxy(m);
//我们调用方法实际上调用的时在代理类中重写的invoke()方法
msService.sendMessage();
msService.sendWeChatMessage();
//proxy start
//Machine send message
//proxy stop
//proxy start
//Machine send WeChat
//proxy stop
}
CGLIB 动态代理机制
- 介绍:
- JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类
- CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理
- 在 CGLIB 动态代理机制中
MethodInterceptor
接口和Enhancer
类是核心。
- 步骤:
- 定义一个类;
- 自定义MethodInterceptor并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
- 通过 Enhancer 类的 create()创建代理类;
- Enhancer类似于Proxy,用于创建一个代理实例
- MethodInterceptor:
- MethodInterceptor类似于InvocationHandler
Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy)
:参数说明,obj
:被代理的对象(需要增强的对象);method
:被拦截的方法(需要增强的方法);args
:方法需要的参数;proxy
:用于调用原始方法MethodProxy类对象。似于invoke方法,通过Enhancer创建的代理实例调用被代理类的方法会调用intercept方法
//CGLIB(Code Generation Library) 实际是属于一个开源项目,需要手动引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
//定义一个需要被代理的类,它有发送功能
public class AliSmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//自定义一个类实现MethodInterceptor接口
public class DebugMethodInterceptor implements MethodInterceptor {
/**
* @param o 被代理的对象(需要增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}
}
import net.sf.cglib.proxy.Enhancer;
//有了代理类,现在我们要获取一个代理实例
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}
//所有定义完成就可以使用了
//这里的类型实际上时系统定义的一个被代理的子类,但它可以转为被代理类
AliSmsService aliSmsService = (AliSmsService)
CglibProxyFactory.getProxy(AliSmsService.class);
//我们实际上调用的是intercept方法
aliSmsService.send("java");
JDK 动态代理和 CGLIB 动态代理对比
- JDK 动态代理只能只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
- 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
静态代理和动态代理的对比
- 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
- JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。