Java基础之反射

反射是java中比较重要的一块知识,虽然平时的开发中可能不太用到反射,但是在框架的开发中,反射是非常重要的,而且各个框架中大量使用的注解,在解析注解的时候也需要用到反射,所以说反射是框架的灵魂。说了这么多,其实反射做的事情很简单,就是在运行时获取类或者对象的信息。

1.获取class对象

  我们将.java结尾的文件称为源文件,源文件最终会被编译。class文件,也就是字节码文件,反射的作用就是操作字节码文件来获取类和对象的信息。

  为了能够利用反射获取想要的信息,首先要做的就是获取类对象,一般通过以下三种方式来获取:

  1. Class.forName(“类的全称”)
  2. 类名.class
  3. 对象.getClass()

*通常情况下建议使用第二种方式来获取class对象,与另外两种方式比较,他有两个优势:

(1)代码更安全,在编译阶段就可以确认class是否存在;

(2)性能更好,无需调用方法;

很多情况下也通过第一种方式来获取class对象,比如通过读取配置文件的类路径来读取对象。

 

2.获取class中的各个组成部分

获取class对象,只是为了方便我们能够拿到这个类中各个组成部分的信息,在了解这些具体方法之前,我们先来了解下一个类的组成,看下具体有哪些组成部分:

如上图,通常一个类可能的组成部分有这些,还有一些比如内部类,静态代码块之类的没有列在其中。

 

接下来,我们就通过反射一一获取:

  • 包名

getPackage()

System.out.println(dog.getPackage().toString());

  • 类名

getName()

System.out.println(dog.getName());

  • 父类

getSuperClass()

System.out.println(dog.getSuperclass());

  • 接口

getInterfaces()

λ	for(Class inter : dog.getInterfaces()){
    System.out.println(inter);
}

  • 成员变量

获取成员变量时可以注意到有两个方法提供:getFields()getDeclaredFields(),我们执行下看看区别:

public class TestDogReflection {
    public static void main(String[] args) {
        Class dog = Dog.class;


        for (Field field:dog.getFields()){
            System.out.println(field);
        }

        System.out.println("=========");

        for (Field field:dog.getDeclaredFields()){
            System.out.println(field);
        }

    }
}

使用getFields()方法时,父类和当前类中的成员变量都被打印出来了,但是被private修饰符修饰的变量没有输出,而使用getDeclaredFields()方法则是输出了当前类的所有成员变量

  • 构造器

和获取成员变量类似,获取构造器同样提供了getConstructors()getDeclaredConstructors()两个方法:

public static void main(String[] args) {
    Class dog = Dog.class;



    for (Constructor constructor:dog.getConstructors()){
        System.out.println(constructor);
    }

    System.out.println("=========");

    for (Constructor constructor:dog.getDeclaredConstructors()){
        System.out.println(constructor);
    }

}

需要获取执行构造器的时候,只需要将参数类型传进去即可:

public static void main(String[] args) throws NoSuchMethodException {
    Class dog = Dog.class;

    Constructor constructor =  dog.getDeclaredConstructor(String.class,int.class);

    System.out.println(constructor);

}

  • 方法

获取方法同样也会有getMethods()getDeclaresMethods():

public static void main(String[] args) throws NoSuchMethodException {
    Class dog = Dog.class;

    for(Method method : dog.getMethods()){
        System.out.println(method);
    }

    System.out.println("============");

    for(Method method : dog.getDeclaredMethods()){
        System.out.println(method);
    }
}

 可以看到,使用getMethods()方法,出了当前类的公共方法以外,所有的父类包括Object中的方法也都被打印出来了。

 

通过方法名和具体参数类型,可以获取指定方法:

public static void main(String[] args) throws NoSuchMethodException {
    Class dog = Dog.class;

    Method method = dog.getDeclaredMethod("makeNoise");
    System.out.println(method);
}

  • 注解

getAnnotations()可以获取方法或者类上的使用的注解

Method method = dog.getDeclaredMethod("run");
for (Annotation annotation : method.getAnnotations()){
    System.out.println(annotation);
}

  • 修饰符
public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
    Class dog = Dog.class;
    Field field = dog.getDeclaredField("age");
    int mod = field.getModifiers();
    System.out.println(Modifier.toString(mod));
}

3.通过反射操作对象

上面简单介绍了获取class对象的内容,接下来我们操作clss对象,主要就是实例化对象,调用对象的方法。

 

还是直接看例子吧:

通过newInstance()方法调用无参构造方法,需要调用有参的只需要加入参数即可:

Class clazz = Class.forName("com.ljw.reflection.Dog");
Dog dog = (Dog) clazz.newInstance();


