42

自己动手写Spring

 5 years ago
source link: http://www.bdqfork.cn/post/a31b1747.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

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

需求简介

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

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

项目框架

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

eaUFzi3.png!web

其中,

  • 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);

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

iaUJJ3r.png!web

其中,抽象父类中实现了一些通用的方法,部分代码如下,省略一些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;
}

继承关系图如下:

7ruEvmZ.png!web

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进行测试,希望大家批评指正。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK