Java反射角度简单理解spring IOC容器

概述

Java反射(Reflection)是Java编程语言的一个特性,它允许在运行时对类、接口、字段和方法进行动态查询和操作。反射提供了一种在运行时查看和修改程序行为的能力,这通常用于实现一些高级功能,如框架(Spring)、ORM(对象关系映射)工具、IDE(集成开发环境)等。 
反射的主要用途
动态加载类:可以在运行时加载和使用类,而无需在编写/编译时知道它们。
检查类信息:可以获取类的名称、父类、实现的接口、字段、方法等。
动态调用方法:可以调用类的任何公共方法,即使该方法在编译时未知。
创建和操作对象:可以创建类的实例,并调用其方法或访问其字段。
修改字段值:可以修改类的私有字段的值(但通常不推荐这样做,因为它破坏了封装性,又名暴力反射)。 

反射使用场景及理解 

1.1. 动态加载类信息及动态调用类中的方法

 假设我们需要报考微软云的证书证书分别有如下几种,而且将来可能还会新增。
Azure900, Azure903, Azure300, Azure380

我们假设有这样一个需求,当用户传入的参数能模糊匹配上如下的方法名时,就算要报名该考试。如我传入 "30", 这个值正好能模糊匹配上方法名“singUpAzure930Exam”和“signUpAzure300Exam”,这时就报名930 和300两门考试,如我传入Azure,很显然,四门课程都会报名。具体需要传入什么样的参数根据用户需求动态而定。

我们这里用伪代码编写一个报名系统,首先我们申明一个报名类。

public class AzureCloudExam {
    public void AzureCloudExam(){
    }

    public String signUpAzure900Exam(){
        return "You signed up Azure 900!";
    }

    public String singUpAzure930Exam(){
        return "You signed up Azure 930!";
    }

    public String signUpAzure300Exam(){
        return "You signed up Azure 300!";
    }

    public String signUpAzure380Exam(){
        return "You signed up Azure 380!";
    }
}

显然这种场景,使用我们以往的先new 对象,再调用方法的普通模式是很难做到的,而且将来有新增加的考试方法后,我们也需要修改调用考试方法的服务类才能做到,继而需要修改服务代码,重新部署等麻烦。因为这种普通模式需要调用哪个方法是我们编写是就必须按照逻辑提前写好的,显然它不够灵活,然后在我们平时的开发过程中,普通方式new 对象调用方法已经足够用了,但是在面临一些更加灵活的需要时,就不行了,如spring框架等都有大量的使用反射解决,因为我们在使用spring框架开发时,显然编写阶段spring也是不知道需要加载哪些bean的。而是运行时根据注解动态生成的。

因而如果我们使用反射,就可以很好的解决动态调用方法的这个问题,模拟步骤如下:

编写服务代码用于调用如上的考试类。

package com.mycompany.myreflect.refDemo2;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MyReflectionServer {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
        //接收用户传入参数
        String className = args[0];
        String examLike = args[1];
        //动态加载需要使用class字节码文件
        Class<?> clazz = Class.forName(className);
        //动态无参构造方法new 对象
        Object obj = clazz.getDeclaredConstructor().newInstance();
        //获取class字节码中对应的所有方法
        Method[] allMethods = clazz.getDeclaredMethods();
        //loop所有方法
        for (Method method:allMethods){
            //如果用户传入的参数能模糊匹配上当前方法名
            if (method.getName().contains(examLike)){
                //调用报名方法
                String result = (String) method.invoke(obj);
                System.out.println(result);
            }
        }
    }
}

如我们传入参数
运行结果

如上实现了动态决定调用哪个方法。
如将来还要加入Alicloud 的考试门类,我们也只需要添加Alicloud的报名类就可以了,而服务类的代码不需要做任何改动。这就是反射带来的动态调用方法的灵活性。

1.2. 动态创建类对象

