造一个方形的轮子6--Controller支持(上)

造一个方形轮子文章目录:造一个方形的轮子

01、解决遗留问题

在上一篇《造一个方形的轮子5--数据库支持》的最后提出了一个问题,容器中的bean是多对一的关系,多个key对应的 都是同一个Bean 如果使用循环的方式,就至少会多一倍解析处理依赖注入问题,这个问题可以使用简单的方式处理掉。

BeansInitUtil.java 修改initDI()方法:

    /**
     * 处理关系依赖
     * @param map Bean容器
     */
    private static void initDI(BeansMap map){
        List<BeanObject> list = new ArrayList<>();
        BeanObject sqlBean = null;
        // 循环所有Bean处理依赖
        for(Map.Entry entry : map.entrySet()){
            BeanObject beanObject = (BeanObject)entry.getValue();
            // 如果已经处理过,则跳过
            if(list.contains(beanObject)){
                break;
            }
            // 添加到已处理列表
            list.add(beanObject);
            // ....
        }
    }

简单解决了一下,添加一个已处理列表,处理之前先判断一下是否处理过,如果在已处理列表中则跳过。

02、Controller支持的整体思路

先来理一下,支持Controller的基本要求:

1、Controller本身也是容器中的Bean、因为需要做依赖注入

2、Controller能够支持路径映射,以处理不同的请求

3、需要有处理不同请求方式的能力,对应HTTP的GET/POST/PUT/DELETE

4、要有解析请求参数的能力,可以处理URL参数以及body参数(映射为JAVA VO类,方便使用)

5、可以格式化输出响应结果(先只支持一下json格式)

处理一个http请求的流程:

1、发起http请求

2、DispatcherServlet(自定义)统一截获请求

3、分析请求路径及请求类型

4、根据类型+路径获取Controller及方法

5、根据对应的方法参数列表从HttpRequest中获取并处理参数

6、反射调用对应的方法,获取返回结果

7、处理返回结果,设置对应的响应类型(application/json)

大概的流程就是这样,动手实现一下

03、改造容器类

开始之前先来改造一下原来的容器类,在之前的章节中,都是直接使用了一个HashMap做为Bean的容器,因为原来只在容器中保存一个Bean实例就可以了,但现在加入了Controller,需要一个http请求路径对应Bean实例的映射,原来的BeanMap就不能满足了(因为原来是使用的,接口、类、指定名称做的映射Key值),原来的Bean对象使用了一个BeanObject封装类,对应的现在为Controller对象添加一个ControllerObject类:

package com.jisuye.core;
// import ...
/**
 * controller对象
 * @author ixx
 * @date 2019-07-14
 */
public class ControllerObject {
    private static final Logger log = LoggerFactory.getLogger(ControllerObject.class);

    /**
     * bean 实例
     */
    private Object object;

    /**
     * 具体方法
     */
    private Method method;

    /**
     * http请求方式
     */
    private String httpMethod;

    /**
     * 参数列表(获取@RequestParam指定的参数名,或者@RequestBody指定的类)
     */
    private SquareParam[] params;
    // ....getter and setter
}

这里记录的和BeanObject有很大区别,主要记录了被封装的对象实例,对应的执行方法以及参数列表,这里又引入了一个参数对象SquareParam.java:

package com.jisuye.core;
/**
 * 封装参数类型
 * @author ixx
 * @date 2019-07-14
 */
public class SquareParam {
    /**
     * 是否是url参数
     */
    private boolean param;
    /**
     * 参数类型
     */
    private Class clazz;
    /**
     * 参数名(只有是url参数是才有)
     */
    private String paramName;
    public SquareParam(Class clazz){
        this.param = false;
        this.clazz = clazz;
    }
    public SquareParam(String paramName, Class clazz){
        this.param = true;
        this.paramName = paramName;
        this.clazz = clazz;
    }
    // ...getter and setter
}

参数对象的功能,会在后边使用的地方详细说明,这里先来看改造后的容器类,为了做最小的改动,我添加了一个

BeansMap.java:

package com.jisuye.core;
// import ...
/**
 * beans容器
 * @author ixx
 * @date 2019-07-14
 */
public class BeansMap {
    // bean容器
    private static HashMap<String, BeanObject> beans = new HashMap<>();
    // controller容器
    private static HashMap<String, ControllerObject> controllers = new HashMap<>();
    public void putController(String key, ControllerObject controllerObject){
        controllers.put(key, controllerObject);
    }
    public ControllerObject getController(String key){
        return controllers.get(key);
    }
    public void put(String key, BeanObject beanObject){
        beans.put(key, beanObject);
    }
    public BeanObject get(String key){
        return beans.get(key);
    }
    public Set<Map.Entry<String, BeanObject>> entrySet(){
        return beans.entrySet();
    }
    public int size(){
        return beans.size();
    }
}

这里的方法是为了兼容原来的HashMap容器添加的,这样把之前beansMap 的定义及参数类型,统一改成BeansMap就可以了。

04、添加相关注解

为了满足对Controller支持的需要,添加如下注解:

@Controller

