最近想自己学习写框架,看过一些资料,决定从JavaWeb开始。近来用到SpringBoot写过一些模拟接口,我对这种框架很感兴趣,几行代码就可以写出很实用的数据接口。

框架设计除了需要巧妙的思路之外,还需要准备三种技术:

1.反射;

2.自定义注解;

3.资源文件的读写。一般框架常用xml文件做配置,也可以使用properties属性文件。

我是用Idea做工具。

一、首先创建一个Java项目,勾选Web Application。目的是进行调试。成熟的框架应该是独立的。

二、导入几个包,包括gson和java6,java6会在创建servlet时提示下载,另外还要org.apache.commons.io包,用来读取post提交的请求数据,通过maven就可以下载。

三,创建文件结构如图:

java 界面设计 框架 java框架设计技巧_注解

四、实现三个自定义注解

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



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


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



五、实现核心类

public class KfServlet extends javax.servlet.http.HttpServlet {

@Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //字符集处理
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        response.setHeader("Content-type", "application/json;charset=UTF-8");
        PrintWriter out = response.getWriter();
        /**
         * 目前没有设计GET方法请求KV方式
         * 严格的处理要判断请求方式,然后根据method判断是否可以使用RequestBody注解
         * 这里只进行一点初步的设计
         */
        //获得请求字符串
        String requestBody = getRequestBodyString(request);
        //获得请求路径
        String requestURI = request.getRequestURI();
        //去掉开头的"/"
        if (requestURI.startsWith("/")) {
            requestURI = requestURI.substring(1, requestURI.length());
        }
        //去掉末尾的"/"
        if (requestURI.endsWith("/")) {
            requestURI = requestURI.substring(0, requestURI.length() - 1);
        }
        //分割剩余的路径字符串,三段为合法:项目名+控制器+方法
        String[] paths = requestURI.split("/");
        String controllerName = "", methodName = "";
        boolean findController = false, findMethod = false;//控制器和方法的标记
        if (paths.length == 3) {//长度为3才合法
            controllerName = paths[1].toLowerCase();//控制器名
            methodName = paths[2].toLowerCase();//方法名

            String packageName = "com.kf";//基本包名,正确做法应该是在配置文件或者全局变量中设置
            //通过"一条大红龙"的工具类扫描包下的所有类
            //正确做法应该是在扫描的时候用解析的控制器名去匹配
            List<Class<?>> classes = ClassUtil.getClasses(packageName);
            //遍历扫描的类
            //获得类上面的KFController注解
            for (Class clazz : classes) {
                KFController kfControllerAnno = (KFController) clazz.getAnnotation(KFController.class);
                //判断KFController注解是否为空
                if (kfControllerAnno != null&& kfControllerAnno.value().toLowerCase().equals(controllerName)) {
                    findController = true;//标记找到了控制器
                    try {
                        //获得Controller类下的所有方法
                        Method[] methods = clazz.getMethods();
                        for (Method method : methods) {
                            //获取方法上的RequestUrl注解
                            RequestUrl requestUrlAnno = method.getAnnotation(RequestUrl.class);
                            //判断RequestUrl注解是否为空
                            if (requestUrlAnno != null&&requestUrlAnno.value().toLowerCase().equals(methodName)) {
                                findMethod = true;//标记找到了方法
                                //获得方法的所有参数
                                Parameter[] parameters = method.getParameters();
                                //无参
                                if (parameters == null || parameters.length == 0) {
                                    //执行无参数方法,返回
                                    out.write((String) method.invoke(clazz.newInstance()));
                                    return;
                                } else {
                                    //有参
                                    for (Parameter parameter : parameters) {
                                        //获得参数RequestBody注解
                                        Annotation requestBodyAnno = parameter.getAnnotation(RequestBody.class);
                                        //判断RequestBody注解是否为空
                                        if (requestBodyAnno != null) {
                                            //判断如果使用了RequestBody注解,就把RequestBody赋值给它
                                            //把请求body转化为对象类型
                                            Object obj = new Gson().fromJson(requestBody, parameter.getType());
                                            //执行带一个用TequestBody修饰过的参数的方法
                                            out.write((String) method.invoke(clazz.newInstance(), obj));
                                            return;
                                        } else {
                                            out.write("404错误 对不起,没有找到符合注解的相应的请求方法");
                                        }
                                    }
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        if (!findMethod) {
                            out.write("404错误 对不起,没有找到相应的请求方法");
                        }
                        if (out != null) {
                            out.close();
                        }
                    }
                }
            }
            if (!findController) {
                out.write("404错误 对不起,没有找到相应的控制器");
            }
        } else {
            out.write("404 URL不合法");
        }

        if (out != null) {
            out.close();
        }
    }

    public static String getRequestBodyString(HttpServletRequest request) throws IOException {
        String requestBody = "";
        if ("POST".equals(request.getMethod())) {
            requestBody = IOUtils.toString(request.getInputStream(), "UTF-8");
        } else {
            String queryString = request.getQueryString();
            if (queryString != null && queryString.length() != 0) {
                requestBody = queryString.replace("%22", "\"");
            }
        }
        return requestBody;
    }
}


六、里面用到我从找到的工具类



/**
     * 取得某个接口下所有实现这个接口的类
     */
    public static List<Class> getAllClassByInterface(Class c) {
        List<Class> returnClassList = null;

        if (c.isInterface()) {
            // 获取当前的包名
            String packageName = c.getPackage().getName();
            // 获取当前包下以及子包下所以的类
            List<Class<?>> allClass = getClasses(packageName);
            if (allClass != null) {
                returnClassList = new ArrayList<Class>();
                for (Class classes : allClass) {
                    // 判断是否是同一个接口
                    if (c.isAssignableFrom(classes)) {
                        // 本身不加入进去
                        if (!c.equals(classes)) {
                            returnClassList.add(classes);
                        }
                    }
                }
            }
        }
        return returnClassList;
    }


    /*
    * 取得某一类所在包的所有类名 不含迭代
    */
    public static String[] getPackageAllClassName(String classLocation, String packageName) {
        //将packageName分解
        String[] packagePathSplit = packageName.split("[.]");
        String realClassLocation = classLocation;
        int packageLength = packagePathSplit.length;
        for (int i = 0; i < packageLength; i++) {
            realClassLocation = realClassLocation + File.separator + packagePathSplit[i];
        }
        File packeageDir = new File(realClassLocation);
        if (packeageDir.isDirectory()) {
            String[] allClassName = packeageDir.list();
            return allClassName;
        }
        return null;
    }

    /**
     * 从包package中获取所有的Class
     *
     * @param packageName
     * @return
     */
    public static List<Class<?>> getClasses(String packageName) {

        //第一个class类的集合
        List<Class<?>> classes = new ArrayList<Class<?>>();
        //是否循环迭代
        boolean recursive = true;
        //获取包的名字 并进行替换
        String packageDirName = packageName.replace('.', '/');
        //定义一个枚举的集合 并进行循环来处理这个目录下的things
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            //循环迭代下去
            while (dirs.hasMoreElements()) {
                //获取下一个元素
                URL url = dirs.nextElement();
                //得到协议的名称
                String protocol = url.getProtocol();
                //如果是以文件的形式保存在服务器上
                if ("file".equals(protocol)) {
                    //获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    //以文件的方式扫描整个包下的文件 并添加到集合中
                    findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
                } else if ("jar".equals(protocol)) {
                    //如果是jar包文件
                    //定义一个JarFile
                    JarFile jar;
                    try {
                        //获取jar
                        jar = ((JarURLConnection) url.openConnection()).getJarFile();
                        //从此jar包 得到一个枚举类
                        Enumeration<JarEntry> entries = jar.entries();
                        //同样的进行循环迭代
                        while (entries.hasMoreElements()) {
                            //获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();
                            //如果是以/开头的
                            if (name.charAt(0) == '/') {
                                //获取后面的字符串
                                name = name.substring(1);
                            }
                            //如果前半部分和定义的包名相同
                            if (name.startsWith(packageDirName)) {
                                int idx = name.lastIndexOf('/');
                                //如果以"/"结尾 是一个包
                                if (idx != -1) {
                                    //获取包名 把"/"替换成"."
                                    packageName = name.substring(0, idx).replace('/', '.');
                                }
                                //如果可以迭代下去 并且是一个包
                                if ((idx != -1) || recursive) {
                                    //如果是一个.class文件 而且不是目录
                                    if (name.endsWith(".class") && !entry.isDirectory()) {
                                        //去掉后面的".class" 获取真正的类名
                                        String className = name.substring(packageName.length() + 1, name.length() - 6);
                                        try {
                                            //添加到classes
                                            classes.add(Class.forName(packageName + '.' + className));
                                        } catch (ClassNotFoundException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return classes;
    }

    /**
     * 以文件的形式来获取包下的所有Class
     *
     * @param packageName
     * @param packagePath
     * @param recursive
     * @param classes
     */
    public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<Class<?>> classes) {
        //获取此包的目录 建立一个File
        File dir = new File(packagePath);
        //如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        //如果存在 就获取包下的所有文件 包括目录
        File[] dirfiles = dir.listFiles(new FileFilter() {
            //自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
            public boolean accept(File file) {
                return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
            }
        });
        //循环所有文件
        for (File file : dirfiles) {
            //如果是目录 则继续扫描
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(),
                        file.getAbsolutePath(),
                        recursive,
                        classes);
            } else {
                //如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    //添加到集合中去
                    classes.add(Class.forName(packageName + '.' + className));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}



七、创建两个model,一个是用于打包数据的UserModel,一个是用于解析请求数据的UserRequst

public class UserModel {
    public long id;
    public String name;
    public boolean gender;
    public int age;

    public UserModel(long id, String name, boolean gender, int age) {
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
}



public class UserRequest {
    public long id;
    public String name;
    public String passWord;
}



八、创建两个controller,目的是进行测试区别

@KFController("test")
public class TestController {

    @RequestUrl("test")
    public String test() {
        return "我就是test宝哥";
    }

    @RequestUrl("gettest")
    public String gettest() {
        UserModel userModel = new UserModel(1, "Test Chen", true, 38);
        return new Gson().toJson(userModel);
    }

    @RequestUrl("bodyTest")
    public String bodyTest(@RequestBody String body) {
        return body;
    }

}



@KFController("user")
public class UserController {

    @RequestUrl("getUserInfo")
    public String getUserInfo() {
        return "我就是宝哥";
    }

    @RequestUrl("getUser")
    public String getUser() {
        return new Gson().toJson(new UserModel(1, "Kaly Chen", true, 38));
    }

    @RequestUrl("getUserBody")
    public String getUserBody(@RequestBody UserRequest body) {
        return new Gson().toJson(new UserModel(body.id, body.name, true, 38));
    }

}



九、还有web.xml需要配置,让所有的请求都从KfServlet经过,然后分发

<servlet>
        <servlet-name>KfServlet</servlet-name>
        <servlet-class>com.kf.core.KfServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>KfServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>



十、测试

1.浏览器输入http://192.168.1.101:8080/kfdemo/user/getuserbody?{%22id%22:19,%22name%22:%22chenfabao%22,%22passWord%22:%22123456%22}

java 界面设计 框架 java框架设计技巧_注解_02

2.PostMan测试

java 界面设计 框架 java框架设计技巧_框架_03


下一次,我们考虑去操作数据库,并实现ORM