最近想自己学习写框架,看过一些资料,决定从JavaWeb开始。近来用到SpringBoot写过一些模拟接口,我对这种框架很感兴趣,几行代码就可以写出很实用的数据接口。
框架设计除了需要巧妙的思路之外,还需要准备三种技术:
1.反射;
2.自定义注解;
3.资源文件的读写。一般框架常用xml文件做配置,也可以使用properties属性文件。
我是用Idea做工具。
一、首先创建一个Java项目,勾选Web Application。目的是进行调试。成熟的框架应该是独立的。
二、导入几个包,包括gson和java6,java6会在创建servlet时提示下载,另外还要org.apache.commons.io包,用来读取post提交的请求数据,通过maven就可以下载。
三,创建文件结构如图:
四、实现三个自定义注解
@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}
2.PostMan测试
下一次,我们考虑去操作数据库,并实现ORM