package com.jisuye.annotations;
// import ...
/**
 * Web  Controller annotations
 * @author ixx
 * @date 2019-07-14
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
    String value() default "";
}

@GetMapping

package com.jisuye.annotations;
// import ...
/**
 * http get 注解
 * @author ixx
 * @date 2019-07-14
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GetMapping {
    String value() default "";
}

@PostMapping

package com.jisuye.annotations;
// import ...
/**
 * http post 注解
 * @author ixx
 * @date 2019-07-14
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PostMapping {
    String value() default "";
}

@PutMapping

package com.jisuye.annotations;
// import ...
/**
 * http put 注解
 * @author ixx
 * @date 2019-07-14
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PutMapping {
    String value() default "";
}

@DeleteMapping

package com.jisuye.annotations;
// import ...
/**
 * http delete 注解
 * @author ixx
 * @date 2019-07-14
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DeleteMapping {
    String value() default "";
}

@RequestBody

package com.jisuye.annotations;
// import ...
/**
 * 请求体body参数注解
 * @author ixx
 * @date 2019-07-14
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestBody {
}

@RequestParam

package com.jisuye.annotations;
// import ...
/**
 * url参数注解
 * @author ixx
 * @date 2019-07-14
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestParam {
    String value() default "";
}

都是平常做JAVA Web开发经常使用的注解,不做过多解释了。

05、初始化Controller

接下来在做Bean初始化的时候添加初始化Controller功能,这样服务启动完毕后对应的Bean及Controller都初始化完毕可以直接使用了

项目启动过程先去初始化Bean,发现当前Bean是使用@Controller注解的,则调用初始化Controller方法,在initController方法中,执行过程如下:

1、获取当前Bean的所有方法,

2、循环判断是否有@GetMapping、@PostMapping、@PutMapping、@DeleteMapping注解,如果有则获取http请求类型及对应的方法映射路径

3、获取方法的参数列表,处理@RequestParam及@RequestBody注解

4、构造ControllerObject对象,并以请求类型:类映射路径/方法映射路径为key保存到BeansMap容器中

在原来的BeansInitUtil类中做如下修改(以下是代码片段只说明修改的地方,完整代码参见文末代码库分支):

public class BeansInitUtil {
    .....
    private static void loadClass(File file, BeansMap map){
                ......
                // 按注解输入value设置bean
                for (Annotation annotation : annotations) {
                    String tmp_name = "";
                    if(annotation instanceof Service){
                        tmp_name = ((Service)annotation).value();
                    } else if(annotation instanceof Component) {
                        tmp_name = ((Component)annotation).value();
                    } else if(annotation instanceof Controller) {
                        // 这里添加Controller初始化调用
                        initController(clzz, ((Controller)annotation).value(), map);
                    }
                    ......
    }   
    /**
     * 初始化Controller
     * @param clzz
     * @param classPath
     */
    private static void initController(Class clzz, String classPath, BeansMap beansMap){
        try {
            Method[] methods = clzz.getMethods();
            // 处理每一个方法
            for (Method method : methods) {
                Annotation[] annotations = method.getDeclaredAnnotations();
                String[] methodPath = getMethodAnnotationValue(annotations);
                // 说明是@GetMapping @PostMapping @PutMapping @DeleteMapping 中的一个
                if (methodPath != null) {
                    // 获取参数及注解
                    Parameter[] parameters = method.getParameters();
                    SquareParam[] params = new SquareParam[parameters.length];
                    int i = 0;
                    for (Parameter parameter : parameters) {
                        Annotation[] paramAnnotations = parameter.getAnnotations();
                        SquareParam param = getParam(paramAnnotations, parameter.getType());
                        params[i++] = param;
                    }
                    ControllerObject co = new ControllerObject();
                    co.setParams(params);
                    co.setHttpMethod(methodPath[0]);
                    co.setMethod(method);
                    // 这里直接取Beans容器中获取Controller实例,保持单例并解决依赖注入问题
                    co.setObject(beansMap.get(firstToLowerCase(clzz.getSimpleName())).getObject());
                    String key = methodPath[0] +":"+ classPath+methodPath[1];
                    beansMap.putController(key, co);
                }
            }
        } catch (Exception e){
            log.error("init controller error.", e);
            throw new SquareException("init controller error", e);
        }
    }
    /** * 获取方法上的注解value */
    private static String[] getMethodAnnotationValue(Annotation[] annotations){
        String[] methodPath = null;
        for (Annotation annotation : annotations) {
            if(annotation instanceof DeleteMapping){
                methodPath = new String[]{"delete", ((DeleteMapping) annotation).value()};
            } else if(annotation instanceof GetMapping){
                methodPath = new String[]{"get", ((GetMapping) annotation).value()};
            } else if(annotation instanceof PostMapping){
                methodPath = new String[]{"post", ((PostMapping) annotation).value()};
            } else if(annotation instanceof PutMapping){
                methodPath = new String[]{"put", ((PutMapping) annotation).value()};
            }
        }
        return methodPath;
    }
}

06、小结

到目前为止,已经把添加Controller支持工作完成了一半了,现在已经可以初始化对应的Controller到容器中了,接下来要做的事就是把对应的请求和我们自己定义的Controller对应上就可以了,内容较多,就单起一篇写了。

本篇代码地址: https://github.com/iuv/square/tree/square6

本文作者: ixx
本文链接: http://jianpage.com/2019/07/17/square6
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

推荐阅读更多精彩内容