Java反射与“整活(IOC容器)”

2023年 7月 14日 127.6k 0

前言

没啥意思,太无无聊了,中值定理玩到吐,最近在复习高数,考研和开发并发进行,恢复博文更新,一方面是为了毕设,另一方面是为了秋招,看看有没有机会。当然考研还是个大方向,但是如果有合适的机会,那么,实话实话,鄙人没有什么远大理想,就搞钱钱,如果去读个研,后面还得装sz拿着可怜,甚至没有的补助的话,那不如去上个班。书什么时候都可以去读,但是机会不见得什么时候都有。至于所谓的人脉,非头部,别说话。那么废话不多说,我们来玩玩今天的反射吧。之后,我们再看看,如何将我们的反射用到我们的实际的项目当中,而不是,一直使用Spring或者SpringBoot。这里我们是做Java程序员,不是Spring
\SpringBoot程序员。你可以说,你常用的玩意是它,但是不能说你只会这玩意。

反射

什么是反射

Java反射是指在运行时动态地获取类的信息并操作对象的能力。通过反射,可以在运行时检查和修改类、方法、字段等的属性和行为,即使在编译时无法确定具体的类结构和方法。

举个例子:假设你拿到了一本书,但是在不打开书的情况下,你只能看到书的封面,而无法知道书中具体的内容。此时,反射就相当于打开这本书,你可以逐页阅读书籍的每个章节、段落和文字,甚至可以修改或添加内容。通过反射,你可以探索和操作类的结构和对象的特性,灵活地进行编程。
Java反射就是提供了一种动态检查和操作类、方法、字段等的能力。 通过反射,我们可以获取到类的属性,方法等等。通过这些内容,我们可以很轻松地完成类的操作。并且可以不进行显示的new对象。方便进行扩展,等操作。

基本操作

概念的话,我就念到这里,说实话,作用不大,直接看代码作用更大。

获取类对象

