首先来想下,如果没有spring ioc的时候,程序员是如何来获取与管理对象的,考虑如下的例子

A.java
void functionA(){
	ObjA a = new ObjA();
}

B.java
void functionB(ObjA a){
	a.funca();
	ObjB b = new ObjB();
}

C.java
void functionC(ObjA a,ObjB b){
	a.funca1();
	b.funcb();
}

以上三个java类,考虑如下几种场景:

1,按照上面的执行方法,在C.java中是用都对象a,b是经过方法调用一层一层传递下去的,上面只有三个类时,这样传递似乎看上去还可以。但是如果有N个java文件,难道准备这样一直传递下去?

2,1显然不行,考虑一个办法,在用到的类中在new一个对象,这样带来的后果是分配了很多用一次后就没有用处的对象,增加了gc时间,这个办法不可行。

3,再考虑一个办法,将new的对象放在threadLocal中,这样就避免了传递,但是还是不可避免的会有多份不必要new出来的对象。在绝大多数场景下,我们仅仅需要一个单例就可以完成任务了。

4,假如对象需要进行一些使用之前的初始化操作呢?

5,对象销毁,比如数据源对象,在销毁之前要做一系列的关闭连接,清除等操作,放到业务代码中去显然不合适

6,何时执行对象的销毁操作也是程序员要考虑的

综上,那我们的一个解决办法就是自行写个单例,考虑工厂,对象的初始化和销毁操作都交给这个工厂去做,但是其弊端显而易见:

1,程序员不能只是专注于实现业务逻辑了,还得抽出时间来管理对象的生命周期,每添加一个新的对象,就得去工厂中实现对应的获取与销毁

2,何时执行对象的销毁这个问题交给工厂了也解决不了,需要自己实现监听web服务器的close事件,执行对象的销毁操作

对于程序员来说,我就只想关注我要实现的业务逻辑,用到的对象最好是有地方已经帮我准备好,我只管拿来用,也不用去管理对象的生命周期等破事。迫切需要一个玩意儿来帮我干这件事,把我解放出来,不然我太累了。。。。。

ok,spring ioc容器就可以把程序员解放出来了,下敏我根据自己的理解,一步步实现一个自己的ioc容器

===================================================================================================================================

考虑如下几个问题:
1,首先要让容器知道,哪些类是需要创建实例并交由容器管理的,如果没有默认的构造方法,那得让容器知道如何产生这个实例。

2,bean如何解析

3,bean如何存放,方便程序能够快速获取到对应的bean

4,bean的生命周期

下面围绕这几个问题,编写自己的ioc容器,编写的时候按照(需求)从上到下的方式,首先编写一个使用bean的测试程序,一个常见的获取bean代码如下:

public class Main {

    public static void main(String[] args) throws Exception{
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = (User)context.getBean("user");
        user.toString();
    }
}

其中,我们采用xml方式来配置bean的定义,applicationContext.xml中定义了哪些bean需要创建,并交由容器管理,ClassPathXmlApplicationContext就是用来处理这些bean定义,生成bean,管理bean,首先来看下applicationContext.xml中是怎样的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="contactInfo" class="com.my.ioc.ContactInfo">
        <constructor-arg name="mobile" value="13211111111"></constructor-arg>
        <constructor-arg name="mail" value="123@123.com"></constructor-arg>
    </bean>

    <bean id="user" class="com.my.ioc.User">
        <property name="name" value="test"></property>
        <property name="contactInfo" ref="contactInfo"></property>
    </bean>

</beans>
其中User和ContactInfo如下:
public class User {

    private String name;

    private ContactInfo contactInfo;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public ContactInfo getContactInfo() {
        return contactInfo;
    }

    public void setContactInfo(ContactInfo contactInfo) {
        this.contactInfo = contactInfo;
    }

    @Override
    public String toString() {
        return "name:"+this.getName()+",contact info:"+contactInfo.getMobile()+","+contactInfo.getMail();
    }
}

public class ContactInfo {

    private String mobile;

    private String mail;

    public ContactInfo(String mobile,String mail){
        this.mobile = mobile;
        this.mail = mail;
    }

