背景

实现一个简单的Ioc容器

实现自定义注解

  • 自定义注解方法解读
//Target指示注释类型适用的上下文
//SOURCE public static final RetentionPolicy SOURCE注释将被编译器丢弃。
//CLASS public static final RetentionPolicy CLASS注释将由编译器记录在类文件中,但VM不需要在运行时保留。 这是默认行为。
//RUNTIME public static final RetentionPolicy RUNTIME注释将由编译器记录在类文件中,并由VM在运行时保留,因此可以反射读取。
//ElementType 这个枚举类型的常量提供了在Java程序中可能出现注释的句法位置的简单分类。 在java.lang.annotation.Target元注释中使用这些常量来指定写入给定类型的注释的合法位置。
//ElementType TYPE类,接口(包括注释类型)或枚举声明
// FIELD字段声明(包括枚举常数)
// METHOD方法声明
//CONSTRUCTOR构造函数声明
//LOCAL_VARIABLE局部变量声明
//ANNOTATION_TYPE注解类型声明
//PACKAGE包装声明
//TYPE_PARAMETER键入参数声明
//TYPE_USE使用类型
@Target({ElementType.FIELD})
//Retention指示要注释具有注释类型的注释的保留时间。 如果注释类型声明中没有保留注释,则保留策略默认为RetentionPolicy.CLASS 。
//CLASS 注释将由编译器记录在类文件中,但VM不需要在运行时保留。
//RUNTIME 注释将由编译器记录在类文件中,并由VM在运行时保留,因此可以反射读取。
//SOURCE 注释将被编译器丢弃。
@Retention(RetentionPolicy.RUNTIME)
//表示具有类型的注释默认情况下由javadoc和类似工具记录。 应该使用此类型来注释其注释影响其客户端使用注释元素的类型的声明。 如果类型声明使用Documented进行注释,则其注释将成为注释元素的公共API的一部分。
@Documented

上述参数是用于注解类参数方法,其次再定义方法(在jvm运行时再利用反射方法获得到的被添加注解的类或方法和参数的具体实现)。主要运用到反射原理。

仿照Spring的自定义注解

  • 仿照spring自定义注解

contoller注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPController {
    String value() default "";
}

requestmapping注解

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPRequestMapping {
    String value() default "";
}

requestparam注解

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPRequestParam {
    String value() default "";
}

service注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPService {
    String value() default "";
}

autowired注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPAutowired {
    String value() default "";
}

初始化方法

/**
 * @Author jtao
 * @Date 2020/12/4 23:14
 * @Description 采用设计模式:工厂模式,单例模式,委派模式。策略模式将init()方法中的代码进行封装
 */

public class GPDispatcherServlet extends HttpServlet {
    /**
     * @Author jtao
     * @Date 2020/12/4 23:40
     * @Description 初始化阶段
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //getInitParameter方法, 可以从web.xml中获取数据
        //2.扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));
        //3.初始化扫描到的类,并将它们放入到IoC容器中
        doInstance();
        //4.完成依赖注入
        doAutowired();
        //5.初始化HandlerMapping
        initHandlerMapping();

        System.out.println("GP Spring framework is init");
    }

    /**
     * @Author jtao
     * @Date 2020/12/4 23:40
     * @Description 声明全局成员变量,其中IoC容器就是注册时单例的具体案例
     */
    //6.保存application.properties配置文件中的内容
    private Properties contextConfig = new Properties();
    //7.保存扫描的所有类名
    private List<String> classNames = new ArrayList<>();

    //8.传说中的Ioc容器,我们来解开它的面纱
    //为了简化程序,暂时不考虑ConcurrentHashMap
    //主要还是关注设计思想和原理
    private Map<String, Object> ioc = new HashMap<>();
    //9.保存url和Method的对应关系
    private Map<String, Method> handlerMapping = new HashMap<>();

