模仿spring实现简易组件扫描,依赖注入

5 篇文章 0 订阅

一.前言

大家好,最近学校让写一个控制台的饿了么点餐项目,但是要求用mvc模式,没学spring框架之前,我们一般在要注入的类中主动new对象,像这样: UserService userservice = new UserService();

 这样其实不好,而且在一些类中每次都新建对象,当该属性需要依赖其他对象时,其他对象还没创建,就会引起失败,于是我模仿spring简易的实现了一个依赖注入的小型框架,我给它起名为:

Winter

二.@ComponentScan,@Component,@Autowired注解定义

我们主要模拟实现该三个注解的基本功能,我们先把这三个注解定义出来:

/**
 * @author JJH
 */
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

/**
 * @author JJH
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}


/**
 * 组件扫描注解
 * @author JJH
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {

    String[] packagePath();

}

三.WinterUtils工具类

该工具类主要是用来扫描包下的.class文件,返回,以及获取类名等功能

 

/**
 * winter框架提供的工具类
 * @author JJH
 */
public class WinterUtils {
      private static final String WINTER_BANNER = "src/main/resources/banner.txt";

    /**
     * 返回当前时间
     * @return
     */
    public static String getNowTime(){

       return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());

    }




    /**
     * 打印winterLOGO
     */
    public static void printWinterLogo() {

        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(WINTER_BANNER)));
            String msg = null;
            while ((msg = reader.readLine()) != null) {
                System.out.println(msg);
            }


        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     *  从文件路径截取出类名
     */
    public static String getClassName(String path){
        String replace = path.replace("\\", ".");
        int index = replace.lastIndexOf("classes");
        String substring = replace.substring(index+8);
        String classname = substring.substring(0, substring.lastIndexOf("class")-1);
        return classname;
    }


    /**
     *  递归遍历所有文件
     */
    public static void listFile(File file, List<File> fileList){
        if (!file.isDirectory()) {
            fileList.add(file);
        }else {
            File[] files = file.listFiles();
            for (File subFile : files) {
                listFile(subFile,fileList);
            }
        }
    }

    /**
     * 根据包名数组获取.class文件
     *
     * @param packagePaths 包名路径数组
     */
    public static List<File> getClassFileByPackage(String[] packagePaths){
        List<File> fileList = new ArrayList<>();
        ArrayList<File> classFileList = new ArrayList<>();
        for (String packge : packagePaths) {
            //把对应包名下的文件中含有@Component注解的类解析了,然后创建对象
            String replace = packge.replace(".", "/");
            URL url = ClassLoader.getSystemResource(replace);
            String path = url.getPath();
            //找该目录下的所有.class文件
            File file = new File(path);
            listFile(file,fileList);
            for (File fiLe : fileList) {
                String name = fiLe.getName();
                if(name.endsWith(".class")){
                    classFileList.add(fiLe);
                }
            }
        }
        return classFileList;
    }


    /**
     * 返回类路径下的所有.class文件
     */
    public static List<File> getClassFiles(){
        URL url = Thread.currentThread().getContextClassLoader().getResource("");

        String path = url.getPath();
        File file = new File(path);
        List<File> fileList = new ArrayList<>();
        listFile(file,fileList);
        ArrayList<File> classFileList = new ArrayList<>();
        for (File fiLe : fileList) {
            String name = fiLe.getName();
            if(name.endsWith(".class")){
                classFileList.add(fiLe);
            }
        }
        return classFileList;
    }

    /**
     * 获取类上有@Component该注解的类文件集合
     * @return 类文件集合
     */
    public static List<File> getClassFileByAnnatation(Class<Component> componentClass) {
        List<File> list = new ArrayList<>();
        List<File> classFiles = getClassFiles();
        for (File classFile : classFiles) {
            String path = classFile.getPath();
            String className = getClassName(path);
            Class<?> aClass = null;
            try {
                aClass = Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            Annotation annotation = aClass.getAnnotation(componentClass);
            if(annotation!=null){
                //表示该类上有该注解,可以把该文件装入集合中
                list.add(classFile);
            }
        }
       return list;
    }
}

