Spring boot实现一个简单的ioc(2)

网友投稿 178 2023-05-20


Spring boot实现一个简单的ioc(2)

前言

跳过废话,直接看正文

仿照spring-boot的项目结构以及部分注解,写一个简单的ioc容器。

测试代码完成后,便正式开始这个ioc容器的开发工作。

正文

项目结构

实际上三四个类完全能搞定这个简单的ioc容器,但是出于可扩展性的考虑,还是写了不少的类。

因篇幅限制,接下来只将几个最重要的类的代码贴出来并加以说明,完整的代码请直接参考https://github.com/clayandgithub/simple-ioc。

SimpleAutowired

代码

import java.lang.annotation.*;

@Target({ElementType.FIELD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface SimpleAutowired {

boolean required() default true;

String value() default ""; // this field is moved from @Qualifier to here for simplicity

}

说明

@SimpleAutowired的作用是用于注解需要自动装配的字段。

此类和spring的@Autowired的作用类似。但又有以下两个区别:

- @SimpleAutowired只能作用于类字段,而不能作用于方法(这样实现起来相对简单些,不会用到aop)

- @SimpleAutowired中包括了required(是否一定需要装配)和value(要装配的bean的名字)两个字段,实际上是将spring中的@Autowired以及Qualifier的功能简单地融合到了一起

SimpleBean

代码

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface SimpleBean {

String value() default "";

}

说明

@SimpleBean作用于方法,根据方法返回值来生成一个bean,对应spring中的@Bean

用value来设置要生成的bean的名字

SimpleComponent

代码

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface SimpleBean {

String value() default "";

}

说明

@SimpleComponent作用于类,ioc容器会为每一个拥有@SimpleComponent的类生成一个bean,对应spring中的@Component。特殊说明,为了简单起见,@SimpleComponent注解的类必须拥有一个无参构造函数,否则无法生成该类的实例,这个在之后的SimpleAppliationContext中的processSingleClass方法中会有说明。

SimpleIocBootApplication

代码

import java.lang.annotation.*;

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface SimpleIocBootApplication {

String[] basePackages() default {};

}

说明

@SimpleIocBootApplication作用于应用的入口类。

这个启动模式是照搬了spring-boot的启动模式,将启动任务委托给SimpleIocApplication来完成。ioc容器将根据注解@SimpleIocBootApplication的相关配置自动扫描相应的package,生成beans并完成自动装配。(如果没有配置,默认扫描入口类(测试程序中的SampleApplication)所在的package及其子package)

以上就是这个ioc容器所提供的所有注解,接下来讲解ioc容器的扫描和装配过程的实现。

SimpleIocApplication

代码

import com.clayoverwind.simpleioc.context.*;

import com.clayoverwind.simpleioc.util.LogUtil;

import java.util.Arrays;

import java.util.Map;

import java.util.logging.Logger;

public class SimpleIocApplication {

private Class> applicationEntryClass;

private ApplicationContext applicationContext;

private final Logger LOGGER = LogUtil.getLogger(this.getClass());

public SimpleIocApplication(Class> applicationEntryClass) {

this.applicationEntryClass = applicationEntryClass;

}

public static void run(Class> applicationEntryClass, String[] args) {

new SimpleIocApplication(applicationEntryClass).run(args);

}

public void run(String[] args) {

LOGGER.info("start running......");

// create application context and application initializer

applicationContext = createSimpleApplicationContext();

ApplicationContextInitializer initializer = createSimpleApplicationContextInitializer(applicationEntryClass);

// initialize the application context (this is where we create beans)

initializer.initialize(applicationContext); // here maybe exist a hidden cast

// process those special beans

processSpecialBeans(args);

LOGGER.info("over!");

}

private SimpleApplicationContextInitializer createSimpleApplicationContextInitializer(Class> entryClass) {

// get base packages

SimpleIocBootApplicatihttp://on annotation = entryClass.getDeclaredAnnotation(SimpleIocBootApplication.class);

String[] basePackages = annotation.basePackages();

if (basePackages.length == 0) {

basePackages = new String[]{entryClass.getPackage().getName()};

}

// create context initializer with base packages

return new SimpleApplicationContextInitializer(Arrays.asList(basePackages));

}

private SimpleApplicationContext createSimpleApplicationContext() {

return new SimpleApplicationContext();

}

private void processSpecialBeans(String[] args) {

callRegisteredRunhttp://ners(args);

}

private void callRegisteredRunners(String[] args) {

Map applicationRunners = applicationContext.getBeansOfType(SimpleIocApplicationRunner.class);

try {

for (SimpleIocApplicationRunner applicationRunner : applicationRunners.values()) {

applicationRunner.run(args);

}

} catch (Exception e) {

throw new RuntimeException(e);

}

}

}

说明

前面说到应用的启动会委托SimpleIocApplication来完成,通过将应用入口类(测试程序中的SampleApplication)传入SimpleIocApplication的构造函数,构造出SimpleIocApplication的一个实例并运行run方法。在run方法中,会首先生成一个applicationContext,并调用SimpleApplicationContextInitializer来完成applicationContext的初始化(bean的扫描、装配)。然后调用processSpecialBeans来处理一些特殊的bean,如实现了SimpleIocApplicationRunner接口的bean会调用run方法来完成一些应用程序的启动任务。

这就是这个ioc容器的整个流程。

SimpleApplicationContextInitializer

代码

import java.io.IOException;

import java.util.LinkedHashSet;

import java.util.List;

import java.util.Set;

public class SimpleApplicationContextInitializer implements ApplicationContextInitializer {

private Set basePackages = new LinkedHashSet<>();

public SimpleApplicationContextInitializer(List basePackages) {

this.basePackages.addAll(basePackages);

}

@Override

public void initialize(SimpleApplicationContext applicationContext) {

try {

applicationContext.scan(basePackages, true);

} catch (ClassNotFoundException e) {

throw new RuntimeException(e);

} catch (IOException e) {

throw new RuntimeException(e);

}

applicationContext.setStartupDate(System.currentTimeMillis());

}

}

说明

在SimpleIocApplication的run中,会根据basePackages来构造一个SimpleApplicationContextInitializer 的实例,进而通过这个ApplicationContextInitializer来完成SimpleApplicationContext 的初始化。

在SimpleApplicationContextInitializer中, 简单地调用SimpleApplicationContext 中的scan即可完成SimpleApplicationContext的初始化任务

SimpleApplicationContext

说明:

终于到了最重要的部分了,在SimpleApplicationContext中将真正完成扫描、生成bean以及自动装配的任务。这里scan即为SimpleApplicationContext的程序入口,由SimpleApplicationContextInitializer在初始化时调用。

代码的调用逻辑简单易懂,就不多加说明了。

这里只简单列一下各个字段的含义以及几个比较关键的方法的作用。

字段

- startupDate:启动时间记录字段

- scannedPackages:已经扫描的包的集合,保证不重复扫描

- registeredBeans:已经完全装配好并注册好了的bean

- earlyBeans : 只是生成好了,还未装配完成的bean,用于处理循环依赖的问题

- totalBeanCount : 所有bean的计数器,在生成bean的名字时会用到其唯一性

方法

- processEarlyBeans:用于最终装配earlyBeans 中的bean,若装配成功,则将bean移至registeredBeans,否则报错

- scan : 扫描并处理传入的package集合

- processSingleClass:处理单个类,尝试生成该类的bean并进行装配(前提是此类有@SimpleComponent注解)

- createBeansByMethodsOfClass : 顾名思义,根据那些被@Bean注解的方法来生成bean

- autowireFields:尝试装配某个bean,lastChance代表是否在装配失败是报错(在第一次装配时,此值为false,在装配失败后会将bean移至earlyBeans,在第二次装配时,此值为true,实际上就是在装配earlyBeans中的bean,因此若仍然装配失败,就会报错)。在这个方法中,装配相应的bean时会从registeredBeans以及earlyBeans中去寻找符合条件的bean,只要找到,不管是来自哪里,都算装配成功。

代码

import com.clayoverwind.simpleioc.context.annotation.SimpleAutowired;

import com.clayoverwind.simpleioc.context.annotation.SimpleBean;

import com.clayoverwind.simpleioc.context.annotation.SimpleComponent;

import com.clayoverwind.simpleioc.context.factory.Bean;

import com.clayoverwind.simpleioc.util.ClassUtil;

import com.clayoverwind.simpleioc.util.ConcurrentHashSet;

import com.clayoverwind.simpleioc.util.LogUtil;

import java.io.IOException;

import java.lang.annotation.Annotation;

import java.lang.reflect.Field;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.util.*;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.atomic.AtomicLong;

import java.util.logging.Logger;

/**

* @author clayoverwind

* @E-mail clayanddev@163.com

* @version 2017/4/5

*/

public class SimpleApplicationContext implements ApplicationContext {

private long startupDate;

private Set scannedPackages = new ConcurrentHashSet<>();

private Map registeredBeans = new ConcurrentHashMap<>();

private Map earlyBeans = new ConcurrentHashMap<>();

private final Logger LOGGER = LogUtil.getLogger(this.getClass());

AtomicLong totalBeanCount = new AtomicLong(0L);

AtomicLong nameConflictCount = new AtomicLong(0L);

@Override

public Object getBean(String name) {

return registeredBeans.get(name);

}

@Override

public T getBean(String name, Class type) {

Bean bean = (Bean)getBean(name);

return bean == null ? null : (type.isAssignableFrom(bean.getClazz()) ? type.cast(bean.getObject()) : null);

}

@Override

public T getBean(Class type) {

Map map = getBeansOfType(type);

return map.isEmpty() ? null : type.cast(map.values().toArray()[0]);

}

@Override

public boolean containsBean(String name) {

return getBean(name) != null;

}

@Override

public Map getBeansOfType(Class type) {

Map res = new HashMap<>();

registeredBeans.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> res.put(entry.getKey(), type.cast(entry.getValue().getObject())));

return res;

}

@Override

public void setStartupDate(long startupDate) {

this.startupDate = startupDate;

}

@Override

public long getStartupDate() {

return startupDate;

}

/**

* try to autowire those beans in earlyBeans

* if succeed, remove it from earlyBeans and put it into registeredBeans

* otherwise ,throw a RuntimeException(in autowireFields)

*/

private synchronized void processEarlyBeans() {

for (Map.Entry entry : earlyBeans.entrySet()) {

Bean myBean = entry.getValue();

try {

if (autowireFields(myBean.getObject(), myBean.getClazz(), true)) {

registeredBeans.put(entry.getKey(), myBean);

earlyBeans.remove(entry.getKey());

}

} catch (IllegalAccessException e) {

throw new RuntimeException(e);

}

}

}

/**

* scan base packages and create beans

* @param basePackages

* @param recursively

* @throws ClassNotFoundException

*/

public void scan(Set basePackages, boolean recursively) throws ClassNotFoundException, IOException {

LOGGER.info("start scanning......");

ClassLoader classLoader = ThreadMLKPTqSeVj.currentThread().getContextClassLoader();

// get all classes who haven't been registered

Set> classes = new LinkedHashSet<>();

for (String packageName : basePackages) {

if (scannedPackages.add(packageName)) {

classes.addAll(ClassUtil.getClassesByPackageName(classLoader, packageName, recursively));

}

}

// autowire or create bean for each class

classes.forEach(this::processSingleClass);

processEarlyBeans();

LOGGER.info("scan over!");

}

/**

* try to create a bean for certain class, put it into registeredBeans if success, otherwise put it into earlyBeans

* @param clazz

*/

private void processSingleClass(Class> clazz) {

LOGGER.info(String.format("processSingleClass [%s] ...", clazz.getName()));

Annotation[] annotations = clazz.getDeclaredAnnotations();

for (Annotation annotation : annotations) {

if (annotation instanceof SimpleComponent) {

Object instance;

try {

instance = clazz.newInstance();

} catch (InstantiationException e) {

throw new RuntimeException(e);

} catch (IllegalAccessException e) {

throw new RuntimeException(e);

}

long beanId = totalBeanCount.getAndIncrement();

SimpleComponent component = (SimpleComponent) annotation;

String beanName = component.value();

if (beanName.isEmpty()) {

beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId);

}

try {

if (autowireFields(instance, clazz, false)) {

registeredBeans.put(beanName, new Bean(instance, clazz));

} else {

earlyBeans.put(beanName, new Bean(instance, clazz));

}

} catch (IllegalAccessException e) {

throw new RuntimeException(e);

}

try {

createBeansByMethodsOfClass(instance, clazz);

} catch (InvocationTargetException e) {

throw new RuntimeException(e);

} catch (IllegalAccessException e) {

throw new RuntimeException(e);

}

}

}

}

private void createBeansByMethodsOfClass(Object instance, Class> clazz) throws InvocationTargetException, IllegalAccessException {

List methods = getMethodsWithAnnotation(clazz, SimpleBean.class);

for (Method method : methods) {

method.setAccessible(true);

Object methodBean = method.invoke(instance);

long beanId = totalBeanCount.getAndIncrement();

Class> methodBeanClass = methodBean.getClass();

//bean name

SimpleBean simpleBean = method.getAnnotation(SimpleBean.class);

String beanName = simpleBean.value();

if (beanName.isEmpty()) {

beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId);

}

// register bean

registeredBeans.put(beanName, new Bean(methodBean, methodBeanClass));

}

}

