美文网首页Java
关于Java反射

关于Java反射

作者: 大黑跟小白的日常 | 来源:发表于2020-04-07 22:56 被阅读0次

关于Class

类加载的几种方式

new Xxx()

public class OrderService {
    public static List<String> names;
}
public class UserService {
    static {
        System.out.println("UserService 静态代码块被执行");
        OrderService.names = new ArrayList<>();
        OrderService.names.add("GY");
        OrderService.names.add("TLX");
    }
}
public class TestClient {
    public static void main(String[] args) {
        // 采用new 创建对象的方式 加载 UserService类
        UserService userService = new UserService();
        // 静态代码块被执行,OrderService的静态属性names被初始化
        System.out.println(OrderService.names);
    }
}
image-20200406214926926.png

classLoader.loadClass(String className)

public class TestClient {
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader classLoader = TestClient.class.getClassLoader();
        // 这里不会去解析(链接)这个类,只是加载Class到JVM,也就是说不会去初始化类的静态资源(静态代码块)
        classLoader.loadClass("cls.loadclasstest.UserService");
        // 看具体打印结果,UserService类的 static {} 并没有执行 
        System.out.println(OrderService.names);
    }
}
image-20200406215757573.png
image-20200406215937524.png

Class.forName(String className)

public class TestClient {
    public static void main(String[] args) throws ClassNotFoundException {
        // 默认会去初始化类,初始化类的静态资源,执行类的 static {}
        Class<?> cls1 = Class.forName("cls.loadclasstest.UserService");
        // 使用如下方式,则不会初始化类的静态资源
//        Class.forName("cls.loadclasstest.UserService", false, TestClient.class.getClassLoader());
        System.out.println(OrderService.names);
    }
}
image-20200406220612748.png
image-20200406220533146.png

具体使用

Spring框架中的IOC,根据XML中配置的目标类地址,加载得到对应的Class;

根据包扫描路径,扫描到class文件,加载各个Class到程序。最终通过Class生成对应实例Bean交给IOC管理;

得到Class cls对象之后

public interface IUserService {}
public class AbstractUserService implements IUserService {}
public class UserService extends AbstractUserService {}

判断Class是不是接口

isInterface

public class ClientTest {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> c1 = Class.forName("cls.clstest.IUserService");
        Class<?> c2 = Class.forName("cls.clstest.AbstractUserService");
        Class<?> c3 = Class.forName("cls.clstest.UserService");
        System.out.println(c1.isInterface());// true
        System.out.println(c2.isInterface());// false
        System.out.println(c3.isInterface());// false
    }
}

获取Class实现的接口Class[]

getInterfaces

Class<?>[] interfaces1 = c2.getInterfaces();
// 只可以获取到该类直接实现的接口,无法获取到父类实现的接口
Class<?>[] interfaces2 = c3.getInterfaces();
image-20200406225756049.png
image-20200406225838176.png

判断Class是不是被某个注解修饰了

isAnnotationPresent

// 定义一个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
    String name();
    String[] values();
}
@Controller(name="userService", values = {"abc","edf"})
public class UserService {}
public class ClientTest {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> c3 = Class.forName("cls.clstest.UserService");
        // 判断某个Class对象上是否加了某个注解
        boolean annotationPresent = c3.isAnnotationPresent(Controller.class);
        System.out.println(annotationPresent); // true
        Class<?> c2 = Class.forName("cls.clstest.AbstractUserService");
        annotationPresent = c2.isAnnotationPresent(Controller.class);
        System.out.println(annotationPresent); // false
    }
}

获取被修饰注解中的属性

getAnnotation

        Class<?> c3 = Class.forName("cls.clstest.UserService");
        // 获取 UserService类上的Controller注解对象
        Controller annotation = c3.getAnnotation(Controller.class);
        // 从注解对象中获取属性
        String name = annotation.name();// userService
        String[] values = annotation.values();// ["abc","edf"]
        System.out.println(name);
        for (String value : values) {
            System.out.println(value);
        }
image-20200407103109178.png

具体应用

基于指定基础包扫描路径、注解模式实现的Spring IOC,底层实则就用到了根据Class对象,获取其上修饰的注解(对象),判断该类的实例是否是需要IOC进行管理的。并且可以根据Class之上的注解(对象),获取注解中的属性值,以用于实现如何构建当前Class的Bean(比如指定其在IOC中的Bean的名称、是否为单例、或用于Bean对象的一些成员属性的初始化赋值以用于后续业务操作)。