假设我们有这样一个需求。还是如上的报名考试系统,但是由于业务的扩展,现在不仅要支持微软云认证报名,还需要支持阿里云报名。将来还会新增厂家。报名的用户也在爆发增长。
显然我们每次客户请求都new一个对应厂商的的类,然后再在该类中去调用对应厂商的服务接口是报名,会带来不必要的对象创建和销毁。我们这里只需要一个单例的bean对象就可以了。

  • 在创建报名类之前我们先创建一个注解@MySupportExam, 用于注解某厂商的某类考试我们是否支持在我们这里报名,因为有的门类报名,对应厂商有授权考虑。
    package com.mycompany.myreflect.refDemo2;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MySupportExam {
    }
    
  • 创建@MyComponent注解,用于标准这是我们需要注入的厂商bean
    package com.mycompany.myreflect.refDemo2;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyComponent {
    }
  • 创建对应厂商的报名类。
    package com.mycompany.myreflect.refDemo2;
    
    @MyComponent
    public class AzureCloudExam {
        public void AzureCloudExam(){}
        @MyEnableSupport
        public String signUpAzure900Exam(){return "You signed up Azure 900!";}
        @MyEnableSupport
        public String singUpAzure930Exam(){return "You signed up Azure 930!";}
    
        public String signUpAzure300Exam(){return "You signed up Azure 300!";}
    
        public String signUpAzure380Exam(){return "You signed up Azure 380!";}
    }
    
    package com.mycompany.myreflect.refDemo2;
    
    @MyComponent
    public class AliCloudExam {
        public void AliCloudExam(){
        }
    
        public String signUpAli900Exam(){return "You signed up Ali 900!";}
        @MyEnableSupport
        public String singUpAli930Exam(){return "You signed up Ali 930!";}
        @MyEnableSupport
        public String signUpAli300Exam(){return "You signed up Ali 300!";}
    
        public String signUpAli380Exam(){return "You signed up Ali 380!";}
    }
    
  •  创建一个ClassScanner 扫描添加了@MyCompany注解的厂商类
    package com.mycompany.myreflect.refDemo2;
    import java.io.File;
    import java.net.URL;
    import java.net.URLDecoder;
    import java.util.Enumeration;
    import java.util.LinkedHashSet;
    import java.util.List;
    import java.util.Set;
    
    public class ClassScanner {
        public static Set<Class<?>> scanPackage(String packageName) {
            Set<Class<?>> classSet = new LinkedHashSet<>();
            try {
                Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packageName.replace(".", "/"));
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    String protocol = url.getProtocol();
                    if ("file".equals(protocol)) {
                        String filePath = URLDecoder.decode(url.getPath(), "UTF-8");
                        findClassByPackageName(new File(filePath), packageName, classSet);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return classSet;
        }
    
        private static void findClassByPackageName(File file, String packageName, Set<Class<?>> classSet) throws ClassNotFoundException {
            if (file.isFile() && file.getName().endsWith(".class")) {
                String className = file.getName().substring(0, file.getName().length() - 6);
                String fullClassName = packageName + "." + className;
                Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(fullClassName);
                classSet.add(clazz);
            } else if (file.isDirectory()) {
                File[] files = file.listFiles();
                for (File f : files) {
                    findClassByPackageName(f, packageName, classSet);
                }
            }
        }
    
        public static List<Class<?>> getExamCompanyClazz(List<Class<?>> examCompany,String packageName) {
            Set<Class<?>> classes = scanPackage(packageName);
            for (Class<?> clazz : classes) {
                if (clazz.getDeclaredAnnotation(MyComponent.class) !=null){
                    examCompany.add(clazz);
                }
            }
            return examCompany;
        }
    }
    
  •  创建一个容器类用于管理所有的厂商bean,和方法bean
    package com.mycompany.myreflect.refDemo2;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class ExamContainer {
        private Map<Class<?>,List<Method>> methodsSupportMap;
        private Map<Class<?>,Object> servicesMap;
        private List<Class<?>> examCompanies;
    
        public void init() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
            examCompanies = new ArrayList<>();
            servicesMap = new HashMap<>();
            methodsSupportMap = new HashMap<>();
            ClassScanner.getExamCompanyClazz(examCompanies,"com.mycompany.myreflect.refDemo2");
            for (int i = 0;i<examCompanies.size();i++){
                Class<?> clazz = examCompanies.get(i);
                //将获取到的厂商字节码,判断容器中是否存在该对象,如果不存在则创建该对象并放入容器中
                setServiceInstanceByClass(clazz);
                setMethod(clazz);
            }
        }
    
        public void setServiceInstanceByClass(Class<?> clazz) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
            Object obj = clazz.getDeclaredConstructor().newInstance();
            servicesMap.put(clazz,obj);
        }
    
        public void setMethod(Class<?> clazz){
            Method[] methods =clazz.getDeclaredMethods();
            // 如果方法加了@MySupportExam注解,说明我们这里支持该科目考试,加入容器
            for (Method method: methods){
                if (method.getDeclaredAnnotation(MySupportExam.class) != null){
                    if (methodsSupportMap.get(clazz)!=null){
                        methodsSupportMap.get(clazz).add(method);
                    }else {
                        List<Method> methodList = new ArrayList<>();
                        methodList.add(method);
                        methodsSupportMap.put(clazz,methodList);
                    }
                }
            }
        }
    
        public void signUpExam(String examLike) throws InvocationTargetException, IllegalAccessException {
            for (Class<?> clazz:examCompanies) {
                Object object = servicesMap.get(clazz);
                List<Method> methodList = methodsSupportMap.get(clazz);
                for (Method method : methodList) {
                    if (method.getName().contains(examLike)) {
                        System.out.println(method.invoke(object));
                    }
                }
            }
    
        }
    
    }
    
  • 用户请求服务方法调用 
    package com.mycompany.myreflect.refDemo2;
    
    import java.lang.reflect.InvocationTargetException;
    
    public class MyReflectionServer {
        public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
            ExamContainer examContainer = new ExamContainer();
            examContainer.init();
            String examLike = args[0];
            examContainer.signUpExam(examLike);
        }
    }
    

    请求参数
    运行结果

    请求参数

    运行结果

 1.3. 暴力反射

