文章目录
- 前言
- 反射
- 什么是反射
- 基本操作
- 获取类对象
- 获取类属性
- 获取类方法
- 方法的执行
- 对构造方法的操作
- 注解
- 定义
- 获取注解
- 整活(IOC容器)
- 项目结构
- IOC/DI流程
- ApplicationContext
- BeanDefinitionReader
- BeanDefinition
- BeanWrapper
- getBean()方法
- IOC完整实现
- 启动/调用
- 完整实现
前言
没啥意思,太无无聊了,中值定理玩到吐,最近在复习高数,考研和开发并发进行,恢复博文更新,一方面是为了毕设,另一方面是为了秋招,看看有没有机会。当然考研还是个大方向,但是如果有合适的机会,那么,实话实话,鄙人没有什么远大理想,就搞钱钱,如果去读个研,后面还得装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<String, HUBeanDefinition> beanDefinitionMap = new HashMap<>();private Map<String, HUBeanWrapper> factoryBeanInstanceCache = new HashMap<>();private Map<String, Object> factoryBeanObjectCache = new HashMap<>();public HUApplicationContext(String... configLocations) {this.configLocations = configLocations;//加载配置文件读取器this.reader = new HUBeanDefinitionReader(this.configLocations);List<HUBeanDefinition> beanDefinitions = this.reader.doLoadBeanDefinitions();//2.缓存beanDefinitions对象try {doRegistryBeanDefintition(beanDefinitions);} catch (Exception e) {e.printStackTrace();}//3.创建IOC容器,把类都放在IOC容器里面doCreateBean();}private void doCreateBean() {for (Map.Entry<String, HUBeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {String beanName = beanDefinitionEntry.getKey();//非延时加载getBean(beanName);}}private void doRegistryBeanDefintition(List<HUBeanDefinition> 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版本。