自己动手写Spring

作者: bdqfork | 来源:发表于2019-02-24 23:17 被阅读4次

    众说周知,Spring是一个具有强大的依赖注入功能的Java框架。本篇文章将介绍笔者自己动手写的一个轻量级依赖注入框架,实现jsr330并兼容jsr330注解。

    完整代码托管在github中,可以点击https://github.com/bdqfork/spring-toy 查看完整项目。

    另笔者开发经验不足,欢迎大家指正批评。

    需求简介

    1. 可以使用注解标记类为组件,并自动扫描包路径,识别组件类。
    2. 获取注解信息,将组件类注册到容器中,供以后的访问使用。
    3. 解析组件之间的依赖关系,初始化组件类,并注入相关依赖。
    4. 从容器中获取组件类的实例,并正常调用相应的方法。

    更多更具体的细节需求,可以查看jsr330。jsr330对依赖注入作了一个详细的说明,但并未给出实现,笔者尝试根据jsr330的定义,作出了自己的实现。

    项目框架

    整个项目大致分为以下几个包:

    image

    其中,

    • 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源码的同学,应该了解到BeanDefinition。BeanDefinition是对Bean的一个描述,我们可以定义一个BeanDefinition。它描述了一个Bean的类型,名称,是否需要单例等信息。使用Map<String,BeanDefinition>等方式来作为容器,这样上文描述的问题就迎刃而解了。

    笔者定义的BeanDefinition如下:

    public class BeanDefinition {
        /**
         * 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);
            }
        }
    

    这样一来,容器的实现就简单多了。BeanDefinition的完整代码,可以查阅container包下的BeanDefinition类。

    下面贴出容器的代码实现,只贴出注册部分,完整代码请查看container包下的BeanContainer类。

    public class BeanContainer {
        private Map<String, BeanDefinition> beans = new HashMap<>();
    
        public void register(String beanName, BeanDefinition beanDefinition) throws ConflictedBeanException {
            if (beans.containsKey(beanName)) {
                throw new ConflictedBeanException(String.format("the entity named: %s has conflicted ! ", beanName));
            }
            beans.put(beanName, beanDefinition);
        }
    }
    

    通过调用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);
                    }
    
                    BeanDefinition beanDefinition = new BeanDefinition(candidate, isSingleton, name);
                    beanDefinition.setInjectorProvider(new InjectorProvider(candidate, this.beanNameGenerator));
    
                    beanContainer.register(beanDefinition.getName(), beanDefinition);
                }
            }
    
            Map<String, BeanDefinition> beanDefinationMap = beanContainer.getBeanDefinations();
            Resolver resolver = new Resolver(beanContainer);
            for (Map.Entry<String, BeanDefinition> entry : beanDefinationMap.entrySet()) {
                resolver.resolve(entry.getValue());
            }
    
        }
    

    完整的代码在context包下的AnnotationApplicationContext类中可以查看。

    依赖信息的管理

    通过上文的介绍,我们使用BeanDefinition描述了一个组件Bean的基本信息,但是我们还有一样重要的信息没有描述——组件依赖信息。组件类之间是有着依赖的关系的,BeanDefinition并没有描述组件类的依赖信息,为了要完整的描述组件类的信息,引入InjectorData来描述依赖注入信息。

    InjectorData是一个接口,可以有多种实现,其定义如下:

    public interface InjectorData {
    
        /**
         * 设置注入的bean
         *
         * @param bean
         */
        void setBean(BeanDefinition bean);
    
        /**
         * 返回依赖的bean
         *
         * @return
         */
        BeanDefinition getBean();
    
        /**
         * 设置依赖的默认名称
         *
         * @param defaultName
         */
        void setDefaultName(String defaultName);
    
        /**
         * 获取依赖的默认名称
         *
         * @return
         */
        String getDefaultName();
    
        /**
         * 获取指定的依赖的名称
         *
         * @return
         */
        String getRefName();
    
        /**
         * 获取依赖的类型
         *
         * @return
         */
        Class<?> getType();
    
        /**
         * 判断依赖是否匹配
         *
         * @param beanDefinition
         * @return
         */
        boolean isMatch(BeanDefinition beanDefinition);
    
        /**
         * 是否必须
         *
         * @return
         */
        boolean isRequired();
    
        /**
         * 设置是否是注入器
         *
         * @param provider
         */
        void setProvider(boolean provider);
    
        /**
         * 是否是注入器
         *
         * @return
         */
        boolean isProvider();
    
        /**
         * 设置注入器类型
         *
         * @param providedType
         */
        void setProvidedType(Class<?> providedType);
    
    }
    

    根据具体实现类的不同,可以用来描述不同的依赖注入信息,包括字段依赖注入信息,参数注入信息。继承关系图如下:

    image

    其中,抽象父类中实现了一些通用的方法,部分代码如下,省略一些get,set方法:

    public abstract class AbstractInjectorData implements InjectorData {
        /**
         * 默认依赖名称
         */
        private String defalultName;
        /**
         * 指定依赖名称
         */
        private String refName;
        /**
         * 依赖的BeanDefination实例
         */
        private BeanDefinition 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 void setDefaultName(String defaultName) {
            this.defalultName = defaultName;
        }
    
        @Override
        public String getDefaultName() {
            return defalultName;
        }
    
        @Override
        public String getRefName() {
            return refName;
        }
    
        @Override
        public void setBean(BeanDefinition bean) {
            this.bean = bean;
        }
    
        @Override
        public BeanDefinition getBean() {
            return this.bean;
        }
    
        @Override
        public boolean isRequired() {
            return isRequired;
        }
    
        @Override
        public boolean isMatch(BeanDefinition beanDefinition) {
            if (refName != null && refName.equals(beanDefinition.getName())) {
                return true;
            } else if (defalultName.equals(beanDefinition.getName())) {
                return true;
            } else {
                Class<?> type = getType();
                return beanDefinition.isType(type);
            }
        }
    
        @Override
        public void setProvider(boolean provider) {
            isProvider = provider;
        }
    
        @Override
        public boolean isProvider() {
            return isProvider;
        }
    
        @Override
        public void setProvidedType(Class<?> providedType) {
            this.providedType = providedType;
        }
    
        protected Class<?> getProvidedType() {
            return providedType;
        }
    }
    

    其子类实现也是很简单,将标记的依赖字段或者参数,传入相应的依赖描述里面保存下来即可:

    /**
     * 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 beanDefinition
         * @return boolean
         */
        boolean hasDependence(BeanDefinition beanDefinition);
    
        /**
         * 注入依赖
         *
         * @param instance
         * @param beanDefinition
         * @return
         * @throws InjectedException
         */
        Object inject(Object instance, BeanDefinition beanDefinition) throws InjectedException;
    }
    

    继承关系图如下:

    image

    AbstractInjector是Injector的抽象实现类,实现了一些通用的方法,代码如下:

    /**
     * @author bdq
     * @date 2019-02-14
     */
    public abstract class AbstractInjector implements Injector {
        protected List<InjectorData> injectorDatas;
    
        public AbstractInjector(List<InjectorData> injectorDatas) {
            this.injectorDatas = injectorDatas;
        }
    
        @Override
        public boolean hasDependence(BeanDefinition beanDefinition) {
            for (InjectorData injectorData : injectorDatas) {
                if (injectorData.isMatch(beanDefinition)) {
                    return true;
                }
            }
            return false;
        }
    
    }
    

    ConstructorInjector的功能是进行构造函数的注入,产生对象实例,主要代码如下:

        /**
         * 构造器注入
         *
         * @param beanDefinition
         * @return
         * @throws ConstructorInjectedException
         */
        public Object inject(BeanDefinition beanDefinition) throws ConstructorInjectedException {
            return inject(null, beanDefinition);
        }
    
        @Override
        public Object inject(Object instance, BeanDefinition beanDefinition) throws ConstructorInjectedException {
            if (constructor != null) {
                if (injectorDatas != null && injectorDatas.size() > 0) {
                    List<Object> args = new LinkedList<>();
                    //遍历构造函数的参数依赖信息
                    for (InjectorData injectorData : injectorDatas) {
                        BeanDefinition 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!", beanDefinition.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!", beanDefinition.getName()), e);
                    }
                }
            }
            return instance;
        }
    

    另外两个注入器的实现原理与之类似,只不过反射调用的方法不同罢了,就不再贴出代码了。

    依赖解析

    依赖注入器实现了依赖注入的过程,而依赖解析的过程并没有体现。依赖解析的步骤,笔者认为不属于注入器功能上的定义,依赖注入器应该只关注于进行依赖注入,所以笔者将这部分代码放在了Resolver中。

    在Resolver中,会对依赖进行解析,查询依赖的Bean,设置依赖信息。其主要代码如下:

        public void resolve(BeanDefinition beanDefinition) throws SpringToyException {
            //如果已经解析过了,则返回
            if (beanDefinition.isResolved()) {
                return;
            }
            //优先解析父类
            Class<?> superClass = beanDefinition.getClazz().getSuperclass();
            if (superClass != null && superClass != Object.class) {
    
                for (BeanDefinition bean : beanContainer.getBeans(superClass).values()) {
                    if (bean != beanDefinition) {
                        //递归解析父类
                        resolve(bean);
                    }
                }
            }
    
            InjectorProvider injectorProvider = beanDefinition.getInjectorProvider();
            if (injectorProvider != null) {
    
                //如果有构造器注入,则先解析构造器注入依赖
                if (injectorProvider.getConstructorParameterDatas() != null) {
                    for (InjectorData parameterInjectorData : injectorProvider.getConstructorParameterDatas()) {
                        doResolve(beanDefinition, injectorProvider, parameterInjectorData, parameterInjectorData.isRequired());
                    }
                }
    
                //如果有字段注入,则解析字段注入依赖
                if (injectorProvider.getFieldInjectorDatas() != null) {
                    for (InjectorData fieldInjectorData : injectorProvider.getFieldInjectorDatas()) {
                        doResolve(beanDefinition, injectorProvider, fieldInjectorData, fieldInjectorData.isRequired());
                    }
                }
    
                //如果有方法注入,则解析方法注入依赖
                if (injectorProvider.getMethodInjectorAttributes() != null) {
                    for (MethodInjectorAttribute methodInjectorAttribute : injectorProvider.getMethodInjectorAttributes()) {
                        if (methodInjectorAttribute.getParameterInjectorDatas() != null) {
                            for (InjectorData parameterInjectorData : methodInjectorAttribute.getParameterInjectorDatas()) {
                                doResolve(beanDefinition, injectorProvider, parameterInjectorData, methodInjectorAttribute.isRequired());
                            }
                        }
                    }
                }
    
            }
    
            beanDefinition.setResolved(true);
    
        }
    
        private void doResolve(BeanDefinition beanDefinition, InjectorProvider injectorProvider, InjectorData injectorData, boolean isRequired) throws UnsatisfiedBeanException {
            BeanDefinition ref = null;
    
            Map<String, BeanDefinition> 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 (BeanDefinition 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 (beanDefinition == ref || injectorProvider.hasDependence(beanDefinition)) {
                throw new UnsatisfiedBeanException("unsatisfied entity , there two entity ref each other !");
            } else {
                //设置依赖信息
                injectorData.setBean(ref);
            }
        }
    

    至此,一个简单的依赖注入框架完成了。这个框架还有很多需要完善的地方,比如效率的优化,更多安全性的检查等等。

    如果对笔者的框架感兴趣的,可以点击:https://github.com/bdqfork/spring-toy 查看完整代码,运行example进行测试,希望大家批评指正。

    相关文章

      网友评论

        本文标题:自己动手写Spring

        本文链接:https://www.haomeiwen.com/subject/oouiyqtx.html