Method method = clazz.getMethod("run");

method.invoke(dog);

通过方法名可以直接获取到指定方法,通过method.invoke(对象,参数列表)方法,可以执行方法,需要注意的时,这样操作需要遵守访问修饰符的权限,如果需要调用私有方法,需要设计访问权限:

Class clazz = Class.forName("com.ljw.reflection.Dog");
Dog dog = (Dog) clazz.newInstance();

Method method = clazz.getDeclaredMethod("fly",int.class);

method.setAccessible(true);
method.invoke(dog,5);

同样的,改变私有成员变量,也需要对field设置访问权限,从这点上来讲,反射在一定程度上破坏了java的封装性。

 

4.代理

除了以上,利用反射还可以生成jdk动态代理。(有关代理模式具体内容参考设计模式这篇博客)。

      java开发的基本原则就是开闭原则,对修改封闭,对扩展开放。代理类做的工作就是如此,jdk动态代理只能代理有接口的类,只不过他是通过实现InvocationHandler接口来实现对目标的代理.我们一起开看下代码:

 java中提供了一个Proxy代理类,在java.lang.reflect包下,可以看下这个类的说明:

通过提供的静态方法对接口实现代理,而且官方还给出了示例代码:

* <pre>
*     InvocationHandler handler = new MyInvocationHandler(...);
*     Class proxyClass = Proxy.getProxyClass(
*         Foo.class.getClassLoader(), new Class[] { Foo.class });
*     Foo f = (Foo) proxyClass.
*         getConstructor(new Class[] { InvocationHandler.class }).
*         newInstance(new Object[] { handler });
* </pre>
* or more simply:
* <pre>
*     Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
*                                          new Class[] { Foo.class },
*                                          handler);
* </pre>

可以看到,想要实现jdk动态代理,只需要实现InvocationHandler接口,再调用Proxy的newProxyInstance方法对具体的接口方法进行代理即可。

 我们看一个具体例子:

接口类:

package com.ljw.reflection;

/**
 * Created by liujiawei on 2018/8/1.
 */
public interface AnimalAction {

    public void run();
}

实现类:

package com.ljw.reflection;

/**
 * Created by liujiawei on 2018/7/26.
 */
public class Dog implements AnimalAction {


    @Override
    public void run() {
        System.out.println("dog run");
    }
}

代理类:

package com.ljw.reflection;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Created by liujiawei on 2018/8/1.
 */
public class DynamicProxy implements InvocationHandler {


    private Object target; //代理类

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

    //重写invode实现代理工作  aop
    @Override
    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 null;
    }


    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
    }


}

测试类:

package com.ljw.reflection;

/**
 * Created by liujiawei on 2018/8/1.
 */
public class TestDogActionByDynamicProxy {

    public static void main(String[] args) {

        Dog dog = new Dog();

        DynamicProxy invocationHandler = new DynamicProxy(dog);

        AnimalAction proxy = (AnimalAction) invocationHandler.getProxy();

        proxy.run();


    }
}

可以看到代理类起到的作用,在没有修改对象的情况下,增加了部分实现,通过重新InvocationHandler的invoke()方法完成代理工作,通过Proxy类的newProxyInstance指定目标类完成整个动态代理工作,jdk动态代理只针对有接口的类,没有接口的类实现代理需要通过cglib生成代理。

 