private List getMethodsWithAnnotation(Class> clazz, Class> annotationClass) {

List res = new LinkedList<>();

Method[] methods = clazz.getDeclaredMethods();

for (Method method : methods) {

Annotation[] annotations = method.getAnnotations();

for (Annotation annotation : annotations) {

if (annotation.annotationType() == annotationClass) {

res.add(method);

break;

}

}

}

return res;

}

/**

* try autowire all fields of a certain instance

* @param instance

* @param clazz

* @param lastChance

* @return true if success, otherwise return false or throw a exception if this is the lastChance

* @throws IllegalAccessException

*/

private boolean autowireFields(Object instance, Class> clazz, boolean lastChance) throws IllegalAccessException {

Field[] fields = clazz.getDeclaredFields();

for (Field field : fields) {

Annotation[] annotations = field.getAnnotations();

for (Annotation annotation : annotations) {

if (annotation instanceof SimpleAutowired) {

SimpleAutowired autowired = (SimpleAutowired) annotation;

String beanName = autowired.value();

Bean bean = getSimpleBeanByNameOrType(beanName, field.getType(), true);

if (bean == null) {

if (lastChance) {

if (!autowired.required()) {

break;

}

throw new RuntimeException(String.format("Failed in autowireFields : [%s].[%s]", clazz.getName(), field.getName()));

} else {

return false;

}

}

field.setAccessible(true);

field.set(instance, bean.getObject());

}

}

}

return true;

}

