学习记录03——模仿spring的<context:component-scan/>标签和写一个简单的ioc容器

  学习spring时,曾经试过因为没有在xml中配<context:component-scan base-package="xxx" />,导致标注了@Component、Controller、Service也找不到bean,于是对这个功能起了兴趣,试着自己尝试在xml中配置标签后通过一个注解去扫包,扫完后通过反射创建对象放到自己写的ioc容器中,也算是做了一个简易版ioc容器。(底层只是一个map)

  先写一个朴实无华的xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <componentScan package="com.joker"/>

</beans>

写一个类解析此xml:

public class MyXmlParser {

    public File parseConfig() throws ParserConfigurationException, IOException, SAXException {
        return parseConfig("applicationContext.xml");
    }

    public File parseConfig(String resourcePath) throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        String scanPackage = null;

        //创建DocumentBuilder对象
        DocumentBuilder db = dbf.newDocumentBuilder();
        //通过DocumentBuilder对象的parser方法解析applicationContext.xml文件
        InputStream is = getClass().getClassLoader().getResourceAsStream(resourcePath);
        Document document = db.parse(is);
        //获得<componentScan></componentScan>标签内容
        NodeList node = document.getElementsByTagName("componentScan");

        Node item = node.item(0);
        if (item.getNodeType() == Node.ELEMENT_NODE) {
            Element element = (Element) item;
            //将<componentScan>标签中的package=xxx拿到
            scanPackage = element.getAttribute("package");

        }
        List<String> fileNames = new ArrayList<>();
        //com.joker ===> com/joker,变成文件路径形式
        String packagePath = scanPackage == null ? "" : scanPackage.replace(".", "/");
        Enumeration<URL> urls = getClass().getClassLoader().getResources(packagePath);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            String path = url.toString();
            if (url == null)
                continue;
            //String type = url.getProtocol();
            String realPath = path.substring(6);
            return new File(realPath);
        }
        return null;
    }
}

  debug时获取到的信息如下,至此,MyXmlParser中的方法已经可以返回一个路径为xml中写的包路径的File对象了,至于为什么要返回File对象,因为后面要用到

   现在知道了包的所在路径,但如何找到它下面的所有类名呢,所以要再写一个类,写一个核心方法,也是一个递归方法,找到他的子子孙孙包下的所有.class文件:

public class MyComponentScan {

    private List<String> classNames = new ArrayList<>();

    public List<String> getClassNames() {
        return classNames;
    }

    public MyComponentScan(){

    }

    public MyComponentScan(File file){
        getClassesRecursion(file);
    }

    public void getClassesRecursion(File file){

        File[] files = file.listFiles();

        for (File childFile : files) {
            if (childFile.isDirectory()) {
                //如果该包下还有包,递归调用自己
                getClassesRecursion(childFile);

            } else {
                String childFilePath = childFile.getPath();
                //C:\Users\joker\Desktop\course\spring\Aop\target\classes\com\joker\myspring\MyComponentScan.class
                if (childFilePath.endsWith(".class")) {
                    //windows系统路径为反斜杠,类Unix系统要写"/"
                    childFilePath = childFilePath.substring(childFilePath.lastIndexOf("classes")+8, childFilePath.lastIndexOf("."));//com\joker\myspring\MyComponentScan.class
                    childFilePath = childFilePath.replace("\\",".");
                    classNames.add(childFilePath);
                }
            }
        }
    }
}

我的包结构如下:

 

写个测试类看看拿到了没:

 

  拿到此包下所有类的名字后,就剩最后一步了:模仿spring的ioc容器,自己写一个简易版ioc容器,首先是四个注解,为了区分加上了My前缀,这里只列出一个,毕竟四个的功能是一样的,剩下的MyController、Myrepository、MyService只是名字不同,内容都一样(这里被Retention这个元注解坑过,不写runtime无法通过反射获取此注解):

package com.joker.stereotype;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyComponent {

    String[] beanId() default "";
}

  毕竟是自己写的简易版,我这个注解必须写bean的id,否则默认没有,后面取放bean时就很麻烦。

  然后是简易版IOC容器,底层是一个map,通过反射获取类的注解后new对象放入map中,id作为键,对象作为值,这里跟spring同理,默认调用类的空参构造器,没有的话会报错:

public class MyIocContainer {
    Map<String,Object> bean = new HashMap();

    public MyIocContainer(List<String> classNames) throws Exception {

        for (String className : classNames) {
            Class<?> clazz = Class.forName(className);

            Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();

            for (Annotation anno : declaredAnnotations) {
                Class<? extends Annotation> annotation = anno.annotationType();
//            MyComponent myComponent = UserDaoImpl.class.getAnnotation(MyComponent.class);

                if(     annotation.getName().equals("com.joker.stereotype.MyComponent") ||
                        annotation.getName().equals("com.joker.stereotype.MyRepository") ||
                        annotation.getName().equals("com.joker.stereotype.MyService") ||
                        annotation.getName().equals("com.joker.stereotype.MyController")
                ){
                    //确认到该类注解为@MyComponent之后,将其强转,为的是能调用其方法,获取其成员变量的值(调用beanId()方法)
                    MyComponent myComponent = (MyComponent)anno;
                    String[] beanIds = myComponent.beanId();

                    for (int i = 0; i < beanIds.length; i++) {
                        if (i != 0){
                            //当有多个id时,防止反复创建对象,取出ioc容器中已有的对象即可
                            Object bean = getBean(beanIds[i - 1]);
                            setBean(beanIds[i],bean);
                            continue;
                        }
                        //将beanId做为键,new出来的对象作为值,放到我自己的"ioc容器"中
                        setBean(beanIds[i],clazz.getDeclaredConstructor().newInstance());
                    }
                }
            }
        }


    }

    public Object getBean(String beanId){
        return bean.get(beanId);
    }

    public void setBean(String beanId,Object bean){
        this.bean.put(beanId,bean);
    }
}

  最后在最开始写的那个xml中的包(com.joker)中的随意一个类上标上注解,试试看。没加注解或者不在该包下的类自然拿不到:

 

 乞丐版springIOC容器和扫包功能完成

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值