    public String getMail() {
        return mail;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMail(String mail) {
        this.mail = mail;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
}
接下来,我们先定义一个接口BeanFactory,接口中定义方法getBean,这样以后有其他容器时,只需实现该接口,程序就可以获取bean
public interface BeanFactory {
    public Object getBean(String beanName);
}
接下来看下ClassPathXmlApplicationContext类,根据测试程序,首先得有构造函数:
public class ClassPathXmlApplicationContext implements BeanFactory {

    public ClassPathXmlApplicationContext(String configXmlLocation){
        
    }
}
接下来,就得处理bean的解析,首先得定义BeanDefinition的类,里面包含了bean定义该有的元素:
public class BeanDefinition {
    private String id;
    private String className;
    private String initMethod;
    private String destroyMethod;
    private List<ConstructorArg> constructorArgList;
    private List<Property> properties;
}
class ConstructorArg{
    private String name;
    private String value;
}
class Property{
    private String name;
    private String value;
    private String ref;
}
接下来,就应该解析xml,封装成一个个的BeanDefinition,我们放在XmlParser中实现(引入dom4j.jar):
public class ClassPathXmlApplicationContext implements BeanFactory{

    public ClassPathXmlApplicationContext(String configXmlLocation) throws Exception{
        /**
         * 1,parse xml
         */
        Map<String,BeanDefinition> beanDefinitionMap = XmlParser.parse(configXmlLocation);
    }
}

public class XmlParser {
    public static Map<String,BeanDefinition> parse(String path) throws Exception {
        InputStream is = XmlParser.class.getResourceAsStream("/"+path);
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(is);
        Element element = document.getRootElement();//root elements must be beans
        if(!element.getName().equalsIgnoreCase("beans"))
            throw new Exception("wrong xml format");
        Map<String,BeanDefinition> map = new HashMap<>();
        List<Element> elements = element.elements();
        for(Element elem : elements){
            String name = elem.getName();
            //考虑到applicationContext.xml可以加入更多的标签进行解析,我们将对应的parser从策略工厂里获取
            StrategyFactory.getBeanDefinitionParser(name).parse(elem,map);
        }
        return map;
    }
}

考虑到后面可以支持更多的标签解析,我们首先将解析的行为抽出来
public interface BeanDefinitionParser {

    /**
     * 解析element
     * @param element
     * @param beanDefinitionMap
     */
    public void parse(Element element,Map<String,BeanDefinition> beanDefinitionMap);
}

然后定义相关的parser,如
public class CommonBeanDefinitionParser implements BeanDefinitionParser {
    @Override
    public void parse(Element element, Map<String,BeanDefinition> beanDefinitionMap) {
        BeanDefinition bean = new BeanDefinition();
        String id = element.attributeValue("id");
        String classname = element.attributeValue("class");
        bean.setId(id);
        bean.setClassName(classname);
        String initMethod = element.attributeValue("init-method");
        bean.setInitMethod(initMethod);
        String destroyMethod = element.attributeValue("destroy-method");
        bean.setDestroyMethod(destroyMethod);
        //todo 后续如果bean下面的子节点增加得很多,这里可以再进行改造
        /**
         * constructor-arg
         */
        List<Element> constructorArgs = element.elements("constructor-arg");
        if(null != constructorArgs && constructorArgs.size()>0){
            processConstructorArgs(bean,constructorArgs);
        }
        /**
         * property
         */
        List<Element> properties = element.elements("property");
        if(null != properties && properties.size()>0) {
            processProperties(bean,properties);
        }
        if(beanDefinitionMap.containsKey(id)){
            throw new RuntimeException("repeat bean definition on "+id);
        }
        beanDefinitionMap.put(id,bean);
    }

    private void processProperties(BeanDefinition bean, List<Element> properties) {
        for(Element e : properties){
            Property p = new Property();
            String name = e.attributeValue("name");
            String value = e.attributeValue("value");
            String ref = e.attributeValue("ref");
            p.setName(name);
            p.setValue(value);
            p.setRef(ref);
            bean.getProperties().add(p);
        }
    }

    private void processConstructorArgs(BeanDefinition bean, List<Element> constructorArgs) {
        for(Element e : constructorArgs){
            ConstructorArg arg = new ConstructorArg();
            String name = e.attributeValue("name");
            String value = e.attributeValue("value");
            arg.setName(name);
            arg.setValue(value);
            bean.getConstructorArgList().add(arg);
        }
    }
}

然后在工厂中注册要解析的标签与对应的parser关系:
public class StrategyFactory {

    /**
     * 存放bean定义对应的解析parser
     */
    private static ConcurrentHashMap<String,BeanDefinitionParser> beanDefinitionParserMap = new ConcurrentHashMap<String,BeanDefinitionParser>();

    static {
        //初始化各个定义对应的parser
        beanDefinitionParserMap.putIfAbsent("bean",new CommonBeanDefinitionParser());
    }

    /**
     * 获取元素名称对应的parser
     * @param definitionName
     * @return
     */
    public static BeanDefinitionParser getBeanDefinitionParser(String definitionName){
        return beanDefinitionParserMap.get(definitionName);
    }

}

上面已经有了beanDefinition,接下来就应该是bean的实例化了,实例化的bean我们放在一个hashmap中,方便程序获取bean:
public ClassPathXmlApplicationContext(String configXmlLocation) throws Exception{
        /**
         * 1,parse xml
         */
        beanDefinitionMap = XmlParser.parse(configXmlLocation);
        /**
         * //init instance
         */
        for(String key : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(key);
            Object bean = createBean(beanDefinition);//这里暂时没有支持init-method,如有支持,应该要调用init method
            beanMap.put(key, bean);
        }
    }

private Object createBean(BeanDefinition beanDefinition) throws Exception {//通过反射的方式创建一个实例
        String className = beanDefinition.getClassName();
        Class cls = Class.forName(className);
        //constructor args?
        List<ConstructorArg> constructorArgs = beanDefinition.getConstructorArgList();
        List<Property> properties = beanDefinition.getProperties();
        if(null != constructorArgs && constructorArgs.size()>0){
            List<Class> argTypes = new ArrayList<Class>();
            Constructor[] constructors = cls.getDeclaredConstructors();
            for(ConstructorArg arg : constructorArgs){
                Field field = cls.getDeclaredField(arg.getName());
                argTypes.add(field.getType());
            }
            Constructor ct = null;
            for(int i=0;i<constructors.length;i++){
                Constructor constructor = constructors[i];
                Class[] classes = constructor.getParameterTypes();
                if(classes.length == argTypes.size()){
                    int j = 0;
                    for(j=0;j<classes.length;j++){
                        if(!argTypes.get(j).getName().equals(classes[j].getName())){
                            break;
                        }
                    }
                    if(j == classes.length){
                        ct = constructor;
                        break;
                    }
                }
            }
            Object[] obj = new Object[argTypes.size()];
            int i = 0;
            for(ConstructorArg arg : constructorArgs){
                obj[i++] = arg.getValue();
            }
            Object object = ct.newInstance(obj);
            return object;
        }else if(properties != null && properties.size() > 0){
            Object object = cls.newInstance();
            for(Property property : properties){
                String name = property.getName();
                String value = property.getValue();
                String ref = property.getRef();
                //get method
                Method method = getMethod(cls,name);
                if(value != null){
                    method.invoke(object,value);
                }else if(null != ref && !"".equals(ref.trim())){
                    Object bean = beanMap.get(ref);
                    if(null == bean) {
                        bean = createBean(beanDefinitionMap.get(ref));
                    }
                    method.invoke(object,bean);
                    return  object;
                }
            }
        }

        return cls.newInstance();
    }

上面已经将创建好的实例放入到beanMap中,接下来就是实现beanFactory接口的功能了:
/**
     * 获取bean
     * @param beanName
     * @return
     */
    public Object getBean(String beanName){
        return beanMap.get(beanName);
    }

至此,前三个问题已经得到解决了!但是,有没有感觉不爽呢,程序员还得去xml挨个定义哪些bean由ioc容器创建,以后每新增一个类,都要去xml中配置一下,多累呀!这样的xml配置方式就显得鸡肋了,有木有更简单的办法能让容器知道这个类在创建对象以及管理对象都应该交由ioc容器来做呢?有的,这时候,java的annotation就派上用场了,我们首先自定义一个注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
    String value() default "";
}
然后新写一个类用于测试:
@MyService
public class MyUserService {