获取构造

Constructor也是一种类型,具体构造函数也是对象

public class UserService {
    private String name;
    private Integer age;
    public UserService(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    public UserService(String name) {
        this.name = name;
    }
    public UserService() {
    }
}
public class ClientTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Class<?> c3 = Class.forName("cls.clstest.UserService");
        // 根据参数类型获取构造函数
        Constructor<?> constructor1 = c3.getConstructor(String.class, Integer.class);
        // 使用构造函数创建对象
        UserService userService = (UserService) constructor1.newInstance("hello", 18);

        // 获取所有构造函数
        Constructor<?>[] constructors = c3.getConstructors();
        // 遍历构造函数
        for (Constructor<?> constructor : constructors) {
            // 获取该构造函数的 参数类型集
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            System.out.println("------------");
            for (Class<?> parameterType : parameterTypes) {
                // 打印参数类型
                System.out.println(parameterType.getName());
            }
        }
    }
}
image-20200407113939445.png

具体应用体现

Spring IOC初始化过程中通过构造方法注入属性,生成Bean

<bean id="******" class="***.***.***">
    <constructor-arg type="java.lang.String" value="***" />
</bean>
<!--或者-->
<bean id="******" class="***.***.***">
    <!--这里实则是根据Class中匹配构造函数的参数的名称获取对应构造方法-->
    <!-- 具体参见 : https://blog.csdn.net/qq_31803503/article/details/80990821 -->
    <constructor-arg name="**" value="***" />
    <constructor-arg name="***" value="****"/>
</bean>

附加内容如下:

Class<?> c3 = Class.forName("cls.clstest.UserService");
// 根据参数类型获取构造函数
Constructor<?> constructor1 = c3.getConstructor(String.class, Integer.class);
// 获取构造函数参数对象Parameter
Parameter[] parameters = constructor1.getParameters();
for (Parameter parameter : parameters) {
    // 打印参数名称
    System.out.println(parameter.getName()); // 当前这种获取参数的方式,打印的参数名称跟实际定义的名称不服
}
image-20200407122505454.png

如上这中方式获取的构造函数参数的名称,没有具体意义,并不是具体的name、age, Spring使用ASM字节码操作框架来获取方法参数的名称 。

具体参见:https://blog.csdn.net/qq_31803503/article/details/80990821

创建对象

用Constructor来创建对象

// 比如通过获取无参构造来构建对象,要求目标Class得有可访问的无参构造
Class<?> c3 = Class.forName("cls.clstest.UserService");
Constructor<?> constructor2 = c3.getConstructor();
UserService o = (UserService) constructor2.newInstance();

如果构造不可访问,如:

public class UserService {
    // 私有无参构造外部不可访问
    private UserService() {}
    private String name;
    private Integer age;
    public UserService(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

则需要暴力靠近

Class<?> c3 = Class.forName("cls.clstest.UserService");
// 获取声明过的构造(不管是否可访问)
Constructor<?> constructor2 = c3.getDeclaredConstructor();
// 暴力靠近
constructor2.setAccessible(true);
// 通过当前Constructor构建对象
UserService o = (UserService) constructor2.newInstance();

如果获取的是带参构造,则需按类型进行传参构建对象

Class<?> c3 = Class.forName("cls.clstest.UserService");
// 获取可访问的构造函数对象
Constructor<?> constructor1 = c3.getConstructor(String.class, Integer.class);
// 按照参数类型对应传参构建实例
UserService userService = (UserService) constructor1.newInstance("hello", 18);

cls.newInstance构建

Class<?> c3 = Class.forName("cls.clstest.UserService");
// 这里底层实则也是调用的无参数构造,要求目标类Class提供可访问的无参构造
UserService o1 = (UserService) c3.newInstance();

应用体现

Spring IOC 中的各个Service、Controller、Component、Configuration...对象,都是通过反射执行构造生成的(除去代理对象);

Method方法

// 目标类
public class UserService {
    // 私有方法
    private String f1(String s) {
        System.out.println("private f1");
        return s;
    }
    // 静态方法
    public static Integer f2(Integer i1, Integer i2) {
        System.out.println("static f2");
        return i1 + i2;
    }
    // 无参方法
    public String f3() {
        System.out.println("public f3 no args");
        return "f3";
    }
    // 带参方法
    public String f3(String name, Integer age) {
        System.out.println("public f3 has args");
        return "f3 " + name + " " + age;
    }
}

获取Class中Method对象

Class<?> c3 = Class.forName("cls.clstest.UserService");
// 获取已声明的方法(包含私有不可访问的方法),按名称获取方法
Method f3_1 = c3.getDeclaredMethod("f3");// 获取无参方法f3
Method f3_2 = c3.getDeclaredMethod("f3", String.class, Integer.class);// 获取带参方法
// 获取所有方法
Method[] methods = c3.getDeclaredMethods();
for (Method method : methods) {
    // 方法名称
    System.out.println(method.getName());
    // 方法所在类
    System.out.println(method.getDeclaringClass().getName());// 即 cls.clstest.UserService
    // 方法返回类型
    System.out.println(method.getReturnType().getName());
    // 方法参数类型集合
    Class<?>[] parameterTypes = method.getParameterTypes();
    for (Class<?> parameterType : parameterTypes) {
        // 打印方法参数类型名称
        System.out.println(parameterType.getName());
    }
    System.out.println("----------------------");
}
image-20200407203739503.png

执行Method

执行私有普通方法

Class<?> c3 = Class.forName("cls.clstest.UserService");
// 构建执行方法的对象
UserService userService = new UserService();
// 获取私有方法Method对象需要使用getDeclaredMethod
Method f1 = c3.getDeclaredMethod("f1", String.class);
// 执行私有方法前,需要暴力靠近
f1.setAccessible(true);
// 执行
f1.invoke(userService,"haha");

执行静态方法

Class<?> c3 = Class.forName("cls.clstest.UserService");
Method f2 = c3.getMethod("f2", Integer.class, Integer.class);
// 静态方法使用Class对象去执行
Object invoke = f2.invoke(c3, 1, 1);
System.out.println(invoke);
image-20200407205401240.png

执行普通无参方法

Class<?> c3 = Class.forName("cls.clstest.UserService");
// 如果需要获取带参方法,则需要将参数类型一并传入
Method f3 = c3.getMethod("f3");
UserService userService = new UserService();
Object invoke = f3.invoke(userService);
System.out.println(invoke);

获取Method上面的注解

// 自定义注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    String[] value() default {};
}
@Controller(name = "userController", values = {"aaa","bbb"})
public class UserController {
    @Autowired
    private UserService userService;
    // 在方法上加上注解
    @RequestMapping("/test")
    public String f() {
        String s = userService.f3();
        return s;
    }
}
Class<?> controllerClass = Class.forName("cls.clstest.UserController");
// 根据方法名称获取Method对象,这里该方法没有参数
Method f = controllerClass.getMethod("f");
// 判断Method上是否包含某个注解
boolean annotationPresent = f.isAnnotationPresent(RequestMapping.class);
System.out.println(annotationPresent);// true
if(annotationPresent) {
    // 获取注解对象
    RequestMapping annotation = f.getAnnotation(RequestMapping.class);
    // 获取注解对象中的参数
    String[] value = annotation.value();
    for (String s : value) {
        System.out.println(s); // /test
    }
}
image-20200407213447967.png

应用体现

  • 在RPC框架中,服务提供者在接收到请求数据后,从协议数据中获取方法名称、参数类型、以及客户端请求参数、及注册中心的具体服务Bean的信息,从服务提供端容器中获取最终执该方法的对象,然后用反射进行执行,获取执行结果,响应服务调用者;
  • 在Spring MVC中通过获取具体Method对象上的注解,解析注解对象中的属性值,最终生成映射关系,在具体客户端请求时,通过url及一些信息最终定位到具体用于处理该请求业务的Method方法,然后从IOC中获取到该Controller对象,然后利用反射进行执行,获取业务请求结果,响应客户端;

Field属性

获取Field对象

public class UserController {
    @Autowired
    private UserService userService;
}
Class<?> controllerClass = Class.forName("cls.clstest.UserController");
// 根据属性名称,获取已声明属性(可以获取私有属性)
// controllerClass.getField("userService"); // 这样则无法获取私有属性
Field userServiceField = controllerClass.getDeclaredField("userService");
// 获取所有属性--数组
Field[] declaredFields = controllerClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
    // 属性名称
    System.out.println(declaredField.getName());
    // 属性类型
    System.out.println(declaredField.getType());
}
image-20200407215600842.png

