一文读懂 Java 反射,是时候掌握高效简化代码的方式了

707 阅读7分钟

Java反射机制是指在程序运行时能够访问、检查和修改程序的状态或行为的一种机制。通过反射机制,可以在程序运行时动态地获取类的属性、方法、构造函数等信息,并且可以在运行时动态创建对象、调用方法、获取/设置字段值等。Java反射机制广泛应用于框架开发、数据库连接、动态代理、注解处理等领域。

Java反射机制的优点:
  1. 灵活性高:反射机制让程序员可以动态地获取运行时类的完整信息,支持动态执行方法和创建对象,使得程序具有更强的灵活性。

  2. 动态性强:反射机制使得程序可以在运行时动态地获取类信息并进行操作,而不需要在编译时确定类名和方法名等信息,从而具有更强的动态性。

  3. 接口化:反射机制可以通过使用接口来约束类的实现,从而支持对类的解耦,提高程序的可维护性。

Java反射机制的缺点:
  1. 性能低:反射机制的运行速度要比组件直接调用慢很多,在程序对性能要求较高时,反射机制的应用会影响程序的性能。

  2. 安全问题:由于反射机制可以访问和修改所有类的信息,包括私有属性和方法等,因此在安全性要求较高的应用中使用反射机制可能存在安全问题。

  3. 可读性差:由于反射机制的语法比较繁琐且易错,因此在程序可读性方面存在一定问题,尤其是在代码复杂度较高时。

一、反射机制的原理

Java反射机制是Java语言中动态编程的核心机制之一。它的原理是通过三个类来实现:Class类、Constructor类和Method类。

1. Class类

Class类是Java反射机制的核心类,用于表示一个类的完整定义信息,包括类的名称、属性、方法等。通过Class类,可以获取并控制某个类的所有信息。

获取Class对象的两种方式:

  • 对象.getClass():获取对象所属类对应的Class对象。
  • 类名.class:获取类名对应的Class对象。
String str = "Hello Reflection";
Class<?> clazz1 = str.getClass();

Class<?> clazz2 = String.class;

2. Constructor类

Constructor类用于表示类的构造函数信息,包括构造函数的参数列表、修饰符、异常等。通过Constructor类,可以在运行时动态地创建对象,并调用该对象的构造函数。

获取Constructor对象的两种方式:

  • Class.getConstructor(Class... parameterTypes):获取指定参数类型的public构造器。
  • Class.getDeclaredConstructor(Class... parameterTypes):获取指定参数类型的任意构造器。
// 获取String类的构造方法
Constructor<?>[] constructors = String.class.getConstructors();
for (Constructor<?> constructor : constructors) {
    System.out.println(constructor);
}

// 调用String类的构造方法
Constructor<?> constructor = String.class.getConstructor(StringBuilder.class);
String str = (String) constructor.newInstance(new StringBuilder("Hello Reflection"));

3. Method类

Method类用于表示类的方法信息,包括方法的名称、参数列表、返回值、修饰符等。通过Method类,可以在运行时动态地调用类的方法。

获取Method对象的两种方式:

  • Class.getMethod(String name, Class... parameterTypes):获取指定方法名和参数类型的public方法。
  • Class.getDeclaredMethod(String name, Class... parameterTypes):获取指定方法名和参数类型的任意方法。
// 获取String类的方法
Method[] methods = String.class.getMethods();
for (Method method : methods) {
    System.out.println(method.getName());
}

// 调用String类的方法
Method method = String.class.getMethod("charAt", int.class);
char c = (char) method.invoke("Hello Reflection", 1);

二、获取类信息

通过反射机制,可以在程序运行时获取类的各种信息,包括类的名称、父类、接口、属性、方法、构造函数等。

1. 获取类的名称

Class<?> clazz = String.class;
String name1 = clazz.getName(); // "java.lang.String"
String name2 = clazz.getSimpleName(); // "String"

2. 获取类的父类

Class<?> clazz = String.class;
Class<?> superClass = clazz.getSuperclass(); // java.lang.Object

3. 获取类的接口

Class<?> clazz = ArrayList.class;
Class<?>[] interfaces = clazz.getInterfaces(); // [java.util.List, java.util.RandomAccess, java.lang.Cloneable, java.io.Serializable]

4. 获取类的属性

Class<?> clazz = String.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

5. 获取类的方法

Class<?> clazz = String.class;
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    System.out.println(method.getName());
}

6. 获取类的构造函数

Class<?> clazz = String.class;
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
    System.out.println(constructor);
}

三、创建对象

通过反射机制,可以在程序运行时动态地创建对象。使用Class类的newInstance()方法可以创建一个对象,但是该方法只能调用默认构造函数,因此需要使用Constructor类来显式地调用指定的构造函数来创建对象。

1. 使用Class.newInstance()方法

Class<?> clazz = String.class;
String str = (String) clazz.newInstance();

2. 使用Constructor.newInstance()方法

Class<?> clazz = String.class;
Constructor<?> constructor = clazz.getConstructor(String.class);
String str = (String) constructor.newInstance("Hello Reflection");

四、访问属性

通过反射机制,可以在程序运行时动态地获取和设置类的各种属性值。

1. 获取属性值

Class<?> clazz = String.class;
Field field = clazz.getDeclaredField("value");
field.setAccessible(true);
String str = "Hello Reflection";
char[] value = (char[]) field.get(str);

2. 设置属性值