    public void print(){
        System.out.println("my user service use annotation MyService");
    }
}
在applicationContext.xml中加入一行:<component-scan base-package="com.my.ioc"/> 扫描com.my.ioc目录下以及子目录下所有的class文件
因为新加入了一个标签,自然的,得新加一个parser:
public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {
    @Override
    public void parse(Element element, Map<String, BeanDefinition> beanDefinitionMap) {
        String basePackage = element.attributeValue("base-package");
        if(null != basePackage && !"".equals(basePackage.trim())){
            List<String>classes = readClassesFromPackage(basePackage);
            for(String s : classes){
                Class cls = null;
                try {
                    cls = Class.forName(s);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }

                if(cls.isAnnotationPresent(MyService.class)){
                    MyService value = (MyService) cls.getAnnotation(MyService.class);
                    String name = value.value();
                    if(null == name || "".equalsIgnoreCase(name.toString())){
                        String[] str = s.split("\\.");
                        name = str[str.length-1];
                        name = name.substring(0,1).toLowerCase()+name.substring(1);
                        BeanDefinition bean = new BeanDefinition();
                        bean.setClassName(s);
                        bean.setId(name);
                        if(beanDefinitionMap.containsKey(name))
                            throw new RuntimeException("repeat definition name:"+name);
                        beanDefinitionMap.put(name,bean);
                    }
                }
            }
        }
    }

    private List<String> readClassesFromPackage(String basePackage) {
        ArrayList<String>classList = new ArrayList<String>();
        basePackage = basePackage.replace(".","\\");
        String path = System.getProperty("java.class.path");
        String[] paths = path.split(";");
        String currentDir = System.getProperty("user.dir");
        for(String s : paths){
            if(!s.endsWith(".jar") && s.contains(currentDir)){//只有一个目录
                path = s;
                break;
            }
        }
        String dirPath = path+"\\"+basePackage;
        List<String> classes = new ArrayList<String>();
        getFiles(dirPath,classes);
        for(String s : classes){
            s = s.replace(path+"\\","");
            s = s.replace("\\",".");
            s = s.replace(".class","");
            classList.add(s);
        }
        return classList;
    }

    private void getFiles(String path,List<String>classes){
        File file = new File(path);
        if(file.exists()){
            File[] fileList = file.listFiles();
            if(null != fileList) {
                for(File f : fileList){
                    if(f.isDirectory()){
                        getFiles(f.getAbsolutePath(),classes);
                    }else{
                        String fileName = f.getName();
                        if(fileName.endsWith(".class")){
                            classes.add(f.toString());
                        }
                    }
                }
            }
        }
    }
}



下面看下第四个问题,bean的生命周期:

bean的生命周期是依附于容器的,容器什么时候该进行初始化,什么时候该关闭是首要考虑的问题。现在的web应用大多部署在web服务器下,本文以tomcat为例,为我们的ioc

提供宿主环境,下面看下ioc容器该如何进行初始化与关闭.

我们知道tomcat在启动时会去加载web.xml,解析并创建与初始化web.xml中的servlet,借助于这个动作,我们自定义一个servlet,将其在web.xml配置下,利用这个servlet的

init方法和destroy方法来处理bean(ioc容器)的生命周期。首先定义一个servlet,在init方法内调用上面ClassPathXmlApplicationContext构造函数,在destroy方法中执行关闭

操作。这里有个问题,上面我们定义的BeanFactory,它的作用是约束所有的容器行为,都必须提供getBean方法,仅此而已,它不应该关心容器的关闭。基于此,对上面的代码

进行改造,新写一个抽象类,名字就叫AbastractApplicationContext吧:
public abstract  class AbastractApplicationContext implements BeanFactory {

