手动开发--简单的 Spring 基于注解配置的程序

80 阅读3分钟

手动开发--简单的 Spring 基于注解配置的程序

需求说明

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

也就是说,不使用 Spring 原生框架,我们自己使用 IO+Annotaion+反射+集合 技术实现

思路分析+程序结构

1) 我们使用注解方式完成, 这里不用 xml 来配置

2) 程序框架图

 

● 应用实例

1. 手动实现注解的方式来配置 Controller / Service / Respository / Component

2. 我们使用自定义注解来完成.

创建ComponentScan.java注解

作用

可以指定要扫描包 

类似于component--scan 扫描

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 "";
}

创建WyxSpringConfig

这是一个配置类, 作用类似我们原生spring的 beans.xml 容器配置文件

@ComponentScan(value = "com.spring.component")
public class WyxSpringConfig {
}

创建WyxSpringApplicationContext

 作用

Spring容器

通过WyxSpringConfig 进行初始化

在这个容器中会创建ComponentScan注解指定的包并生成对象

public class WyxSpringApplicationContext {
    private Class configClass;
    //ioc我存放的就是通过反射创建的对象(基于注解方式)
    private final ConcurrentHashMap<String, Object> ioc =
            new ConcurrentHashMap<>();

    //构造器
    public WyxSpringApplicationContext(Class configClass) {

        this.configClass = configClass;
        //System.out.println("this.configClass=" + this.configClass);
        //获取要扫描的包
        //1. 先得到WyxpringConfig配置的的@ComponentScan(value = "com.spring.component")
        ComponentScan componentScan =
                (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2. 通过componentScan的value=> 即要扫描的包
        String path = componentScan.value();
        System.out.println("要扫描的包= " + path);

        //得到要扫描的包下的所有资源(类 .class)
        //1.得到类的加载器
        ClassLoader classLoader =
                WyxApplicationContext.class.getClassLoader();
        //2. 通过类的加载器获取到要扫描的包的资源 url=》类似一个路径
        path = path.replace(".", "/");//一定要把. 替换成 /
        URL resource =
                classLoader.getResource(path);
        System.out.println("resource=" + resource);
        //3. 将要加载的资源(.class) 路径下的文件进行遍历=>io
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                System.out.println("=====================");
                System.out.println("=" + f.getAbsolutePath());
                //D:\xxx_spring\spring\out\production\spring\com\spring\component\UserService.class
                //获取到 com.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.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);
    }
}

注意

获取全类名的步骤

1. 获取到类名

StringclassName=

fileAbsolutePath.substring

(fileAbsolutePath.lastIndexOf("\")+1.fileAbsolutePath.indexOf(".class"));

2. 获取类的完整的路径(全类名)

解读 path.replace("/",".") => com.spring.component.
String classFullName = path.replace("/", ".") + "." + className;

3. 判断该类是不是需要注入容器, 就看该类是不是有注解 @Component @Service..

Class.forName和Class.loadClass的区别

1. Class clazz = Class.forName(classFullName) 可以反射加载类
2. classLoader.loadClass(classFullName); 可以反射类的Class
3. 区别是 : 上面方式后会调用 类的静态方法, 下面方法不会调用 1.