内容简介:众说周知,Spring是一个具有强大的依赖注入功能的Java框架。本篇文章将介绍笔者自己动手写的一个轻量级依赖注入框架,实现jsr330并兼容jsr330注解。完整代码托管在github中,可以点击另笔者开发经验不足,欢迎大家指正批评。
众说周知,Spring是一个具有强大的依赖注入功能的 Java 框架。本篇文章将介绍笔者自己动手写的一个轻量级依赖注入框架,实现jsr330并兼容jsr330注解。
完整代码托管在github中,可以点击 https://github.com/bdqfork/spring-toy 查看完整项目。
另笔者开发经验不足,欢迎大家指正批评。
需求简介
- 可以使用注解标记类为组件,并自动扫描包路径,识别组件类。
- 获取注解信息,将组件类注册到容器中,供以后的访问使用。
- 解析组件之间的依赖关系,初始化组件类,并注入相关依赖。
- 从容器中获取组件类的实例,并正常调用相应的方法。
更多更具体的细节需求,可以查看 jsr330 。jsr330对依赖注入作了一个详细的说明,但并未给出实现,笔者尝试根据jsr330的定义,作出了自己的实现。
项目框架
整个项目大致分为以下几个包:
其中,
- annotation包中定义了一些容器所需要的注解,比如Component,Service等注解。
- container包是容器的主要实现,负责处理容器的相关功能,如依赖注入等。
- context包定义了上下文环境,负责扫描组件,以及依赖解析等过程。
- exception包定义了项目所需的异常。
- proxy包定义了两种动态代理的方式,一种是Jdk的动态代理实现,另一种是CGlib方式。
- utils包定义了一些 工具 类。
功能实现
注解定义
在进行核心功能实现之前,首先定义相关的注解,笔者参考了Spring的注解定义,作出了如下注解。
定义 | 功能 | 参数定义 |
---|---|---|
@Component | 用于标记组件类,被标记的类将会被添加到容器中管理。 | String value(),用于指定类名,默认为””。 |
@Repositorty | 用于标记组件类,被标记的类将会被添加到容器中管理,这是一个领域定义,其功能和Component一致。 | String value(),用于指定类名,默认为””。 |
@Service | 用于标记组件类,被标记的类将会被添加到容器中管理,这是一个领域定义,其功能和Component一致。 | String value(),用于指定类名,默认为””。 |
@Controller | 用于标记组件类,被标记的类将会被添加到容器中管理,这是一个领域定义,其功能和Component一致。 | String value(),用于指定类名,默认为””。 |
@Scope | 用于标记组件类,用于表示,注册到容器中的类,其实例是否为单例。 | String value(),用于表示是否单例,其值在ScopeType类中定义。 |
@AutoWired | 标记待注入的字段,构造函数,setter方法等。 | boolean required(),用于表示是否一定需要注入。 |
@Qualifier | 限定器,可以与AutoWired一起使用,标记待注入依赖名。 | String value(),待注入依赖名。 |
BeanFactory | 用于替换@AutoWired的一个接口,可以实现相同的功能。 | T,待注入依赖的类型。 |
由于同时笔者兼容了jsr330,jsr330官方给出了一些注解,功能上与上文定义的注解可以进行相互替换,在此也进行描述。
注解 | 功能 | 备注 |
---|---|---|
@Named | 可以作为@Component等组件标记注解的替换,标记一个组件类。 | 也可以作为@Qualifier的替换,标记待注入依赖名。 |
@Singleton | 可以替换@Scope(“singleton”),被标记的类的实例将会是单例。 | |
@Inject | 可以和@AutoWired替换,标记待注入的字段,构造函数,setter方法等。 | 不支持required,其标记的依赖必须注入,否则抛出异常。 |
Provider | 与BeanFactory等价,可以互相替换。 |
注解扫描
在相关注解的定义完成之后,需要进行扫描,将标记有@Component等注解的类扫描出来,以进行下一步的处理。
整个扫描的过程实际上是对类进行扫描,可以通过Java的ClassLoader来扫描类路径,将类加载进一个集合中。这个过程的部分代码如下,完整代码可以在utils包下的ReflectUtil中查看。
private static final String FILE_PROTOCOL = "file"; private static final String JAR_PROTOCOL = "jar"; private static final String SUFFIX = ".class"; /** * 根据包名获取获取Class * * @param packageName * @return */ public static Set<Class<?>> getClasses(String packageName) { if (packageName == null || "".equals(packageName)) { return Collections.emptySet(); } //将包名改为相对路径 String packagePath = packageName.replace(".", "/"); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Set<Class<?>> classes = new HashSet<>(); try { //扫描包路径,返回资源的枚举 Enumeration<URL> dirs = classLoader.getResources(packagePath); while (dirs.hasMoreElements()) { URL fileUrl = dirs.nextElement(); String filePath = fileUrl.getPath(); //判断资源类型 if (FILE_PROTOCOL.equals(fileUrl.getProtocol())) { //处理文件类型的Class classes.addAll(getClassesByFilePath(filePath, packagePath)); } else if (JAR_PROTOCOL.equals(fileUrl.getProtocol())) { //处理Jar包中的Class JarURLConnection jarURLConnection = (JarURLConnection) fileUrl.openConnection(); JarFile jarFile = jarURLConnection.getJarFile(); classes.addAll(getClassesByJar(jarFile)); } } } catch (IOException e) { e.printStackTrace(); } return classes; }
通过ClassLoader加载指定包路径下的所有资源,然后对Class进行加载即可,需要注意的是jar包里面的Class加载的方式有些许不同。
容器的实现
容器这个功能可以说是依赖注入的核心之一了,容器是对所有组件的管理,基本上所有的功能都围绕着容器来开展。
最简单的容器可能就是一个Map<String,Object>了,网上很多的文章都是基于这个类型实现的简单的依赖注入。然而,这种方式有很多的缺陷。例如,使用这种方式实现的容器,其存储的大都是待注入对象的直接实例,也就是说获取的对象实例大都是单例的形式,这就导致了一个问题,当需要返回的实例是一个新的实例的时候,这种实现方式就无法满足了。一方面是因为,Map里只保存了一个实例,另一方面是因为返回新的实例,需要重新将依赖注入到新的实例中。
因此,要使用更高级的方式进行实现。看过Spring源码的同学,应该了解到BeanDefination。BeanDefination是对Bean的一个描述,我们可以定义一个BeanDefination。它描述了一个Bean的类型,名称,是否需要单例等信息。使用Map<String,BeanDefination>等方式来作为容器,这样上文描述的问题就迎刃而解了。
笔者定义的BeanDefination如下:
public class BeanDefination { /** * Class类 */ private Class<?> clazz; /** * Bean的名称 */ private String name; /** * 单实例 */ private Object instance; /** * 是否单例 */ private boolean isSingleton; /** * 依赖信息提供者 */ private InjectorProvider injectorProvider; }
获取对象实例的方法如下:
/** * 获取对象实例,如果bean是单例的,则每次都返回同一个实例,如果不是,则每次都创建一个新的实例。 * * @return Object */ public Object getInstance() throws InjectedException { if (isSingleton) { return getSingleInstance(); } return newBean(); } private Object getSingleInstance() throws InjectedException { if (instance == null) { synchronized (Object.class) { if (instance == null) { instance = newBean(); } } } return instance; } private Object newBean() throws InjectedException { Object instance = injectorProvider.doInject(this); Class<?>[] classes = clazz.getInterfaces(); if (classes.length != 0) { JdkInvocationHandler jdkInvocationHandler = new JdkInvocationHandler(); return jdkInvocationHandler.newProxyInstance(instance); } else { CglibMethodInterceptor cglibMethodInterceptor = new CglibMethodInterceptor(); return cglibMethodInterceptor.newProxyInstance(instance); } }
这样一来,容器的实现就简单多了。BeanDefination的完整代码,可以查阅container包下的BeanDefination类。
下面贴出容器的代码实现,只贴出注册部分,完整代码请查看container包下的BeanContainer类。
public class BeanContainer { private Map<String, BeanDefination> beans = new HashMap<>(); public void register(String beanName, BeanDefination beanDefination) throws ConflictedBeanException { if (beans.containsKey(beanName)) { throw new ConflictedBeanException(String.format("the entity named: %s has conflicted ! ", beanName)); } beans.put(beanName, beanDefination); } }
通过调用register方法,即可将Bean注册到容器中。
注册组件
组件的注册过程很简单,扫描包路径,获取所有组件的Class。然后根据jsr330的要求,检测组件是否可注册,将可注册的组件注册到容器中即可。代码如下:
private void scan() throws SpringToyException { Set<Class<?>> candidates = new HashSet<>(); for (String scanPath : scanPaths) { candidates.addAll(ReflectUtil.getClasses(scanPath)); } for (Class<?> candidate : candidates) { if (candidate.isAnnotation() || candidate.isInterface() || Modifier.isAbstract(candidate.getModifiers())) { continue; } String name = getComponentName(candidate); if (name != null) { boolean isSingleton = false; Scope scope = candidate.getAnnotation(Scope.class); if (scope != null) { if (ScopeType.SINGLETON.equals(scope.value())) { isSingleton = true; } else if (!ScopeType.PROTOTYPE.equals(scope.value())) { throw new SpringToyException("the value of scope is error !"); } } else if (candidate.getAnnotation(Singleton.class) != null) { isSingleton = true; } if ("".equals(name)) { name = this.beanNameGenerator.generateBeanName(candidate); } BeanDefination beanDefination = new BeanDefination(candidate, isSingleton, name); beanDefination.setInjectorProvider(new InjectorProvider(candidate, this.beanNameGenerator)); beanContainer.register(beanDefination.getName(), beanDefination); } } Map<String, BeanDefination> beanDefinationMap = beanContainer.getBeanDefinations(); Resolver resolver = new Resolver(beanContainer); for (Map.Entry<String, BeanDefination> entry : beanDefinationMap.entrySet()) { resolver.resolve(entry.getValue()); } }
完整的代码在context包下的AnnotationApplicationContext类中可以查看。
依赖信息的管理
通过上文的介绍,我们使用BeanDefination描述了一个组件Bean的基本信息,但是我们还有一样重要的信息没有描述——组件依赖信息。组件类之间是有着依赖的关系的,BeanDefination并没有描述组件类的依赖信息,为了要完整的描述组件类的信息,引入InjectorData来描述依赖注入信息。
InjectorData是一个接口,可以有多种实现,其定义如下:
public interface InjectorData { /** * 设置注入的bean * * @param bean */ void setBean(BeanDefination bean); /** * 返回依赖的bean * * @return */ BeanDefination getBean(); /** * 设置依赖的默认名称 * * @param defaultName */ void setDefaultName(String defaultName); /** * 获取依赖的默认名称 * * @return */ String getDefaultName(); /** * 获取指定的依赖的名称 * * @return */ String getRefName(); /** * 获取依赖的类型 * * @return */ Class<?> getType(); /** * 判断依赖是否匹配 * * @param beanDefination * @return */ boolean isMatch(BeanDefination beanDefination); /** * 是否必须 * * @return */ boolean isRequired(); /** * 设置是否是注入器 * * @param provider */ void setProvider(boolean provider); /** * 是否是注入器 * * @return */ boolean isProvider(); /** * 设置注入器类型 * * @param providedType */ void setProvidedType(Class<?> providedType);
根据具体实现类的不同,可以用来描述不同的依赖注入信息,包括字段依赖注入信息,参数注入信息。继承关系图如下:
其中,抽象父类中实现了一些通用的方法,部分代码如下,省略一些get,set方法:
public abstract class AbstractInjectorData implements InjectorData { /** * 默认依赖名称 */ private String defalultName; /** * 指定依赖名称 */ private String refName; /** * 依赖的BeanDefination实例 */ private BeanDefination bean; /** * 是否必须 */ private boolean isRequired; /** * 是否是Provider或者BeanFactory依赖 */ private boolean isProvider; /** * Provider或者BeanFactory提供的依赖类 */ private Class<?> providedType; public AbstractInjectorData(String defalultName, String refName, boolean isRequired) { this.defalultName = defalultName; this.refName = refName; this.isRequired = isRequired; } @Override public boolean isMatch(BeanDefination beanDefination) { if (refName != null && refName.equals(beanDefination.getName())) { return true; } else if (defalultName.equals(beanDefination.getName())) { return true; } else { Class<?> type = getType(); return beanDefination.isType(type); } } }
其子类实现也是很简单,将标记的依赖字段或者参数,传入相应的依赖描述里面保存下来即可:
/** * bean的依赖信息 * * @author bdq * @date 2019-02-12 */ public class FieldInjectorData extends AbstractInjectorData { private Field field; public FieldInjectorData(String defalultName, String refName, boolean required, Field field) { super(defalultName, refName, required); this.field = field; } @Override public Class<?> getType() { if (isProvider()) { return getProvidedType(); } return field.getType(); } public Field getField() { return field; } } /** * @author bdq * @date 2019-02-13 */ public class ParameterInjectorData extends AbstractInjectorData { private Parameter parameter; public ParameterInjectorData(String defalultName, String refName, boolean required, Parameter parameter) { super(defalultName, refName, required); this.parameter = parameter; } @Override public Class<?> getType() { if (isProvider()) { return getProvidedType(); } return parameter.getType(); } }
依赖注入器
什么是依赖注入器,依赖注入器是笔者自己定义的一个接口Injector,它的功能是负责管理依赖信息,进行依赖注入。之所以定义这个接口,是因为依赖注入有着三种场景:字段注入,构造器注入,方法注入。不同的注入方式有不同的实现方式,于是引入Injector,分别实现对应三种场景的注入器,Injector的实现应该持有对应的注入信息。
Injector接口的定义如下:
/** * @author bdq * @date 2019-02-14 */ public interface Injector { /** * 判断当前bean是否依赖beanDefination,如果是,返回true,否则返回false * * @param beanDefination * @return boolean */ boolean hasDependence(BeanDefination beanDefination); /** * 注入依赖 * * @param instance * @param beanDefination * @return * @throws InjectedException */ Object inject(Object instance, BeanDefination beanDefination) throws InjectedException; }
继承关系图如下:
AbstractInjector是Injector的抽象实现类,实现了一些通用的方法,代码如下:
public abstract class AbstractInjector implements Injector { protected List<InjectorData> injectorDatas; public AbstractInjector(List<InjectorData> injectorDatas) { this.injectorDatas = injectorDatas; } @Override public boolean hasDependence(BeanDefination beanDefination) { for (InjectorData injectorData : injectorDatas) { if (injectorData.isMatch(beanDefination)) { return true; } } return false; } }
ConstructorInjector的功能是进行构造函数的注入,产生对象实例,主要代码如下:
/** * 构造器注入 * * @param beanDefination * @return * @throws ConstructorInjectedException */ public Object inject(BeanDefination beanDefination) throws ConstructorInjectedException { return inject(null, beanDefination); } @Override public Object inject(Object instance, BeanDefination beanDefination) throws ConstructorInjectedException { if (constructor != null) { if (injectorDatas != null && injectorDatas.size() > 0) { List<Object> args = new LinkedList<>(); //遍历构造函数的参数依赖信息 for (InjectorData injectorData : injectorDatas) { BeanDefination bean = injectorData.getBean(); try { if (bean != null) { //判断是否是Provider if (injectorData.isProvider()) { //添加实例到Provider参数 args.add(new ObjectFactory<>(bean.getInstance())); } else { //添加实例作为参数 args.add(bean.getInstance()); } } } catch (InjectedException e) { throw new ConstructorInjectedException(String.format("failed to inject entity: %s by constructor!", beanDefination.getName()), e); } } try { if (args.size() > 0) { //反射调用构造器,构造对象实例 instance = constructor.newInstance(args.toArray()); } } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new ConstructorInjectedException(String.format("failed to inject entity: %s by constructor!", beanDefination.getName()), e); } } } return instance; }
另外两个注入器的实现原理与之类似,只不过反射调用的方法不同罢了,就不再贴出代码了。
依赖解析
依赖注入器实现了依赖注入的过程,而依赖解析的过程并没有体现。依赖解析的步骤,笔者认为不属于注入器功能上的定义,依赖注入器应该只关注于进行依赖注入,所以笔者将这部分代码放在了Resolver中。
在Resolver中,会对依赖进行解析,查询依赖的Bean,设置依赖信息。其主要代码如下:
public void resolve(BeanDefination beanDefination) throws SpringToyException { //如果已经解析过了,则返回 if (beanDefination.isResolved()) { return; } //优先解析父类 Class<?> superClass = beanDefination.getClazz().getSuperclass(); if (superClass != null && superClass != Object.class) { for (BeanDefination bean : beanContainer.getBeans(superClass).values()) { if (bean != beanDefination) { //递归解析父类 resolve(bean); } } } InjectorProvider injectorProvider = beanDefination.getInjectorProvider(); if (injectorProvider != null) { //如果有构造器注入,则先解析构造器注入依赖 if (injectorProvider.getConstructorParameterDatas() != null) { for (InjectorData parameterInjectorData : injectorProvider.getConstructorParameterDatas()) { doResolve(beanDefination, injectorProvider, parameterInjectorData, parameterInjectorData.isRequired()); } } //如果有字段注入,则解析字段注入依赖 if (injectorProvider.getFieldInjectorDatas() != null) { for (InjectorData fieldInjectorData : injectorProvider.getFieldInjectorDatas()) { doResolve(beanDefination, injectorProvider, fieldInjectorData, fieldInjectorData.isRequired()); } } //如果有方法注入,则解析方法注入依赖 if (injectorProvider.getMethodInjectorAttributes() != null) { for (MethodInjectorAttribute methodInjectorAttribute : injectorProvider.getMethodInjectorAttributes()) { if (methodInjectorAttribute.getParameterInjectorDatas() != null) { for (InjectorData parameterInjectorData : methodInjectorAttribute.getParameterInjectorDatas()) { doResolve(beanDefination, injectorProvider, parameterInjectorData, methodInjectorAttribute.isRequired()); } } } } } beanDefination.setResolved(true); } private void doResolve(BeanDefination beanDefination, InjectorProvider injectorProvider, InjectorData injectorData, boolean isRequired) throws UnsatisfiedBeanException { BeanDefination ref = null; Map<String, BeanDefination> beanDefinationMap = beanContainer.getBeanDefinations(); //判断依赖组件是否存在,先查找指定名称的依赖,如果不存在,则按找默认名称去查找,仍然不存在,则再按类型匹配 if (injectorData.getRefName() != null && beanDefinationMap.containsKey(injectorData.getRefName())) { ref = beanDefinationMap.get(injectorData.getRefName()); } else if (beanDefinationMap.containsKey(injectorData.getDefaultName())) { ref = beanDefinationMap.get(injectorData.getDefaultName()); } else { for (BeanDefination bean : beanDefinationMap.values()) { if (bean.isType(injectorData.getType())) { ref = bean; break; } else if (bean.isSubType(injectorData.getType())) { ref = bean; break; } } } //判断依赖是否存在,如果不存在,则抛出异常。如果依赖存在,但有相互引用的情况,也抛出异常 if (ref == null) { if (isRequired) { throw new UnsatisfiedBeanException("unsatisfied entity , the entity named " + injectorData.getType() + " don't exists"); } } else if (beanDefination == ref || injectorProvider.hasDependence(beanDefination)) { throw new UnsatisfiedBeanException("unsatisfied entity , there two entity ref each other !"); } else { //设置依赖信息 injectorData.setBean(ref); } }
至此,一个简单的依赖注入框架完成了。这个框架还有很多需要完善的地方,比如效率的优化,更多安全性的检查等等。
如果对笔者的框架感兴趣的,可以点击: https://github.com/bdqfork/spring-toy 查看完整代码,运行example进行测试,希望大家批评指正。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
有限与无限的游戏
[美]詹姆斯·卡斯 / 马小悟、余倩 / 电子工业出版社 / 2013-10 / 35.00元
在这本书中,詹姆斯·卡斯向我们展示了世界上两种类型的「游戏」:「有限的游戏」和「无限的游戏」。 有限的游戏,其目的在于赢得胜利;无限的游戏,却旨在让游戏永远进行下去。有限的游戏在边界内玩,无限的游戏玩的就是边界。有限的游戏具有一个确定的开始和结束,拥有特定的赢家,规则的存在就是为了保证游戏会结束。无限的游戏既没有确定的开始和结束,也没有赢家,它的目的在于将更多的人带入到游戏本身中来,从而延续......一起来看看 《有限与无限的游戏》 这本书的介绍吧!