    protected Map<String,Object> beanMap = new HashMap<String,Object>();//存放创建的bean

    protected Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();

    /**
     * 处理容器关闭
     */
    public void close(){
        for(String key : beanDefinitionMap.keySet()){
            BeanDefinition definition = beanDefinitionMap.get(key);
            String destroyMethod = definition.getDestroyMethod();
            if(null != destroyMethod && !"".equals(destroyMethod.trim())){
                //反射调用对象的destroy方法
            }
        }

        beanMap.clear();
        beanDefinitionMap.clear();
        beanMap = null;
        beanDefinitionMap = null;
    }
}

这时ClassPathXmlApplicationContext只需继承它即可:
public class ClassPathXmlApplicationContext extends AbastractApplicationContext{
...........
}

回到上面,这时我们的servlet也就清晰了:
public class IocServlet implements Servlet {

    /**
     * 这里本不应该硬编码的,应该作为一项配置放在web.xml中,为了方便测试,在此先写死
     */
    private static final String APPLICATION_CONTEXT_XML = "applicationContext.xml";

    private AbastractApplicationContext abastractApplicationContext;

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        try{
            abastractApplicationContext = new ClassPathXmlApplicationContext(APPLICATION_CONTEXT_XML);
            BeanUtils.setAbastractApplicationContext(abastractApplicationContext);//供外部调用时用
        }catch (Exception e){
            e.printStackTrace();
            throw new ServletException("init failed");
        }
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {

    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {
        if(null != abastractApplicationContext){
            abastractApplicationContext.close();
        }
    }
}
public class BeanUtils {

    private static AbastractApplicationContext applicationContext;

    public static void setAbastractApplicationContext(AbastractApplicationContext context){
        synchronized (BeanUtils.class){
            if(null == applicationContext){
                synchronized (BeanUtils.class){
                    applicationContext = context;
                }
            }
        }
    }

    public static Object getBean(String beanName){
        return ((BeanFactory)applicationContext).getBean(beanName);
    }
}

最后,在web.xml加入我们servlet配置
<servlet>  
          
        <servlet-name>iocServlet</servlet-name>  
        <servlet-class>com.my.ioc.IocServlet</servlet-class>  
        <!-- load-on-startup:表示启动容器时初始化该Servlet; -->  
        <load-on-startup>1</load-on-startup>  
    </servlet> 

至此,我们的ioc容器算是告一段落了,回过头来,再看下上面的代码,有诸多地方需要完善,如细节方面,线程安全问题,功能方面支持更多的标签解析等等都是可以改进的。
从大方向来说,随着需求的变化(想到的更多问题或满足更多功能),改进或者完善主要应该从解耦,抽取其中的共性,便于扩展这几个方面着重考虑。个人认为,spring那一坨
庞大的代码主要还是围绕这几个考虑方面进行的代码编写。

以上java代码均已上传:https://github.com/reverence/myioc,在windows下测试通过

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