反射的概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
反射的作用:
1、动态地创建类的实例,将类绑定到现有的对象中,或从现有的对象中获取类型。
2、应用程序需要在运行时从某个特定的程序集中载入一个特定的类
反射的原理:
要想理解反射的原理,首先要了解什么是类型信息。Java让我们在运行时识别对象和类的信息,主要有2种方式:一种是传统的RTTI,它假定我们在编译时已经知道了所有的类型信息;另一种是反射机制,它允许我们在运行时发现和使用类的信息。
理解RTTI在Java中的工作原理,首先需要知道类型信息在运行时是如何表示的,这是由Class对象来完成的,它包含了与类有关的信息。Class对象就是用来创建所有“常规”对象的,Java使用Class对象来执行RTTI,即使你正在执行的是类似类型转换这样的操作。
每个类都会产生一个对应的Class对象,也就是保存在.class文件。所有类都是在对其第一次使用时,动态加载到JVM的,当程序创建一个对类的静态成员的引用时,就会加载这个类。Class对象仅在需要的时候才会加载,static初始化是在类加载时进行的。
如果不知道某个对象的确切类型,RTTI可以告诉你,但是有一个前提:这个类型在编译时必须已知,这样才能使用RTTI来识别它。Class类与java.lang.reflect类库一起对反射进行了支持,该类库包含Field、Method和Constructor类,这些类的对象由JVM在启动时创建,用以表示未知类里对应的成员。这样的话就可以使用Contructor创建新的对象,用get()和set()方法获取和修改类中与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()、getMethods()和getConstructors()等许多便利的方法,以返回表示字段、方法、以及构造器对象的数组,这样,对象信息可以在运行时被完全确定下来,而在编译时不需要知道关于类的任何事情。
反射机制并没有什么神奇之处,当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类。因此,那个类的.class对于JVM来说必须是可获取的,要么在本地机器上,要么从网络获取。所以对于RTTI和反射之间的真正区别只在于:
RTTI,编译器在编译时打开和检查.class文件
反射,运行时打开和检查.class文件
反射的使用
反射机制的相关类
与Java反射相关的类如下:
类名 | 用途 |
---|---|
Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 代表类的成员变量(成员变量也称为类的属性) |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
Class类
Class代表类的实体,在运行的Java应用程序中表示类和接口。在这个类中提供了很多有用的方法,这里对他们简单的分类介绍。
- 获得类相关的方法
方法 | 用途 |
---|---|
asSubclass(Class<U> clazz) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) | 根据类名返回类的对象 |
getName() | 获得类的完整路径名字 |
newInstance() | 创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
jdk提供了三种方式获取一个对象的Class,就User user来说
1.user.getClass(),这个是Object类里面的方法
2.User.Class属性,任何的数据类型,基本数据类型或者抽象数据类型,都可以通过这种方式获取类
3.Class.forName(""),Class类提供了这样一个方法,让我们通过类名来获取到对象类
这三种方法用的最多的就是第三种,那么获取到类之后,Class类提供了很多获取类属性,方法,构造方法的api。
//获取整个类
Class c = Class.forName("java.lang.Integer");
//获取所有的属性?
Field[] fs = c.getDeclaredFields();
//定义可变长的字符串,用来存储属性
StringBuffer sb = new StringBuffer();
//通过追加的方法,将每个属性拼接到此字符串中
//最外边的public定义
sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() +"{\n");
//里边的每一个属性
for(Field field:fs){
sb.append("\t");//空格
sb.append(Modifier.toString(field.getModifiers())+" ");//获得属性的修饰符,例如public,static等等
sb.append(field.getType().getSimpleName() + " ");//属性的类型的名字
sb.append(field.getName()+";\n");//属性的名字+回车
}
sb.append("}");
System.out.println(sb);
- 获得类中属性相关的方法
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
获取方法
getDeclaredMethods() 获取所有的方法
getReturnType() 获得方法的放回类型
getParameterTypes() 获得方法的传入参数类型
getDeclaredMethod("方法名",参数类型.class,……) 获得特定的方法
getDeclaredConstructors() 获取所有的构造方法
getDeclaredConstructor(参数类型.class,……) 获取特定的构造方法
getSuperclass() 获取某类的父类
getInterfaces() 获取某类实现的接口
- 获得类中注解相关的方法
方法 | 用途 |
---|---|
getAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
getAnnotations() | 返回该类所有的公有注解对象 |
getDeclaredAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() | 返回该类所有的注解对象 |
- 获得类中构造器相关的方法
方法 | 用途 |
---|---|
getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
- 获得类中方法相关的方法
方法 | 用途 |
---|---|
getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
- 类中其他重要的方法
方法 | 用途 |
---|---|
isAnnotation() | 如果是注解类型则返回true |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果是指定类型注解类型则返回true |
isAnonymousClass() | 如果是匿名类则返回true |
isArray() | 如果是一个数组类则返回true |
isEnum() | 如果是枚举类则返回true |
isInstance(Object obj) | 如果obj是该类的实例则返回true |
isInterface() | 如果是接口类则返回true |
isLocalClass() | 如果是局部类则返回true |
isMemberClass() | 如果是内部类则返回true |
Field类
Field代表类的成员变量(成员变量也称为类的属性)。
方法 | 用途 |
---|---|
equals(Object obj) | 属性与obj相等则返回true |
get(Object obj) | 获得obj中对应的属性值 |
set(Object obj, Object value) | 设置obj中对应属性值 |
//获取类
Class c = Class.forName("User");
//获取id属性
Field idF = c.getDeclaredField("id");
//实例化这个类赋给o
Object o = c.newInstance();
//打破封装
idF.setAccessible(true); //使用反射机制可以打破封装性,导致了java对象的属性不安全。
//给o对象的id属性赋值"110"
idF.set(o, "110"); //set
//get
System.out.println(idF.get(o));
Method类
Method代表类的方法。
方法 | 用途 |
---|---|
invoke(Object obj, Object... args) | 传递object对象及参数调用该对象对应的方法 |
Constructor类
Constructor代表类的构造方法。
方法 | 用途 |
---|---|
newInstance(Object... initargs) | 根据传递的参数创建类的对象 |
例如:String.class.newInstance()
public class Solution {
public static void main(String[] args) throws Exception {
Solution solution = Solution.class.newInstance();
Solution solution2 = solution.getClass().newInstance();
Class solutionClass = Class.forName("Solution");
Solution solution3 = (Solution) solutionClass.newInstance();
System.out.println(solution instanceof Solution); //true
System.out.println(solution2 instanceof Solution); //true
System.out.println(solution3 instanceof Solution); //true
}
}
-
通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,适用于无参和有参构造方法。
例如:String.class.getConstructor(String.class).newInstance(“Hello”);
public class Solution {
private String str;
private int num;
public Solution() {
}
public Solution(String str, int num) {
this.str = str;
this.num = num;
}
public Solution(String str) {
this.str = str;
}
public static void main(String[] args) throws Exception {
Class[] classes = new Class[] { String.class, int.class };
Solution solution = Solution.class.getConstructor(classes).newInstance("hello1", 10);
System.out.println(solution.str); // hello1
Solution solution2 = solution.getClass().getDeclaredConstructor(String.class).newInstance("hello2");
System.out.println(solution2.str); // hello2
Solution solution3 = (Solution) Class.forName("Solution").getConstructor().newInstance(); // 无参也可用getConstructor()
System.out.println(solution3 instanceof Solution); // true
}
}
java动态代理
JAVA动态代理与静态代理相对,静态代理是在编译期就已经确定代理类和真实类的关系,并且生成代理类的。而动态代理是在运行期利用JVM的反射机制生成代理类,这里是直接生成类的字节码,然后通过类加载器载入JAVA虚拟机执行。现在主流的JAVA动态代理技术的实现有两种:一种是JDK自带的,就是我们所说的JDK动态代理,另一种是开源社区的一个开源项目CGLIB(spring中的动态代理)
JDK的动态代理(依赖于接口)
在Java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler接口,另一个是Proxy类。
InvocationHandler接口是给动态代理类实现的,负责处理被代理对象的操作
Proxy类是用来创建动态代理类实例对象的,只有得到这个对象,才能调用需要代理的方法。
动态代理的代理类是在静态代理类上进行修改,将动态代理类实现InvocationHandler接口,重写Invoke方法,Invoke方法通过传入的被代理类方法和参数来执行。
// UserDao.java
public interface UserDao {
void getMoney();
}
// UserDaoImpl.java
@Slf4j
public class UserDaoImpl implements UserDao {
@Override
public void getMoney() {
log.info("******获得大量的美元****");
}
}
// JDKProxy.java
@Slf4j
public class JDKProxy implements InvocationHandler {
private Object object;
public JDKProxy(Object object) {
this.object = object;
}
// 创建代理对象
public Object createProxy() {
Object o = Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
return o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("*********当前时间->{}", DateUtil.format(LocalDateTime.now(),"YYYY-MM-dd:HH:mm:ss") );
method.invoke(object, args);
return null;
}
}
// MybatisApplication.java
public class MybatisApplication {
public static void main(String[] args) throws Exception{
UserDaoImpl userDao = new UserDaoImpl();
UserDao proxy= (UserDao) new JDKProxy(userDao).createProxy();
proxy.getMoney();
cglib动态代理(继承方式)
cglib动态代理中使用MethodInterceptor来实现动态代理类。
拦截器MethodInterceptor中就是由MethodProxy的InvokSuper方法调用代理方法的。
MethodProxy类生成代理方法和代理方法的签名
// CGLIBProxy.java
@Slf4j
public class CGLIBProxy implements MethodInterceptor {
private Object object;
public CGLIBProxy(Object object) {
this.object = object;
}
// 创建代理对象
public Object createProxy() {
Object o = Enhancer.create(object.getClass(), this);
return o;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
log.info("*********当前时间->{}", DateUtil.format(LocalDateTime.now(),"YYYY-MM-dd:HH:mm:ss") );
methodProxy.invoke(object,objects);
return null;
}
}
// 先使用上面有接口的测试用例
public class MybatisApplication {
public static void main(String[] args) throws Exception{
UserDaoImpl userDao = new UserDaoImpl();
UserDao proxy = (UserDao) new CGLIBProxy(userDao).createProxy();
proxy.getMoney();
}
运行结果:
14:49:32.938 [main] INFO com.wyl.mybatis.proxy.CGLIBProxy - *********当前时间->2020-03-10:14:49:32
14:49:32.973 [main] INFO com.wyl.mybatis.dao.impl.UserDaoImpl - ******获得大量的美元****
// 新建UserController.class
@Slf4j
public class UserController {
public void getMoney() {
log.info("**********获得大量的金钱*********");
}
}
// MybatisApplication.java
public class MybatisApplication {
public static void main(String[] args) throws Exception{
UserController userController = new UserController();
UserController proxy = (UserController) new CGLIBProxy(userController).createProxy();
proxy.getMoney();
}