Java反射学习笔记(包含详细例子,以及实际用法)
此篇文章比较适合在社会上的Java初学者,非常适合各个高校的学生进行学习,用最简单的例子,用最朴素的代码展现方式来教会大家。
另外,欢迎大家在评论区交流,指正我描述的问题,大家共同进步,共同学习!
一、Java通过反射创建对象的两种方式:
首先需要了解创建Class对象的三种方式:
public class Test{
String ClassName = "com.example.Test";
//方法一:
Class <?> clazz = ClassName.class;
//方法二:
Class <?> clazz = Class.forName(ClassName);
//方法三:
Test test = new Test();
Class <?> clazz = test.getClass();
}
1、使用Class对象的newInstance方法创建对象
public class Test{
private String name;
public static void main(String[] args){
String selectHandlerClassName = "com.example.Test";
Class <?> handlerClass = Class.forName(selectHandlerClassName);
Object testclass = handlerClass.newInstance();//newInstance无参构造,默认创建的是空对象。
}
}
2、使用Constructor对象的newInstance方法创建对象
public class Test{
private String name;
public Test(String name){
this.name = name;
}
public static void main(String[] args){
String selectHandlerClassName = "com.example.Test";
Class <?> handlerClass = Class.forName(selectHandlerClassName);
//newInstance有参数的情况,getDeclaredConstructor(Stirng.class)在寻找一个含有String类型的构造方法,并调用newInstance方法传入name参数hello。
Object testclass = handlerClass.getDeclaredConstructor(String.class).newInstance("hello");
//另外,在获取构造函数时,getDeclaredConstructor()可以获取所有构造方法,无视权限。但getConstructors()方法只能获取到public类型的构造方法。
}
}
二、反射在Java中的作用:
首先要了解在Java中什么是反射,反射究竟是用来做什么的。
反射在Java中可以说是非常强大,非常厉害。具体体现在,对任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意一个方法和属性。
大家可要注意,Java中有4种访问权限(public、protected、private、default)。public类型就不用多说了,可以在任意的类中被调用。protected类型只能在当前类、子类、同一个包下的类中被调用。private类型只允许在声明该类的内部进行访问。default类型只能在同一个包中的类被调用。而这些只需要setAccessible(true)方法用来无视权限。再搭配反射的机制就可以随意的获取被修饰的方法或者属性。
这个时候肯定会有人说,那这样不就会破坏程序的安全性以及封装性了么。这是必然的,所以在使用时请一定要谨慎。在必要的情况在再使用setAccessible(true)。
举个简单的例子(同时这个例子也是利用反射来获取类的属性的例子):
public class Test{
private String name;
public Test(String name){
this.name = name;
}
}
import java.lang.reflect.Field;
public class Main{
public static void main(String[] args) throws Exception{
Test test = new Test();
Class<?> clazz = test.getClass();
Field field = clazz.getDeclaredField("name");//获取name字段,表示为Field对象
field.setAccessible(true);//暴力破解
String name = (String) field.get(test);
System.out.println("姓名:" + name);
}
}
那么再说说实际的用处:
1、用来获取和修改类的字段:
import lombok.Getter;
public class Test{
@Getter
private String name;
public Test(String name){
this.name = name;
}
}
import java.lang.reflect.Field;
public class Main{
public static void main(String[] args) throws Exception{
Test test = new Test("zhouchi");
Class<?> clazz = test.getClass();
Field field = clazz.getDeclaredField("name");//获取name字段,表示为Field对象
field.setAccessible(true);//暴力破解
String name = (String) field.get(test);//获取字段的实际值
System.out.println("获取姓名:" + name);
String name = (String) field.set(test,"cuiqiuyue");//修改字段的值
System.out.println("修改后的姓名:" + name);
}
}
2、用来获取类的详细信息:
获取类的名称:
public class Test{
String ClassName = "com.example.Test";
Class<?> clazz = ClassName.class;
String classname = clazz.getName();
System.out.println("获取到的类的名称" + classname);
}
获取父类的名称:
public class Fathertest{
//....
}
public class Test extends Fathertest{
String ClassName = "com.example.Test";
Class<?> clazz = ClassName.getSuperclass();//获取父类的Class对象
String classname = clazz.getName();
System.out.println("获取到的父类的名称" + classname);
}
获取接口列表:
interface TestInterface1{
//....
}
interface TestInterface2{
//....
}
public class Test implements TestInterface1 implements TestInterface2{
String ClassName = "com.example.Test";
Class<?> clazz = ClassName.class;
Class<?>[] interfaces = clazz.getInterfaces();//获取接口对象
for(Class<?> inface : interfaces){
System.out.println("接口名称:" + inface.getName());
}
}
获取一个类中的所有字段信息:
import lombok.Getter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@Getter
public class Test{
private String name;
private String age;
public Test(String name){
this.name = name;
}
public Test(String age){
this.age = age;
}
}
public class Main(){
public static void main(String[] args){
String ClassName = "com.example.Test";
Class<?> clazz = ClassName.class;
Field[] fields = clazz.getDeclaredFields();
for(Field field : fields){
setAccessible(true);//要放在循环内部
System.out.println("字段名:" + field.getName());
System.out.println("字段类型:" + field.getType().getName());
//在Java反射机制中,字段的修饰符是以整数类型来表示的。当调用getModifiers()方法后,会返回一个int类型的值。
//public(1)、private(2)、protected(4)、static(8)、final(16)、synchronized(32)和volatile(64)
int modifiers = field.getModifiers();
System.out.println("字段修饰符:" + Modifier.toString(modifiers));
}
}
}
获取方法的信息(包含方法名称、参数类型、修饰符、返回类型)
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class Test {
private String name;
private String age;
public Test(String name) {
this.name = name;
}
public Test(String age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public void displayInfo() {
System.out.println("姓名:" + name);
System.out.println("年龄:" + age);
}
public static void main(String[] args) {
String className = "com.example.Test";
Class<?> clazz = Class.forName(className);
Method[] methods = clazz.getDeclaredMethods();//获取类中方法的对象列表
for (Method method : methods) {
System.out.println("方法名称:" + method.getName());
System.out.println("返回类型:" + method.getReturnType().getName());
Class<?>[] parameterTypes = method.getParameterTypes();//获取方法中的参数列表对象
for (Class<?> paramType : parameterTypes) {
System.out.println("参数类型:" + paramType.getName());
}
int modifiers = method.getModifiers();
System.out.println("修饰符:" + Modifier.toString(modifiers));
}
}
}
三、反射在实际场景中的使用(各个高校的学生可以暂时不看这一段)
-
例如大名鼎鼎的ORM框架:Mybatis-Plus框架就是利用反射来进行对数据库的操作。
-
还有像Java反序列化也涉及到了反射。
-
在JUnit中标注@Test会使用反射机制。先获取测试类的Class对象,然后通过Class对象的getMethods()方法获取到该类中所有的public方法,然后扫描所有获取到的方法上是否标注了@Test注解,如果有的话会将这些有标注的方法放在一个执行集合中,最后按照一定顺序来执行各个方法。
-
Spring中@Autowried注解来进行依赖注入也是反射机制。Spring使用了JAVA中的反射API,来获取被注入的属性或方法的Class对象,然后通过Class对象的getDeclaredFields()或getDeclaredMethods()方法获取到该类中所有的字段或方法。然后步骤就与上面的JUnit一样了,Spring遍历这些字段或方法,并检查每个字段或方法上是否有@Autowired注解。如果有,则将该字段或方法标记为需要注入的依赖项。最后,Spring会根据这些依赖项的类型信息来查找匹配的Bean实例,并将其注入到目标对象中。
-
Spring Boot中@SpringBootApplication注解也使用了反射,
-
@SpringBootApplication注解包含了三个注解,分别是:
@Configuration:这个注解主要是用于定义配置类。Spring会通过反射机制来扫描项目中所有带着@Configuration注解的类,交给Spring容器管理,并注册为Bean。
@EnableAutoConfiguration:这个注解用于开启自动配置功能,它会根据类路径下的配置属性和依赖关系来自动配置应用程序。在运行时,Spring Boot会使用反射机制来查找并加载所有带有@Configuration
和@EnableAutoConfiguration
注解的类加粗样式,并根据这些类中定义的自动配置逻辑来创建相应的Bean。@ComponentScan:这个注解用于指定组件扫描的基础包或类,它告诉Spring在指定的包或类中查找带有特定注解(如
@Component
、@Service
等)的类,并将这些类注册到容器中。在运行时,Spring会使用反射机制来扫描指定的包或类,并根据其中的注解信息来创建相应的Bean。 -
在动态加载类的场景下,如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyDemo {
public static void main(String[] args) {
// 创建一个目标对象
ITarget target = new TargetImpl();
// 创建一个代理工厂
InvocationHandler handler = new MyInvocationHandler(target);
// 使用反射和代理工厂创建代理对象
ITarget proxy = (ITarget) Proxy.newProxyInstance(
target.getClass().getClassLoader(),//获取目标对象的类加载器
target.getClass().getInterfaces(),//获取目标对象实现的所有接口
handler);//一个实现了InvocationHandler接口的对象,用于处理代理对象的方法调用。
// 调用代理对象的方法
proxy.doSomething();
}
}
interface ITarget {
void doSomething();
}
class TargetImpl implements ITarget {
@Override
public void doSomething() {
System.out.println("目标对象执行操作");
}
}
class MyInvocationHandler implements InvocationHandler {
private final ITarget target;
public MyInvocationHandler(ITarget target) {
this.target = target;
}
//invoke()方法一定会被执行,
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理对象执行前的操作");
Object result = method.invoke(target, args);
System.out.println("代理对象执行后的操作");
return result;
}
}