执行Field的对应Get、Set方法

执行属性Get方法

类中不需要提供该属性对应的get、set方法

Class<?> controllerClass = Class.forName("cls.clstest.UserController");
Field userServiceF = controllerClass.getDeclaredField("userService");
// 暴力靠近,让属性的get、set方法可以执行
userServiceF.setAccessible(true);
// 构建目标对象
UserController userController = new UserController();
// 反射执行Field的get方法
UserService userService1 = (UserService) userServiceF.get(userController);
// 执行目标对象的userService这个成员属性的get方法
System.out.println(userService1);// 这里因为没有设置对象的属性,所以为null
image.png

执行属性Set方法

Class<?> controllerClass = Class.forName("cls.clstest.UserController");
UserController userController = new UserController();
// 按属性名称获取属性Field对象
Field userServiceF = controllerClass.getDeclaredField("userService");
userServiceF.setAccessible(true);
// 反射执行Filed的set方法
userServiceF.set(userController,new UserService());
// 反射执行Field的get方法
UserService userService1 = (UserService) userServiceF.get(userController);
System.out.println(userService1);// 打印对象中的属性
image.png

获取Field上面的注解

// 自定义注解
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
}
Class<?> controllerClass = Class.forName("cls.clstest.UserController");
Field userServiceF = controllerClass.getDeclaredField("userService");
// 判断Field上是否被某个注解修饰
boolean b = userServiceF.isAnnotationPresent(Autowired.class);
System.out.println(b);// true
if(b) {
    Autowired annotation = userServiceF.getAnnotation(Autowired.class);
}

拓展内容:

Class<?> controllerClass = Class.forName("cls.clstest.UserController");
Field userServiceF = controllerClass.getDeclaredField("userService");
// 判断Field上是否有@Autowired
boolean b = userServiceF.isAnnotationPresent(Autowired.class);
System.out.println(b);
if(b) {
    // 如果当前字段被@Autowired修饰,Spring IOC 初始化——动态注入环节,则会考虑针对当前字段做自动注入操作
    // 获取字段的类型
    Class<?> type = userServiceF.getType();
    // 从IOC已经构建的Bean容器中根据类型type获取Bean
    // 这里我自己new一个,用于测试
    UserService userService = new UserService();
    // 同样需要从IOC容器中根据当前Class——controllerClass,获取到当前Controller对象,我同样new一个用于测试
    UserController userController = new UserController();
    // 获取到Bean之后,接下来执行Field的set方法,实现自动注入
    userServiceF.setAccessible(true);
    // 动态注入
    userServiceF.set(userController, userService);
}

应用体现

Spring IOC加载过程中,会用到反射操作Field,实现Bean之间的动态注入等功能;

相关文章

  • 关于java反射

    //getDeclaredMethods 返回当前类定义的所有方法 (Field同理) //getMethod...

  • 关于Java反射

    记 java.lang.IllegalArgumentException: object is not an in...

  • 关于Java反射

    关于Class 类加载的几种方式 new Xxx() classLoader.loadClass(String c...

  • 博客地址

    java注解-01、java注解-02、Git面试资源java反射-01、java反射-02、java反射-03为...

  • Java反射机制入门

    Java反射机制入门 一、什么是反射 JAVA反射机制(The JAVA reflection mechanism...

  • Java基础之反射

    Java基础之—反射(非常重要)Java中反射机制详解Java进阶之reflection(反射机制)——反射概念与...

  • 干货!使用Java注解和反射实现Junit4中的用例调用(附源码

    前面我写了关于Java注解和反射的文章: 测试开发必须掌握的知识点:Spring的核心知识点 -Java反射[ht...

  • 反射之一

    总结内容源自一下文章粗浅看java反射机制反射机制应用实践谈谈java反射机制Java Reflection(反射...

  • 反射之二

    总结内容源自一下文章粗浅看java反射机制反射机制应用实践谈谈java反射机制Java Reflection(反射...

  • java关于反射 example

    在之前的时候第一次知道了java的反射的概念。 其应用的是java的类加载器, java的类加载器在加载一个类文件...

网友评论

    本文标题:关于Java反射

    本文链接:https://www.haomeiwen.com/subject/licaphtx.html