最近在开发过程中使用到暴力反射的一个案例就是,当我去给一个service类写UT实,发现某些方法是private的,但是这些private的方法,又是需要写UT的,这个时候我们就可以利用反射的暴力反射去为这个方法写UT调用。只需要设置如下属性即可,这里不在代码举案例。

​​​​​​​method.setAccessible(true);

注意事项

性能:反射通常比直接方法调用要慢得多,因为涉及到了额外的类型检查和安全性检查。因此,在性能敏感的代码中应谨慎使用。
安全性:反射允许绕过访问控制检查(例如,可以访问和修改私有字段),这可能导致安全问题。因此,在使用反射时应格外小心。
可读性:使用反射的代码通常比直接代码更难理解和维护。因此,在可以使用直接代码的情况下,应避免使用反射。 

  • 65
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 很高兴回答您的问题。首先,需要定义一个Bean类,然后使用反射机制创建Bean实例并将其存储在Map中。接着,需要解析Bean的依赖关系并进行注入。最后,需要实现Bean的生命周期管理,包括初始化和销毁。具体实现可以参考Spring源码。 ### 回答2: 实现一个简单的自定义Spring IOC容器需要以下步骤: 1. 定义一个bean类,该类包含需要注入的属性和对应的setter方法。 2. 创建一个容器类,用于管理bean的实例。在该容器类中,定义一个Map用来存储bean的名称和对应的实例。 3. 创建一个注入类,该类负责根据配置文件或注解来实例化和注入bean。可以使用反射机制来动态创建对象。 4. 编写一个配置类,该类用于读取配置文件或注解,获取需要创建的bean的信息。 5. 在容器类中,编写一个方法用于注册bean实例,将bean的名称和对应的实例放入Map中。 6. 在容器类中,编写一个方法用于从容器中获取bean的实例。 7. 将以上步骤整合到一个主程序中,测试容器类的功能。 以下是一个简单的实现示例: 1. 创建一个User类,包含name和age属性,并提供相应的setter方法。 ```java public class User { private String name; private int age; // Getter and Setter methods // ... } ``` 2. 创建一个容器类MyIOCContainer,使用Map来存储bean的名称和对应的实例。 ```java public class MyIOCContainer { private Map<String, Object> beans = new HashMap<>(); public void registerBean(String name, Object bean) { beans.put(name, bean); } public Object getBean(String name) { return beans.get(name); } } ``` 3. 创建一个注入类,用于实例化和注入bean。 ```java public class BeanInjector { public static void inject(Object bean) { // 通过反射机制实例化对象 // 通过反射机制注入属性 } } ``` 4. 创建一个配置类,使用注解来获取bean的信息。 ```java public class AppConfig { @Bean public User getUser() { User user = new User(); // 设置属性值 return user; } } ``` 5. 在容器类中实现注册和获取bean的方法。 ```java public class MyIOCContainer { // ... public void registerBean(String name, Object bean) { BeanInjector.inject(bean); // 注入属性 beans.put(name, bean); } public Object getBean(String name) { return beans.get(name); } // ... } ``` 6. 编写一个主程序,测试容器类的功能。 ```java public class Main { public static void main(String[] args) { MyIOCContainer container = new MyIOCContainer(); // 创建配置类实例 AppConfig config = new AppConfig(); // 获取bean的信息 Method[] methods = config.getClass().getMethods(); for (Method method : methods) { if (method.isAnnotationPresent(Bean.class)) { try { // 调用配置类的方法获取bean实例 Object bean = method.invoke(config); // 注册到容器中 container.registerBean(method.getName(), bean); } catch (Exception e) { e.printStackTrace(); } } } // 从容器中获取bean的实例 User user = (User) container.getBean("getUser"); System.out.println("Name: " + user.getName()); System.out.println("Age: " + user.getAge()); } } ``` 这是一个简单的自定义Spring IOC容器的实现示例,可以根据需要进行扩展和优化。 ### 回答3: 自定义一个简单Spring IOC容器需要包含几个基本的组件和功能。以下是一个简单的实现示例: ```java import java.util.HashMap; import java.util.Map; public class CustomIOCContainer { private Map<String, Object> beanMap; // 存储bean的map public CustomIOCContainer() { beanMap = new HashMap<>(); } public void registerBean(String beanName, Object bean) { beanMap.put(beanName, bean); // 注册bean到map中 } public Object getBean(String beanName) { return beanMap.get(beanName); // 通过beanName获取对应的bean对象 } public static void main(String[] args) { CustomIOCContainer container = new CustomIOCContainer(); // 创建bean对象并注册到容器中 UserService userService = new UserServiceImpl(); container.registerBean("userService", userService); // 从容器中获取bean对象并使用 UserService userServiceFromContainer = (UserService) container.getBean("userService"); userServiceFromContainer.sayHello(); // 调用方法 // 结果输出:Hello, World! } } interface UserService { void sayHello(); } class UserServiceImpl implements UserService { @Override public void sayHello() { System.out.println("Hello, World!"); } } ``` 以上代码实现了一个简单Spring IOC容器,核心思想是使用一个Map来存储注册的bean,通过bean的名称来获取对应的对象。在main方法中,我们创建了一个UserService对象并注册到容器中,然后通过容器的getBean方法获取UserService对象,并调用其sayHello方法进行输出。 这只是一个简化的实现示例,实际的Spring IOC容器功能还包括依赖注入、生命周期管理、AOP等复杂功能,不过以上代码可作为基本框架的起点。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值