/**

* only used in autowireFields

* @param beanName

* @param type

* @param allowEarlyBean

* @return

*/

private Bean getSimpleBeanByNameOrType(String beanName, Class> type, boolean allowEarlyBean) {

// 1. by name

Bean res = registeredBeans.get(beanName);

if (res == null && allowEarlyBean) {

res = earlyBeans.get(beanName);

}

// 2. by type

if (type != null) {

if (res == null) {

res = getSimpleBeanByType(type, registeredBeans);

}

if (res == null && allowEarlyBean) {

res = getSimpleBeanByType(type, earlyBeans);

}

}

return res;

}

/**

* search bean by type in certain beans map

* @param type

* @param beansMap

* @return

*/

private Bean getSimpleBeanByType(Class> type, Map beansMap) {

List beans = new LinkedList<>();

beansMap.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> beans.add(entry.getValue()));

if (beans.size() > 1) {

throw new RuntimeException(String.format("Autowire by type, but more than one instance of type [%s] is founded!", beans.get(0).getClazz().getName()));

}

return beans.isEmpty() ? null : beans.get(0);

}

private String getUniqueBeanNameByClassAndBeanId(Class> clazz, long beanId) {

String beanName = clazz.getName() + "_" + beanId;

while (registeredBeans.containsKey(beanName) || earlyBeans.containsKey(beanName)) {

beanName = clazz.getName() + "_" + beanId + "_" + nameConflictCount.getAndIncrement();

}

return beanName;

}

}

后记

至此,一个简单的ioc容器就完成了,总结一下优缺点。

优点:

小而简单。

可以使用@SimpleBean、@SimpleComponent以及@SimpleAutowired 来完成一些简单但常用的依赖注入任务.

缺点:

很明显,实现过于简单,提供的功能太少。

如果你想了解ioc的实现原理,或者你想要开发一个小型个人项目但又嫌spring过于庞大,这个简单的ioc容器或许可以帮到你。

如果你想做的不仅如此,那么你应该将目光转向spring-boot。

完整代码参考:https://github.com/clayandgithub/simple-ioc。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:java根据模板动态生成PDF实例
下一篇:Spring Batch读取txt文件并写入数据库的方法教程
相关文章

 发表评论

暂时没有评论,来抢沙发吧~