关于Java反射

关于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之间的动态注入等功能;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容