在Java中,可以通过以下三种方式来获取类对象:

  • 使用Class.forName()方法:
    这是一种常用的方式,通过提供类的全限定名作为参数,返回对应的Class对象。例如:

    Class clazz = Class.forName("com.example.MyClass");
    
  • 使用类字面常量:
    可以直接使用类字面常量来获取类对象,这是一种更加简洁方便的方式。例如:

    Class clazz = MyClass.class;
    
  • 调用对象的getClass()方法:
    在已经有一个对象的情况下,可以通过调用该对象的getClass()方法来获取类对象。例如:

    MyClass obj = new MyClass();
    Class clazz = obj.getClass();
    
  • 获取类属性

    要获取类的属性,可以使用以下方法:

  • 使用Class对象的getFields()方法:
    该方法返回类的所有公共字段(包括继承的字段),并以Field对象数组的形式返回。例如:

    Class clazz = MyClass.class;
    Field[] fields = clazz.getFields();
    
  • 使用Class对象的getField(String name)方法:
    通过指定字段名称,可以获取类的特定公共字段。如果字段不存在或不可访问,则会抛出NoSuchFieldException异常。例如:

    Class clazz = MyClass.class;
    Field field = clazz.getField("fieldName");
    
  • 使用Class对象的getDeclaredFields()方法:
    该方法返回类的所有字段(不包括继承的字段),并以Field对象数组的形式返回。例如:

    Class clazz = MyClass.class;
    Field[] fields = clazz.getDeclaredFields();
    
  • 使用Class对象的getDeclaredField(String name)方法:
    通过指定字段名称,可以获取类的特定字段,无论其访问权限如何。如果字段不存在,则会抛出NoSuchFieldException异常。例如:

    Class clazz = MyClass.class;
    Field field = clazz.getDeclaredField("fieldName");
    
  • 这些方法提供了不同的方式来获取类的属性信息,包括公共字段和私有字段。需要注意的是,不过对于私有字段,需要通过setAccessible(true)方法设置访问权限,才能对其进行操作。此外,还可以通过Field对象的getName()、getType()等方法获取字段的名称、类型等相关信息。

    此外我们还可以通过如下方法来对类的属性进行操作:

    使用Field对象的set(Object obj, Object value)方法:
    首先需要获取对应的Field对象,然后使用set方法设置属性的值。第一个参数是要设置属性值的对象实例,如果是静态字段,则可以传入null;第二个参数是要设置的属性值。例如:

    Class clazz = MyClass.class;
    Field field = clazz.getDeclaredField("fieldName");
    field.setAccessible(true); // 设置访问权限
    Object obj = new MyClass();
    field.set(obj, value); // 设置属性值
    

    此外还要使用Field对象的setInt(Object obj, int value)、setDouble(Object obj, double value)等类型特定的方法,无法返回对象是你特定的,而不是Object罢了。

    获取类方法

    之后是获取到类的方法

  • 使用Class对象的getMethods()方法:
    该方法返回类的所有公共方法(包括继承的方法),并以Method对象数组的形式返回。例如:

    Class clazz = MyClass.class;
    Method[] methods = clazz.getMethods();
    
  • 使用Class对象的getMethod(String name, Class... parameterTypes)方法:
    通过指定方法名称和参数类型,可以获取类的特定公共方法。如果方法不存在或不可访问,则会抛出NoSuchMethodException异常。例如:

    Class clazz = MyClass.class;
    Method method = clazz.getMethod("methodName", String.class, int.class);
    
  • 使用Class对象的getDeclaredMethods()方法:
    该方法返回类的所有方法(不包括继承的方法),并以Method对象数组的形式返回。例如:

    Class clazz = MyClass.class;
    Method[] methods = clazz.getDeclaredMethods();
    
  • 使用Class对象的getDeclaredMethod(String name, Class... parameterTypes)方法:
    通过指定方法名称和参数类型,可以获取类的特定方法,无论其访问权限如何。如果方法不存在,则会抛出NoSuchMethodException异常。例如:

    Class clazz = MyClass.class;
    Method method = clazz.getDeclaredMethod("methodName", String.class, int.class);
    
  • 这些方法提供了不同的方式来获取类的方法信息,包括公共方法和私有方法。需要注意的是,对于私有方法,需要通过setAccessible(true)方法设置访问权限,才能对其进行调用。此外,还可以通过Method对象的getName()、getReturnType()等方法获取方法的名称、返回类型等相关信息。

    方法的执行

    现在,我们获取到了这个方法还不够,我们还需要进行执行到这个方法,不然获取这个玩意有啥。

    我们从头捋一遍:

  • 获取方法对象:
    首先,需要获取目标类的Class对象,然后使用getMethod()getDeclaredMethod()方法获得特定的方法对象。例如:

    Class clazz = MyClass.class;
    Method method = clazz.getDeclaredMethod("methodName", String.class, int.class);
    
  • 获取方法的输入参数信息:
    使用方法对象的getParameterTypes()方法获取方法的参数类型数组。例如:

    Class[] parameterTypes = method.getParameterTypes();
    
  • 构造方法的参数值:
    根据参数类型,构造一个与方法参数一一对应的参数值数组。例如:

    Object[] arguments = new Object[parameterTypes.length];
    arguments[0] = "example";
    arguments[1] = 10;
    
  • 设置私有方法可访问:
    如果是私有方法,需要设置方法的可访问性:

    method.setAccessible(true);
    
  • 执行方法并获取返回值:
    使用方法对象的invoke()方法执行方法,并获取返回值。例如:

    Object result = method.invoke(obj, arguments);
    
  • 对构造方法的操作

    这个没啥,就是用这个玩意来newInstance()一个对象,就没了。
    其他的作用不大,就玩玩这些对象就够用了。

    注解

    说到了,这个反射,那肯定是不够的,俺们还有注解没有说。

    注解(Annotation)是Java语言提供的一种元数据(Metadata)机制,它可以用于在类、方法、字段等程序元素上添加额外的信息。注解可以在编译时和运行时被读取和处理,用于对代码进行标记、描述或配置。

    定义

    Java中的注解使用@符号来标识,放置在目标元素前面。例如,常见的注解包括:

  • @Override:用于标识方法覆盖父类的方法。
  • @Deprecated:用于标识过时的方法或类。
  • @SuppressWarnings:用于抑制编译器警告。
  • @Entity:用于标识JPA实体类。
  • @RequestMapping:用于标识Spring MVC控制器方法的映射路径。
  • 此外,我们还可以通过@interface关键字来定义一个新的注解类型。例如:

    import java.lang.annotation.*;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface MyAnnotation {
        String value();
        int count() default 0;
    }
    

    上述代码定义了一个名为MyAnnotation的自定义注解,包含了一个value属性和一个count属性。注解的生命周期为运行时,并且只能应用于方法上。

    此外,对于注解,我们还有对注解的一些描述,这些描述,是对注解进行的一些说明,描述,并给JVM进程一定的操作。例如这个注解作用到哪里,什么时候执行等等。这些被称为元注解。
    例如:

    import java.lang.annotation.*;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    
    
    

    以下是Java中常用的元注解:

  • @Retention:用于指定注解的生命周期,包括三个取值:

    • RetentionPolicy.SOURCE:注解仅存在于源代码中,在编译时会被忽略。
    • RetentionPolicy.CLASS:注解会被保留在编译后的字节码文件中,但在运行时不可用。
    • RetentionPolicy.RUNTIME:注解会被保留在编译后的字节码文件中,并在运行时可以通过反射读取。
  • @Target:用于指定注解适用的目标元素类型,包括多个取值:

    • ElementType.TYPE:类、接口、枚举等。
    • ElementType.FIELD:字段、枚举常量。
    • ElementType.METHOD:方法。
    • ElementType.PARAMETER:方法参数。
    • ElementType.CONSTRUCTOR:构造函数。
    • ElementType.LOCAL_VARIABLE:局部变量。
    • ElementType.ANNOTATION_TYPE:注解类型。
    • ElementType.PACKAGE:包。
    • ElementType.TYPE_PARAMETER:泛型参数。
    • ElementType.TYPE_USE:类型使用。
  • @Documented:用于指定注解是否包含在Java文档中。

  • @Inherited:用于指定注解是否可以被继承。

  • 获取注解

    通过,这个玩意,我们可以获取到类上面的注解

     Annotation[] annotations = clazz.getAnnotations();
    

    此外,我们还可以获取值:
    这是一段示例

    import java.lang.annotation.Annotation;
    
    @MyAnnotation(name = "MyClass", version = 1.0)
    public class MyClass {
    
        public static void main(String[] args) {
            Class clazz = MyClass.class;
            
            // 获取类的注解
            Annotation[] annotations = clazz.getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation instanceof MyAnnotation) {
                    MyAnnotation myAnnotation = (MyAnnotation) annotation;
                    String name = myAnnotation.name();
                    double version = myAnnotation.version();
                    
                    System.out.println("Class Name: " + name);
                    System.out.println("Version: " + version);
                }
            }
        }
    }
    

    当然除此之外,我们还可以获取方法上面的。
    其实是一样的:

    Annotation[] annotations = method.getAnnotations();
    

    其他啥也没变。

    整活(IOC容器)

    现在基础概念和使用方法,过了一遍了,那么我们接下来就要进行简单的运用了,如何把这个玩意用在我们实际的项目开发当中。那么接下来我们来编写一个,简单的项目架子。以后在些一些非Web应用的时候也用上来。虽然简陋,但是性能绝对很牛逼(因为简单,用安全性换的)。

    来,先看到,俺们的项目,啥也没有:
    在这里插入图片描述
    在未来,这个项目将变得非常复杂,就像这样:
    在这里插入图片描述
    并且在这些玩意里面有非常多的东西需要处理。不过在这里,不管这个项目有多复杂。有一个亘古不变的东西,那就是,这些代码分为两大阵营,第一大阵营是苦力工,专门负责运送,加工数据。另一大阵营是数据载体,专门负责运载数据。也就是各种Service,Implement,和Dao,Entity,只不过这些苦力有各种各样的工种,有些负责和数据库打交道,有些负责和其他第三方or rpc接口打交道。同样的对于数据载体也是不同的,有很多类别

    对于数据载体来说,每一个载体都是独一无二的,因为,运载的数据不同。但是一个苦力却可以一直不断地重复处理他所对应的数据。这是啥意思咧,每次我来了一个新的数据,需要处理的时候,我需要找个苦力来进行处理,比如,有一个数据负责用户的注册,我此时就需要一个员工来处理这件事情,并且从事UserService有3年工作检验,来帮我把这个数据,运送到叫做Mysql的地方去,有些有钱的老板还会放到叫做Oracle的地方。不过大部分的老板都没钱,于是只能放到叫做Mysql的地方。当然有时候也会放到redis,mongdb,kafka,es, 等等地方去。这个时候,我有两个选择,要么当数据运算完毕之后,就开除这个员工,要么,先把这个员工招进来,等我需要处理这个数据的时候,就叫他来。如果长时间业务不好,那就再裁掉这个员工。由于每次都招的话,实在是太慢了,于是你选择养起来。招聘员工,先养着,后来随着业务不断扩大,你管理起来越发麻烦了,于是你成立了一个叫做人事部门,当你需要谁去搬砖的时候,就通知人事,叫他过去就好了,当业务不好的时候,再通知人事裁员就好了,最后再把人事裁了。

    那么这个小案例,就说明到了,我们要整活的玩意,就是做一个简单的IOC容器,然后实现控制反转。

    项目结构

    在这里插入图片描述
    其实我们的核心很简单,就是希望通过ApplicationContext 这个玩意可以帮助我拿到类。其他的没啥了,然后这个玩意做为路口,负责我们整个项目的处理,就这么简单。

    那么怎么完成管理呢,其实最简单的办法其实就是,把对应的信息放在Map里面不就好了。

    IOC/DI流程

    在这里插入图片描述

    ApplicationContext

    这个呢,是咱们整个IOC的入口,其实在Spring里面有一个BeanFactory这个是顶层接口,然后那个ApplicationContext是那个接口的实现。这里没有搞那么复杂,不过流程大体流程是这样子的。

    这个ApplicationContext提供了一个非常重要的方法,就是getBean()方法。这个方法就可以帮我们把那个容器里面的对象拿出来。

    BeanDefinitionReader

    这个是一个解析器嘛,负责解析各种配置文件,比如xml,yaml,properites之类的。
    解析之后,要做的是把这个配置文件对应的内容给解析出来,然后把东西封装到BeanDefinition里面

    BeanDefinition

    这个玩意呢,是封装的我们的类的(被扫描出来)的信息。比如
    我们在配置文件里面写的玩意是
    在这里插入图片描述
    我们要扫描这个包下面的所有类,那么这个扫描首先是reader读取了要扫描包的信息,然后reader去扫描
    之后把扫描结果,类名,全包名封装起来。下面是它的代码,可以看到。

    package com.huterox.spring.framework.beans.config;
    
    public class HUBeanDefinition {
        private String factoryBeanName;
        private String beanClassName;
    
        public void setFactoryBeanName(String factoryBeanName) {
            this.factoryBeanName = factoryBeanName;
        }
    
        public void setBeanClassName(String beanClassName) {
            this.beanClassName = beanClassName;
        }
    
        public String getFactoryBeanName() {
            return factoryBeanName;
        }
    
        public String getBeanClassName() {
            return beanClassName;
        }
    }
    
    

    BeanWrapper

    这玩意呢,其实是实例化后的对象,同样我们也是把这玩意封装起来了。
    在这里插入图片描述
    然后我们可以再看到这个代码

    package com.huterox.spring.framework.beans;
    
    public class HUBeanWrapper {
        private Object wrapperInstance;
        private Class wrapperClass;
        public HUBeanWrapper(Object instance) {
            this.wrapperInstance = instance;
            this.wrapperClass = this.wrapperInstance.getClass();
        }
    
        public Object getWrapperInstance() {
            return wrapperInstance;
        }
    
        public Class getWrapperClass() {
            return wrapperClass;
        }
    }
    
    

    getBean()方法

    最后就是我们的这个方法,这个方法呢,是我们的核心
    在这里插入图片描述

    在这里插入图片描述

    IOC完整实现

    启动/调用

    接下来咱们好好聊聊这个IOC

    在这里插入图片描述
    在要的地方进行启动就可以,这个有点操作系统的意思在里面的,你写的应用程序只是其中一半,另一半是我底层些的系统库,并且这些系统库大部分都是用汇编进行编写的。(还是做上层简单)

    完整实现

    ok,我们现在来看到完整的代码:

    ```java
    package com.huterox.spring.framework.context;
    
    import com.huterox.spring.framework.annotation.HUAutowired;
    import com.huterox.spring.framework.annotation.HUController;
    import com.huterox.spring.framework.annotation.HUService;
    import com.huterox.spring.framework.beans.HUBeanWrapper;
    import com.huterox.spring.framework.beans.suports.HUBeanDefinitionReader;
    import com.huterox.spring.framework.beans.config.HUBeanDefinition;
    
    import java.lang.reflect.Field;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class HUApplicationContext {
    
        private String[] configLocations;
        private HUBeanDefinitionReader reader;
    
        private Map beanDefinitionMap = new HashMap();
        private Map factoryBeanInstanceCache = new HashMap();
        private Map factoryBeanObjectCache = new HashMap();
    
        public HUApplicationContext(String... configLocations) {
            this.configLocations = configLocations;
            //加载配置文件读取器
            this.reader = new HUBeanDefinitionReader(this.configLocations);
            List beanDefinitions = this.reader.doLoadBeanDefinitions();
            //2.缓存beanDefinitions对象
            try {
                doRegistryBeanDefintition(beanDefinitions);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            //3.创建IOC容器,把类都放在IOC容器里面
            doCreateBean();
        }
    
        private void doCreateBean() {
            for (Map.Entry beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
                String beanName = beanDefinitionEntry.getKey();
                //非延时加载
                getBean(beanName);
            }
        }
    
        private void doRegistryBeanDefintition(List beanDefinitions) throws Exception {
            //把Definition对象放在咱们的map里面,beanDefinition是保存的类名,全包名
            for (HUBeanDefinition beanDefinition : beanDefinitions) {
                //双键存储便于双向查找
                if (this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                    throw new Exception("The " + beanDefinition.getFactoryBeanName() + " is exists!");
                }
                this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
                this.beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
            }
        }
    
        //创建Bean的实例,完成依赖注入
        public Object getBean(String beanName) {
            //得到对象的全包名,当然这个信息封装在beanDefinition里面
            HUBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
            //实例化
            Object instance = instanceBean(beanName, beanDefinition);
            if (instance == null) return null;
            //实例封装为beanWrapper当中
            HUBeanWrapper beanWrapper = new HUBeanWrapper(instance);
            //将这个wrapper对象放在容器里面IOC里面
            this.factoryBeanInstanceCache.put(beanName, beanWrapper);
            //完成依赖注入
            populateBean(beanName, beanDefinition, beanWrapper);
            return this.factoryBeanInstanceCache.get(beanName).getWrapperInstance();
        }
    
        private void populateBean(String beanName, HUBeanDefinition beanDefinition, HUBeanWrapper beanWrapper) {
            //开始做依赖注入给值
            Object instance = beanWrapper.getWrapperInstance();
            Class clazz = beanWrapper.getWrapperClass();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (!(field.isAnnotationPresent(HUAutowired.class))) continue;
                HUAutowired autowired = field.getAnnotation(HUAutowired.class);
                String autowiredBeanName = autowired.value().trim();
                if ("".equals(autowiredBeanName)) {
                    autowiredBeanName = field.getType().getName();
                }
                field.setAccessible(true);
    
                try {
                    if (!this.factoryBeanInstanceCache.containsKey(autowiredBeanName)) continue;
                    //这个就是为什么要按照标准来写首字母要小写
                    field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
    
        }
    
        private Object instanceBean(String beanName, HUBeanDefinition beanDefinition) {
            String className = beanDefinition.getBeanClassName();
            Object instance = null;
            try {
                Class clazz = Class.forName(className);
                if (!(clazz.isAnnotationPresent(HUService.class) || clazz.isAnnotationPresent(HUController.class))) {
                    return null;
                }
                instance = clazz.newInstance();
                //接下来的这部分是AOP的入口,先忽略
    
    
                //三级缓存,为了解决这种循环依赖的问题,所以在后面进行DI的时候,这里
                //先进行一个缓存。因为完整的生命周期是对象要完成DI注入后,如果没有先进行缓存
                //那么在同一时刻进行DI注入的时候,出现A依赖B,B依赖A,并且缓存池里面没有B,要创建B
                //然后B又要A,A因为没有B又创建不了的情况,先初步缓存这样A要B的时候,创建B然后要依赖A
                //此时A在创建的时候有个缓存,这样B可以被创建,然后A最后完成注入,同样B也会完成。这部分逻辑后面再说
                this.factoryBeanObjectCache.put(beanName, instance);
    
    
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
            return instance;
        }
    
        public Object getBean(Class className) {
            return getBean(className.getName());
        }
    
        public int getBeanDefinitionCount() {
            return this.beanDefinitionMap.size();
        }
    
        public String[] getBeanDefinitionNames() {
            return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
        }
    }
    
    

    可以发现,其实这里没啥太多东西,我们目前按照咱的CURD编码习惯,去掉SpringBoot,去掉Mybatis依赖,其实也可以编写出来Lite的Lite版本。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论