    /**
     * @Author jtao
     * @Date 2020/12/4 23:49
     * @Description 实现doLoadConfig()方法
     */
    //加载配置文件
    private void doLoadConfig(String contextConfigLocation) {
        //直接通过类路径扎到Spring主配置放到文件所在的路径
        //并且将其读取出来放到Properties对象中
        //相当于将ScanPackage=com.gupaoedu.demo保存到内存中
        InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);

        try {
            contextConfig.load(fis);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fis) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * @Author jtao
     * @Date 2020/12/5 0:29
     * @Description 扫描类
     */
    private void doScanner(String scanPackage) {
        //scanPackage=com.gupaoedu.demo ,储存的是包路径
        //转换为文件路径,实际上就是把.替换为/
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classPath = new File(url.getFile());
        //list()方法是返回某个目录下的所有文件和目录的文件名,返回的是String数组
        //listFiles()方法是返回某个目录下所有文件和目录的绝对路径,返回的是File数组
        for (File file : classPath.listFiles()) {
            //public boolean isDirectory()测试此抽象路径名表示的文件是否为目录。
            if (file.isDirectory()) {
                //如果为目录则继续向下执行doScanner方法
                doScanner(scanPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                //文件名为.class,则类名字去除.class
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                classNames.add(className);
            }
        }
    }

    /**
     * @Author jtao
     * @Date 2020/12/5 0:29
     * @Description 实例化放入IoC容器中
     */
    private void doInstance() {
        //初始化,为DI(依赖注入)做准备
        if (classNames.isEmpty()) {
            //return null为返回null,跳出方法
            //return 为void方法,直接跳出
            return;
        }
        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);

                //什么样的类才需要初始化呢?
                //加了注解的类才能初始化,怎么判断
                //为了简化代码逻辑,主要体会设计思想,只用@Controller和@Service举例,
                //@Componment等就不一一举例了

                //boolean isAnnotation() 如果此 类对象表示注释类型,则返回true。
                //boolean isAnnotationPresent(类<? extends Annotation> annotationClass) 如果此元素上 存在指定类型的注释,则返回true,否则返回false。
                if (clazz.isAnnotationPresent(GPController.class)) {
                    Object instance = clazz.newInstance();
                    //Spring默认类名首字母小写
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);
                } else if (clazz.isAnnotationPresent(GPService.class)) {
                    //自定义的beanName
                    //getAnnotation(类<A> annotationClass) 返回该元素的,如果这样的注释 ,否则返回null指定类型的注释。
                    GPService gpService = clazz.getAnnotation(GPService.class);
                    String beanName = gpService.value();
                    if ("".equals(beanName.trim())) {
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.newInstance();
                    ioc.put(beanName, instance);
                    //根据类型自动赋值
                    //getInterfaces() 确定由该对象表示的类或接口实现的接口。如果为接口表示扩展接口的所有对象。如果为类则返回包含表示该类实现的所有接口的对象的数组
                    for (Class<?> i : clazz.getInterfaces()) {
                        if (ioc.containsKey(i.getName())) {
                            throw new Exception("The”" + i.getName() + "”is exists!!");
                        }
                        //把接口的类型直接当成key
                        ioc.put(i.getName(), instance);
                    }

                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @Author jtao
     * @Date 2020/12/5 0:49
     * @Description 实现类名首字母小写方法
     */
    private String toLowerFirstCase(String simpleName) {
        //将字符串转换为数组
        char[] chars = simpleName.toCharArray();
        //大小写字母的ASCII码相差32,且大写小于小写
        //在java中对char做算术运算就是对ASCII码做算术运算
        chars[0] += 32;
        return String.valueOf(chars);
    }

    /**
     * @Author jtao
     * @Date 2020/12/5 2:27
     * @Description 实现doAutoWired方法
     */
    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //获取所有的字段,包括private,protected,default类型的
            //正常来说普通的oop编程只能获得public类型的字段
            //getDeclaredFields()  返回的数组 Field对象反映此表示的类或接口声明的所有字段 类对象。
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(GPAutowired.class)) {
                    continue;
                }
                GPAutowired autowired = field.getAnnotation(GPAutowired.class);
                //如果用户没有自定义beanName,默认就根据类型注入,默认首字母小写
                String beanName = toLowerFirstCase(autowired.value().trim());
                if ("".equals(beanName)) {
                    //获得接口的类型,作为key,稍后用这个key到IoC容器中取值
                    beanName = toLowerFirstCase(field.getType().getName());
                }
                //如果是public以外的类型,只要加了@Autowired注解都要强制赋值
                //反射中叫做暴力访问
                field.setAccessible(true);
                //用反射机制动态给字段赋值
                try {
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * @Author jtao
     * @Date 2020/12/5 18:12
     * @Description 实现initHandlerMapping()方法,策略模式
     */

    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(GPController.class)) {
                continue;
            }
            //保存写在类上面的@GPRequestMapping("/demo")
            String baseUrl = "";
            if (clazz.isAnnotationPresent(GPRequestMapping.class)) {
                GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class);
                baseUrl = requestMapping.value();
            }
            //默认获取所有的public类型的方法
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(GPRequestMapping.class)) {
                    continue;
                }
                GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class);
                //优化
                String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
                handlerMapping.put(url, method);
                System.out.println("Mapped:" + url + "," + method);
            }
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private void doDispatchEasy(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
        if (!this.handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 Not Founf!!");
            return;
        }
        Method method = this.handlerMapping.get(url);
        //第一个参数:方法所在的实例
        //第二个参数:调用时所需要的实参
        Map<String, String[]> params = req.getParameterMap();
        //投机取巧的方式
        //getDeclaringClass() 返回 类表示声明该对象表示的可执行的类或接口对象。
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        //invoke(Object obj, Object... args)  在具有指定参数的 方法对象上调用此 方法对象表示的底层方法。
        method.invoke(ioc.get(beanName), new Object[]{req, resp, params.get("name")[0]});
        System.out.println(method);
    }

    /**
     * @Author jtao
     * @Date 2020/12/5 19:18
     * @Description 优化doDispatch实现url参数动态获取
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
        if (!this.handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 Not Founf!!");
            return;
        }
        Method method = this.handlerMapping.get(url);
        //第一个参数:方法所在的实例
        //第二个参数:调用时所需要的实参
        Map<String, String[]> params = req.getParameterMap();
        //获取方法的形参列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        //保存请求的url参数列表
        Map<String, String[]> parameterMap = req.getParameterMap();
        //保存赋值参数的位置
        Object[] parameterValues = new Object[parameterTypes.length];
        //根据参数位置动态赋值
        for (int i = 0; i < parameterTypes.length; i++) {
            Class parameterType = parameterTypes[i];
            if (parameterType == HttpServletRequest.class) {
                parameterValues[i] = req;
                continue;
            } else if (parameterType == HttpServletResponse.class) {
                parameterValues[i] = resp;
                continue;
            } else if (parameterType == String.class) {
                //提取方法中加了注解的参数
                Annotation[][] pa = method.getParameterAnnotations();
                for (int j = 0; j < pa.length; j++) {
                    for (Annotation a : pa[i]) {
                        if (a instanceof GPRequestParam) {
                            String paramName = ((GPRequestParam) a).value();
                            if (!"".equals(paramName.trim())) {
                                String value = Arrays.toString(parameterMap.get(paramName)).replaceAll("\\[|\\]", "").replaceAll("\\s", ",");
                                parameterValues[i] = value;
                            }
                        }
                    }
                }
            }
        }
        //投机取巧的方式
        //getDeclaringClass() 返回 类表示声明该对象表示的可执行的类或接口对象。
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        //invoke(Object obj, Object... args)  在具有指定参数的 方法对象上调用此 方法对象表示的底层方法。
        method.invoke(ioc.get(beanName), new Object[]{req, resp, params.get("name")[0]});
    }
}

这个家伙很懒,啥也没有留下😋