三.模仿spring容器ApplicationContext

ApplicationContext中应该存bean的map集合,而且还是单例的,我们可以这样写:

/**
 * 模拟spring制作一个winter框架
 * spring是春天的意思,
 * 那么winter就是冬天的意思
 * @author JJH
 *
 *         winterframework
 *
 */
public class ApplicationContext {

    /**
     * 最核心的应该是bean的map集合了
     */
    private static Map<String,Object> beanMap = new ConcurrentHashMap<>();

    public ApplicationContext(){

    }

    /**
     * 模拟run方法
     */
    public static ApplicationContext run(){
        WinterUtils.printWinterLogo();

        System.out.println("LOG: "+WinterUtils.getNowTime()+":   winterframework start success!");

        ApplicationContext context = new ApplicationContext();
        //先把注解解析类注册为bean
        context.registerBean("annotationProcess",new CommonAnnotationProcess());
        context.registerBean("autowiredProcess",new AutoWiredProcess());
        return context;
    }

    /**
     * 向map中添加bean
     * @param beanName bean名称
     * @param bean bean对象
     * @return 是否添加成功
     */
    public boolean registerBean(String beanName,Object bean){
        beanMap.put(beanName,bean);
        return true;
    }

    /**
     * 根据类型获取bean对象
     * @param clazz
     * @return
     */
    public Object getBean(Class<?> clazz){
        String clazzName = clazz.getName();
        Object[] objects = new Object[1];
        beanMap.forEach((name,bean)->{
            if(bean.getClass().getName().equals(clazzName)){
               objects[0] = bean;
            }
        });
        return objects[0];
    }
    /**
     * 根据bean的名称获取bean对象
     */
    public Object getBean(String name){
        Object[] objects = new Object[1];
        beanMap.forEach((id,bean)->{
           if(id.equals(name)){
               objects[0] = bean;
           }
        });
        return objects[0];
    }

    /**
     * 通过静态方法获取bean对象
     * @return
     */
    public static Map<String,Object> getBeans(){
        return beanMap;
    }

    /**
     * 通过this获取
     * @return
     */
    public Map<String,Object> getAllBean(){
        return beanMap;
    }

    /**
     * 静态方法获取bean
     * @param clazz
     * @return
     */
    public static Object GetBean(Class<?> clazz){

        Object[] objects = new Object[1];
        Map<String, Object> beans = getBeans();
        beans.forEach((k,v)->{
            if(v.getClass().equals(clazz)){
                objects[0] = v;
            }
        });
        return objects[0];
    }


}

我们在ApplicationContext中声明了registerBean(),getBean(),getBean(String name)等基本方法

 来注册bean,根据类型,名称获取bean

注意: 

  我们在该容器的构造方法里,已经提前加入了对@ComponentScan,@Component,@Autowired注解解析的处理类CommonAnnotationProcess ,AutoWiredProcess ,下面我们来解析这俩处理类

四.模拟@ComponentScan,@Component实现

该注解就是组件扫描,把@Component注解的类注册为bean,下面我们来模拟实现一下:

我们创建CommonAnnotaionProcess类,用来解析@ComponentScan和@Component注解

原理:

  主要就是先获取.class文件,然后遍历每个文件,通过反射获取该类的注解信息,判断是否在类上有@ComponentScan注解,如果有,我们获取@ComponentScan注解中package的路径信息,

然后在去找该路径下的类文件上是否含有@Component注解,如果有,那就创建该类的对象,把它存入容器中的beanMap集合中

/**
 * 注解解析器,暂时先让它解析@Component  @ComponentScan
 * @author JJH
 */
public class CommonAnnotationProcess {