Class<?> clazz = String.class;
Field field = clazz.getDeclaredField("value");
field.setAccessible(true);
String str = "Hello Reflection";
field.set(str, new char[]{'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'});

五、调用方法

通过反射机制,可以在程序运行时动态地调用类的各种方法。

1. 调用无参方法

Class<?> clazz = String.class;
Method method = clazz.getMethod("length");
String str = "Hello Reflection";
int length = (int) method.invoke(str);

2. 调用有参方法

Class<?> clazz = String.class;
Method method = clazz.getMethod("charAt", int.class);
String str = "Hello Reflection";
char c = (char) method.invoke(str, 1);

六、获取泛型信息

Java中的泛型仅在编译期有效,在运行时会被擦除。但是通过反射机制,可以在运行时获取泛型信息。

1. 获取类的泛型参数

class MyClass<T> {
    private T t;
}
Class<?> clazz = MyClass.class;
TypeVariable<?>[] typeVariables = clazz.getTypeParameters(); // [T]

2. 获取方法的泛型参数

class MyClass {
    public <T> void myMethod(T arg) {}
}
Class<?> clazz = MyClass.class;
Method method = clazz.getMethod("myMethod", Object.class);
TypeVariable<?>[] typeVariables = method.getTypeParameters(); // [T]

3. 获取属性的泛型类型

class MyClass<T> {
    private List<T> list;
}
Class<?> clazz = MyClass.class;
Field field = clazz.getDeclaredField("list");
ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
Type[] types = parameterizedType.getActualTypeArguments(); // [T]

七、动态代理

Java反射机制还可以用于实现动态代理,以满足某些场景下需要代理对象实现特定接口的需求。动态代理是利用反射机制在运行时创建一个实现指定接口的代理对象,该代理对象的方法调用会被转发到InvocationHandler实现类中的invoke方法中,在该方法中进行必要的处理后再调用目标方法。

public interface MyInterface {
    void doSomething();
}

public class MyObject implements MyInterface {
    public void doSomething() {
        System.out.println("do something");
    }
}

public class ProxyHandler implements InvocationHandler {
    private Object target;

    public ProxyHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before");
        Object result = method.invoke(target, args);
        System.out.println("after");
        return result;
    }
}

public class TestProxy {
    public static void main(String[] args) {
        MyObject obj = new MyObject();
        InvocationHandler handler = new ProxyHandler(obj);
        MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
                obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                handler);
        proxy.doSomething();
    }
}

运行TestProxy类,输出:

before do something after

可以看到,在调用代理对象的doSomething()方法时,会先输出"before",再调用目标对象的doSomething()方法,最后输出"after"。这就是动态代理的基本原理。

八、常用框架反射实例

1. Spring注册Bean定义

使用反射机制扫描类路径上的所有类,并将配置文件中配置的Bean定义注入到容器中。

public class BeanDefinitionScanner {
    public void scan(String basePackage, BeanDefinitionRegistry beanDefinitionRegistry) {
        try {
            String path = basePackage.replace(".", "/");
            Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path + "/**/*.class");
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = new CachingMetadataReaderFactory().getMetadataReader(resource);
                    Class<?> clazz = Class.forName(metadataReader.getClassMetadata().getClassName());
                    if (clazz.isAnnotationPresent(Component.class)) {
                        String beanName = Character.toLowerCase(clazz.getSimpleName().charAt(0)) +
                                clazz.getSimpleName().substring(1);
                        beanDefinitionRegistry.registerBeanDefinition(beanName, new RootBeanDefinition(clazz));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2. Spring实例化Bean对象

通过反射机制,Spring框架可以在运行时动态地创建Bean对象。

public class DefaultListableBeanFactory implements BeanFactory {
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    public Object getBean(String name) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        if (beanDefinition == null) {
            throw new NoSuchBeanDefinitionException(name);
        }
        Class<?> clazz = beanDefinition.getBeanClass();
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            throw new BeanInstantiationException(clazz, "Could not instantiate bean class", e);
        }
    }
}

3. Spring执行Bean初始化方法

在Spring框架中,Bean的初始化是通过初始化方法来实现的。

public class DefaultListableBeanFactory implements ConfigurableListableBeanFactory {
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    public void initializeBean(Object existingBean, String beanName) {
        Class<?> clazz = existingBean.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(PostConstruct.class)) {
                try {
                    method.invoke(existingBean);
                } catch (Exception e) {
                    throw new BeanCreationException("Failed to invoke @PostConstruct annotated method: " +
                            method.getName() + " in bean: " + beanName, e);
                }
            }
        }
    }
}

4. Mybatis中SQL语句生成

在Mybatis框架中,SQL语句是通过Mapper接口方法标注的@Select、@Update、@Insert和@Delete等注解来生成的。Mybatis框架使用反射机制来分析Mapper接口及其方法,以生成相应的SQL语句。

@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);

public class SelectSqlSource implements SqlSource {
    private final String script;

    public SelectSqlSource(MetaObject metaObject, Method method, LanguageDriver languageDriver) {
        Select select = method.getAnnotation(Select.class);
        String[] value = select.value();
        script = parseScript(value[0]);
    }

    public BoundSql getBoundSql(Object parameterObject) {
        return new BoundSql(null, script, null, null);
    }

    private String parseScript(String script) {
        if (script.contains("${")) {
            throw new BuilderException("dynamic SQL is not supported for select");
        } else {
            return script;
        }
    }
}