<think>嗯,用户之前问过关于`int``integer`的区别,现在转向Java基础中的反射。可能用户正在学习Java,之前的问题涉及基本数据类型包装类,现在深入到了更高级的主题。我需要先确定用户对Java基础掌握程度,反射通常是面向对象之后的内容,所以用户可能已经有一定的Java基础。 用户的需求是了解Java反射基础知识,可能需要知道反射是什么、为什么用它、以及如何使用。深层需求可能包括实际应用场景或常见问题,比如性能影响或者安全性考虑。需要结构清晰,分点解释,帮助用户逐步理解。 首先,反射的定义,必须用简单明了的语言,避免术语过多。然后核心类,比如Class、Constructor、Method、Field,这些是反射的关键类,需要举例说明。应用场景部分,用户可能想知道反射的实际用途,比如框架开发或动态加载,要举常见的例子如Spring的IoC。 接下来要讲实现步骤,获取Class对象的三种方式,实例化对象的方法,访问字段调用方法。每个步骤都需要代码示例,但用户可能对代码的细节不太清楚,所以需要解释每个步骤的作用。例如,`Class.forName`可能会抛出异常,需要提及异常处理,但可能不需要深入,保持基础知识的介绍。 优缺点部分需要平衡,用户可能想知道为什么有时候不推荐使用反射,所以性能安全问题是重点。最后,实际案例,比如动态加载类,或者简单的框架应用,帮助用户将理论联系实际。 还要注意用户可能存在的误区,比如滥用反射导致代码难以维护,或者性能问题。需要提醒在必要时使用,并遵循最佳实践。此外,检查是否需要提到Java版本的变化对反射的影响,比如模块化系统带来的访问限制,但可能用户刚入门,暂时不需要深入。 最后总结,强调反射的灵活性代价,帮助用户权衡使用场景。整体结构需要逻辑清晰,从定义到实现再到应用,逐步深入,确保用户能够跟随思路,逐步掌握反射基础知识。</think>好的!以下是关于 **Java反射(Reflection)** 的基础知识介绍,以逐步分析的方式展开: --- ### 一、反射的定义 **反射(Reflection)** 是Java提供的一种**运行时动态机制**,允许程序在**不预先知道类结构**的情况下,通过`Class`对象获取类的信息(如字段、方法、构造器等),并操作这些成员。简单来说:**通过反射,程序可以“观察”修改自身的行为**。 --- ### 二、反射的核心类 Java反射通过以下核心类实现: 1. **`Class`**:表示类或接口的元数据。 2. **`Constructor`**:表示类的构造方法。 3. **`Method`**:表示类的方法。 4. **`Field`**:表示类的字段(属性)。 5. **`Modifier`**:解析成员的修饰符(如`public`、`private`)。 --- ### 三、反射的核心用途 1. **动态获取类信息** 例如:获取类名、父类、接口、注解等。 ```java Class<?> clazz = String.class; System.out.println("类名:" + clazz.getName()); // 输出:java.lang.String ``` 2. **动态创建对象** 即使没有显式定义构造方法,也可以通过反射实例化对象。 ```java Class<?> clazz = Class.forName("java.util.ArrayList"); Object list = clazz.newInstance(); // 创建ArrayList实例(已过时,推荐用构造器) ``` 3. **动态调用方法/访问字段** 可以绕过访问权限(如调用私有方法)。 ```java Method method = clazz.getDeclaredMethod("privateMethod"); method.setAccessible(true); // 突破private限制 method.invoke(obj); ``` 4. **实现通用框架** 如Spring的依赖注入(IoC)、MyBatis的ORM映射等。 --- ### 四、反射的实现步骤(以调用方法为例) 1. **获取`Class`对象** 三种方式: ```java Class<?> clazz1 = Class.forName("全限定类名"); // 通过类路径加载 Class<?> clazz2 = Object.class; // 通过类字面量 Class<?> clazz3 = obj.getClass(); // 通过对象实例 ``` 2. **获取方法或字段** ```java Method method = clazz.getDeclaredMethod("方法名", 参数类型.class); // 包括私有方法 Field field = clazz.getDeclaredField("字段名"); // 包括私有字段 ``` 3. **设置访问权限(可选)** ```java method.setAccessible(true); // 突破private限制 ``` 4. **执行操作** ```java Object result = method.invoke(对象实例, 参数值); // 调用方法 field.set(对象实例, 值); // 修改字段值 ``` --- ### 五、反射的优缺点 | **优点** | **缺点** | |------------------------------|-----------------------------------| | 动态性高(如插件化开发) | 性能较差(比直接调用慢约10-100倍)| | 可访问私有成员(灵活性强) | 代码可读性降低 | | 通用性高(框架开发必备) | 绕过类型安全检查,可能引发异常 | --- ### 六、实际应用案例 1. **动态加载类**(如插件机制): ```java Class<?> pluginClass = Class.forName("com.example.Plugin"); Object plugin = pluginClass.newInstance(); Method runMethod = pluginClass.getMethod("run"); runMethod.invoke(plugin); ``` 2. **Spring框架的依赖注入**: ```java // 通过反射创建Bean并注入属性 Class<?> beanClass = Class.forName("com.example.UserService"); Object bean = beanClass.newInstance(); Field daoField = beanClass.getDeclaredField("userDao"); daoField.setAccessible(true); daoField.set(bean, new UserDaoImpl()); // 注入依赖 ``` --- ### 七、注意事项 1. **性能优化**:避免在频繁调用的代码中使用反射。 2. **安全限制**:Java 9+的模块系统可能限制反射访问(需在`module-info.java`中声明`opens`)。 3. **代码健壮性**:反射调用需处理大量异常(如`ClassNotFoundException`、`NoSuchMethodException`)。 --- ### 总结 反射Java中强大的动态编程工具,**核心价值是灵活性**,但需谨慎使用。掌握反射后,可以深入理解框架设计原理(如Spring、Hibernate),但日常开发中应优先考虑直接调用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值