    public CommonAnnotationProcess(){
        //初始化时,就调用
        try {
            componetScanAnalysis();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 找到标注了@ComponentScan注解的类
     */
    public void componetScanAnalysis() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        List<File> classFiles = WinterUtils.getClassFiles();
        for (File classFile : classFiles) {
            String path = classFile.getPath();
            String className = WinterUtils.getClassName(path);
            //类加载获取类对象
            Class<?> aClass = Class.forName(className);
            ComponentScan componentScan = aClass.getAnnotation(ComponentScan.class);
            if(componentScan!=null){
                //该类上面有这个注解,要获取该注解中的包名信息
                String[] paths = componentScan.packagePath();
                List<File> clsFiles = WinterUtils.getClassFileByPackage(paths);
                componentAnalySis(clsFiles);
            }
        }

    }

    /**
     * 解析带有@Component注解的类
     * @param classFiles
     */
    public void componentAnalySis(List<File> classFiles) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        for (File file : classFiles) {
            String className = WinterUtils.getClassName(file.getPath());
            Class<?> aClass = Class.forName(className);
            Component component = aClass.getAnnotation(Component.class);
            if(component!=null){
                //可以给该类创建对象拉!哈哈哈
                Object obj = aClass.newInstance();
                //默认根据类名首字母小写作为id
                String name = aClass.getName();
                String id = name.substring(0, 1).toLowerCase() + name.substring(1);
                //添加到beanmap中去啦!
                ApplicationContext.getBeans().put(id,obj);
            }
        }
    }




    }



五.模拟@Autowired注解实现

 该注解在spring中主要是用来做依赖注入的,可以加在属性上,set方法,构造方法上,我们这里为了简单,我们只做加在属性上的实现:

原理:

 先找到被@Component注解的类,然后通过反射获取该类的属性集合,然后遍历属性集合,依次判断每个属性上是否有@Autowired注解,如果有,那么我们根据该属性的类型,去beanMap中找是否有该类型的bean,如果有,就通过getBean()拿到该bean,将他赋值给该属性

**
 *  Autowired 注解解析器
 * 目的是为了自动注入
 * 原理:
 *   先拿到注解了@Componet的类文件,
 *   然后判断该类中的属性上是否被@Autowired注解标记,
 *   如果标记了,那么就根据这个属性的类型从beanMap中
 *   查找,找到了该类型的bean对象,就把该对象赋值给该
 *   属性
 * @author JJH
 */
public class AutoWiredProcess {


    public AutoWiredProcess(){
        autowiredProcess();
    }


    /**
     * 解析属性上的autowired注解,依赖注入
     *
     */
    public void autowiredProcess() {
      //先把@componet注解的类找到再说
        List<File> comFiles = WinterUtils.getClassFileByAnnatation(Component.class);
        Map<String, Object> beans = ApplicationContext.getBeans();

        for (File comFile : comFiles) {
            //获取属性
            String className = WinterUtils.getClassName(comFile.getPath());
            Class<?> aClass = null;
            try {
                aClass = Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            Field[] fields = aClass.getDeclaredFields();
            for (Field field : fields) {
               if(field.getAnnotation(Autowired.class)!=null){
                   //属性上有@autowired注解,
                   //我们从map集合中查找它所需要的bean
                   field.setAccessible(true);
                   beans.forEach((id,bean)->{
                       if(className.endsWith(bean.getClass().getName())){
                           //类名对住的时候,就该来给他依赖注入了
                           Field[] beanfields = bean.getClass().getDeclaredFields();
                           for (Field beanfield : beanfields) {
                               Class<?> type = beanfield.getType();
                               beans.forEach((k,v)->{
                                   if(type.equals(v.getClass())){
                                       try {
                                           beanfield.setAccessible(true);
                                           beanfield.set(bean,v);
                                       } catch (IllegalAccessException e) {
                                           throw new RuntimeException(e);
                                       }
                                   }
                               });
                           }
                       }
                   });
               }
            }

        }


    }


}

 六.为框架添加Logo和简易日志

为了使框架美观,在启动时显示Logo,我们创建banner.txt文件,在该文件中写入我们想要的文字图

文字图我们可以在该网站:Text to ASCII Art Generator (TAAG) 生成我们想要的图片,我这里选取的图片为:

