带你简化理解Spring 基于注解配置的原理

1 需求说明

  1. 自己写一个简单的 Spring 容器, 通过读取类的注解 (@Component @Controller @Service @Reponsitory),将对象注入到 IOC 容器

bean id= MyComponent bean 对象=
com.hj.spring.component.MyComponent@13221655

bean id= UserDao bean 对象= com.hj.spring.component.UserDao@2f2c9b19
bean id= UserService bean 对象=
com.hj.spring.component.UserService@31befd9f

bean id= UserController bean 对象=
com.hj.spring.component.UserController@1c20c684 i am user da

  1. 我们不使用 Spring 原生框架,自己手写 IO+Annotaion+反射+集合 技术实现, 打通 Spring 注解方式开发的技术难点

2 思路分析

  1. 思路分析+程序结构

    • 我们使用注解方式完成, 不使用 xml 来配置

    • 程序框架图
      在这里插入图片描述

3 代码实现

● 应用实例

  1. 手动实现注解的方式来配置 Controller / Service / Respository / Component
  2. 我们使用自定义注解来完成.

● 代码实现
代码结构:
在这里插入图片描述

  1. 创建 ComponentScan.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 解读
 * 1. @Target(ElementType.TYPE)指定我们的ComponentScan注解可以修饰 Type程序元素
 * 2. @Retention(RetentionPolicy.RUNTIME) 指定ComponentScan注解 保留范围
 * 3. String value() default ""; 表示ComponentScan 可以传入 value
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
    String value() default "";
}
  1. 创建 HJSpringConfig.java
/**
 * @author hj
 * @version 1.0
 * 这是一个配置类, 作用类似我们原生spring的 beans.xml 容器配置文件
 */
@ComponentScan(value = "com.hj.springboot_06_ssmp.component")
public class HJSpringConfig {
}
  1. 创建 SpringApplicationContext.java
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author hj
 * SpringApplicationContext 类的作用类似Spring原生ioc容器
 */
public class SpringApplicationContext {
    private Class configClass;
    //ioc我存放的就是通过反射创建的对象(基于注解方式)
    private final ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();

    public SpringApplicationContext(Class configClass) {
        this.configClass = configClass;

        //获取要扫描的包
        //1. 先得到HJSpringConfig配置的的@ComponentScan(value = "com.hj.springboot_06_ssmp.component")
        ComponentScan componentScan =(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);//注解
        System.out.println("注解内容= " + componentScan);

        //2. 通过componentScan的value=> 即要扫描的包
        String path = componentScan.value();
        System.out.println("要扫描的包= " + path);
    }

    public static void main(String[] args) {
        SpringApplicationContext springApplicationContext = new SpringApplicationContext(HJSpringConfig.class);

    }
}

第一步:获取要扫描的包

//1. 先得到HJSpringConfig配置的的@ComponentScan(value = "com.hj.springboot_06_ssmp.component")
ComponentScan componentScan =(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);//注解
System.out.println("注解内容= " + componentScan);

//2. 通过componentScan的value=> 即要扫描的包
String path = componentScan.value();
System.out.println("要扫描的包= " + path);

运行结果:

注解内容= @com.hj.springboot_06_ssmp.annotation.ComponentScan(value=com.hj.springboot_06_ssmp.component)
要扫描的包= com.hj.springboot_06_ssmp.component

第二步:得到要扫描的包下的所有资源(类.class)

//1.得到类的加载器
ClassLoader classLoader = SpringApplicationContext.class.getClassLoader();

//2. 通过类的加载器获取到要扫描的包的资源 url=》类似一个路径
path = path.replace(".", "/");//一定要把. 替换成 /
URL resource = classLoader.getResource(path);
System.out.println("resource=" + resource);

运行结果:

resource=file:/F:/hspJava/SpringBoot/springboot/springboot_06_ssmp/target/classes/com/hj/springboot_06_ssmp/component

注意这里得到的资源路径是对应target文件下的:
在这里插入图片描述

//3. 将要加载的资源(.class) 路径下的文件进行遍历=>io
File file = new File(resource.getFile());//目录也是一个文件
System.out.println(file.isDirectory());
if(file.isDirectory()) {
    File[] files = file.listFiles();
    for (File f : files) {
        System.out.println("=======================");
        System.out.println(f.getAbsolutePath());
      //F:\hspJava\SpringBoot\springboot\springboot_06_ssmp\target\classes\com\hj\springboot_06_ssmp\component\UserService.class
        //获取到 com.hj.spring.component.UserService
        String fileAbsolutePath = f.getAbsolutePath();
    }
}

运行结果:
在这里插入图片描述