              $$\            $$\                                 $$\              $$$$$$\
              \__|           $$ |                              $$$$ |            $$$ __$$\
$$\  $$\  $$\ $$\ $$$$$$$\ $$$$$$\    $$$$$$\   $$$$$$\        \_$$ |            $$$$\ $$ |
$$ | $$ | $$ |$$ |$$  __$$\\_$$  _|  $$  __$$\ $$  __$$\         $$ |            $$\$$\$$ |
$$ | $$ | $$ |$$ |$$ |  $$ | $$ |    $$$$$$$$ |$$ |  \__|        $$ |            $$ \$$$$ |
$$ | $$ | $$ |$$ |$$ |  $$ | $$ |$$\ $$   ____|$$ |              $$ |            $$ |\$$$ |
\$$$$$\$$$$  |$$ |$$ |  $$ | \$$$$  |\$$$$$$$\ $$ |            $$$$$$\ $$\       \$$$$$$  /
 \_____\____/ \__|\__|  \__|  \____/  \_______|\__|            \______|\__|       \______/

我们想让框架启动时打印一下日志,我们可以在ApplicationContext的run()方法中写:

  System.out.println("LOG: "+WinterUtils.getNowTime()+":   winterframework start success!");

 工具类中写:

 /**
     * 返回当前时间
     * @return
     */
    public static String getNowTime(){

       return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());

    }

 

 七.测试框架启动

我们可以看到在ApplicationContext中有一个静态run()方法,我们先写一些pojo类,来测试我们的依赖注入:

我们创建两个类BookService,UserService,

@Component
public class BookService {


    public void getBook(){
        System.out.println("获取图书信息...");
    }



}


@Component
public class UserService {

    @Autowired
    private BookService bookService;


    public void userinfo(){
        System.out.println("获取用户信息...");
        bookService.getBook();
    }



}

可以看到,在UserService中注入了BookService,调用了bookservice的getBook()方法,两个类都加了@Component注解,下面我们创建启动类:

@ComponentScan(packagePath = {"com.jjh"})
public class TestWinter {
    public static void main(String[] args) {

        ApplicationContext context = ApplicationContext.run();

        Map<String, Object> beans = context.getAllBean();


        UserService userservice = (UserService) context.getBean(UserService.class);

        userservice.userinfo();


    }
}

 我们运行一下:

              $$\            $$\                                 $$\              $$$$$$\
              \__|           $$ |                              $$$$ |            $$$ __$$\
$$\  $$\  $$\ $$\ $$$$$$$\ $$$$$$\    $$$$$$\   $$$$$$\        \_$$ |            $$$$\ $$ |
$$ | $$ | $$ |$$ |$$  __$$\\_$$  _|  $$  __$$\ $$  __$$\         $$ |            $$\$$\$$ |
$$ | $$ | $$ |$$ |$$ |  $$ | $$ |    $$$$$$$$ |$$ |  \__|        $$ |            $$ \$$$$ |
$$ | $$ | $$ |$$ |$$ |  $$ | $$ |$$\ $$   ____|$$ |              $$ |            $$ |\$$$ |
\$$$$$\$$$$  |$$ |$$ |  $$ | \$$$$  |\$$$$$$$\ $$ |            $$$$$$\ $$\       \$$$$$$  /
 \_____\____/ \__|\__|  \__|  \____/  \_______|\__|            \______|\__|       \______/
LOG: 2023-10-07 19:38:48:   winterframework start success!
获取用户信息...
获取图书信息...

 可以看到,成功的对@Component注解解析,并对@Autowird实现了依赖注入功能!

下面我们获取容器中的所有bean:

        beans.forEach((name,bean)->{
            System.out.println(name+"="+bean);
        });
com.jjh.winterframework.test.UserService=com.jjh.winterframework.test.UserService@5702b3b1
com.jjh.winterframework.test.BookService=com.jjh.winterframework.test.BookService@69ea3742
autowiredProcess=com.jjh.winterframework.core.AutoWiredProcess@4b952a2d
annotationProcess=com.jjh.winterframework.core.CommonAnnotationProcess@3159c4b8

可以看到,我们的注解处理类,和我们通过@Component注解指定的两个类都成功被注册到了beanMap集合中,次上,我们的winter框架简易的实现成功了!

八.总结

 我们在学习时,要善于反思,并能举一反三,善于模仿。

对于其他注解作者以后有时间会逐步实现,我们下期再见!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值