//这里我们只处理.class文件
if (fileAbsolutePath.endsWith(".class")) {
    //1. 获取到类名
    String className =
            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
    System.out.println("className=" + className);

    //2. 获取类的完整的路径(全类名)
    //老师解读 path.replace("/",".") => com.hspedu.spring.component.
    String classFullName = path.replace("/", ".") + "." + className;
    System.out.println("classFullName=" + classFullName);

    //3. 判断该类是不是需要注入容器, 就看该类是不是有注解 @Component @Service..
    try {
        //这时,我们就得到该类的Class对象
        //Class clazz = Class.forName(classFullName)
        //老师说一下
        //1. Class clazz = Class.forName(classFullName) 可以反射加载类
        //2. classLoader.loadClass(classFullName); 可以反射类的Class
        //3. 区别是 : 上面方式后调用该类的静态方法, 下面方法不会
        //4. aClass.isAnnotationPresent(Component.class) 判断该类是否有 @Component
        Class<?> aClass = classLoader.loadClass(classFullName);
        if (aClass.isAnnotationPresent(Component.class) ||
                aClass.isAnnotationPresent(Controller.class) ||
                aClass.isAnnotationPresent(Service.class) ||
                aClass.isAnnotationPresent(Repository.class)) {

            //这里老师演示一个Component注解指定value,分配id
            //老师就是演示了一下机制.
            if(aClass.isAnnotationPresent(Component.class)) {
                //获取到该注解
                Component component = aClass.getDeclaredAnnotation(Component.class);
                String id = component.value();
                if(!"".endsWith(id)) {//不为空
                    className = id;//替换
                }
            }

            //这时就可以反射对象,并放入到容器中
            Class<?> clazz = Class.forName(classFullName);
            Object instance = clazz.newInstance();

            //放入到容器中, 将类名的首字母小写作为id
            //StringUtils
            ioc.put(StringUtils.uncapitalize(className) , instance);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

4. 总代码

/**
 * @author hj
 * SpringApplicationContext 类的作用类似Spring原生ioc容器
 */
public class SpringApplicationContext {
    private Class configClass;
    //ioc我存放的就是通过反射创建的对象(基于注解方式)
    private final ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();

    public SpringApplicationContext(Class configClass) {
        this.configClass = configClass;

        //获取要扫描的包
        //1. 先得到HJSpringConfig配置的的@ComponentScan(value = "com.hj.springboot_06_ssmp.component")
        ComponentScan componentScan =(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);//注解
        System.out.println("注解内容= " + componentScan);

        //2. 通过componentScan的value=> 即要扫描的包
        String path = componentScan.value();
        System.out.println("要扫描的包= " + path);

        //得到要扫描的包下的所有资源(类.class)
        //1.得到类的加载器
        ClassLoader classLoader = SpringApplicationContext.class.getClassLoader();

        //2. 通过类的加载器获取到要扫描的包的资源 url=》类似一个路径
        path = path.replace(".", "/");//一定要把. 替换成 /
        URL resource = classLoader.getResource(path);//得到全路径
        System.out.println("resource=" + resource);
        System.out.println(resource.getFile());

        //3. 将要加载的资源(.class) 路径下的文件进行遍历=>io
        File file = new File(resource.getFile());//目录也是一个文件
        System.out.println(file.isDirectory());
        if(file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                System.out.println("=======================");
                System.out.println(f.getAbsolutePath());
                //F:\hspJava\SpringBoot\springboot\springboot_06_ssmp\target\classes\com\hj\springboot_06_ssmp\component\UserService.class
                //获取到 com.hj.spring.component.UserService
                String fileAbsolutePath = f.getAbsolutePath();

                //这里我们只处理.class文件
                if (fileAbsolutePath.endsWith(".class")) {
                    //1. 获取到类名
                    String className =
                            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    System.out.println("className=" + className);

                    //2. 获取类的完整的路径(全类名)
                    //老师解读 path.replace("/",".") => com.hspedu.spring.component.
                    String classFullName = path.replace("/", ".") + "." + className;
                    System.out.println("classFullName=" + classFullName);

                    //3. 判断该类是不是需要注入容器, 就看该类是不是有注解 @Component @Service..
                    try {
                        //这时,我们就得到该类的Class对象
                        //Class clazz = Class.forName(classFullName)
                        //老师说一下
                        //1. Class clazz = Class.forName(classFullName) 可以反射加载类
                        //2. classLoader.loadClass(classFullName); 可以反射类的Class
                        //3. 区别是 : 上面方式后调用该类的静态方法, 下面方法不会
                        //4. aClass.isAnnotationPresent(Component.class) 判断该类是否有 @Component
                        Class<?> aClass = classLoader.loadClass(classFullName);
                        if (aClass.isAnnotationPresent(Component.class) ||
                                aClass.isAnnotationPresent(Controller.class) ||
                                aClass.isAnnotationPresent(Service.class) ||
                                aClass.isAnnotationPresent(Repository.class)) {

                            //这里老师演示一个Component注解指定value,分配id
                            //老师就是演示了一下机制.
                            if(aClass.isAnnotationPresent(Component.class)) {
                                //获取到该注解
                                Component component = aClass.getDeclaredAnnotation(Component.class);
                                String id = component.value();
                                if(!"".endsWith(id)) {//不为空
                                    className = id;//替换
                                }
                            }

                            //这时就可以反射对象,并放入到容器中
                            Class<?> clazz = Class.forName(classFullName);
                            Object instance = clazz.newInstance();

                            //放入到容器中, 将类名的首字母小写作为id
                            //StringUtils
                            ioc.put(StringUtils.uncapitalize(className) , instance);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    //编写方法返回对容器中对象
    public Object getBean(String name) {
        return ioc.get(name);
    }

    public static void main(String[] args) {
        SpringApplicationContext springApplicationContext = new SpringApplicationContext(HJSpringConfig.class);

    }
}

最后如果你觉得这篇文章对你有帮助的话,请转发、收藏和点赞哦!谢谢支持!👍👍👍
纸上得来终觉浅,绝知此事要躬行。希望大家不要光看,还要多练,自己去实现比你想明白更加的有趣!😜😜😜

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值