关于java:java的spring框架简单实现

前言

在本文中,博主一步步地从servlet到controller层实现一个简略的框架。通过此框架,咱们能够像spring那样应用以下根底注解:

  • @XxgController
  • @XxgRequestMapping
  • @XxgParam
  • @XxgRequestBody

观看本文之前,你或者应该先理解以下内容:

  • BeanUtils
  • ObjectMapper
  • Servlet相干常识

思路:拦截器实现路由散发。利用注解?

思考:

  1. 拦截器能够在servlet之前拦挡所有申请门路
  2. 能够找到注解中门路与申请门路相匹配的那个办法
  3. 而后将req,resp转发给该办法来执行

问题:

拦截器如何找到应用了该注解的办法?包扫描?如何实现?

剖析:

包扫描,就波及IO流, 而File类能够递归查问其上面所有的文件,咱们

能够过滤一下:

  1. 只有后缀名为.class的文件,并获取其className(包含包门路)
  2. 通过反射获取这个类,判断其是否有指定的注解进而再次过滤

这样在拦截器拦挡到申请门路,咱们能够进行匹配并调用该办法。

偷个懒:

因为MVC设计模式,咱们个别把api接口都放在同一个包下,所以咱们能够间接指定要扫描包,其它包就不论

一.扫描类1.0版的实现

public class FileScanner {
 private final String packetUrl = "com.dbc.review.controller";
 private final ClassLoader classLoader = FileScanner.class.getClassLoader();
 private List<Class> allClazz = new ArrayList<>(10); //存该包下所有用了注解的类
​
 public List<Class> getAllClazz(){
 return this.allClazz;
 }
​
 public String getPacketUrl(){
 return this.packetUrl;
 }
​
 // 查问所有应用了给定注解的类
 // 递归扫描包,如果扫描到class,则调用class解决办法来收集想要的class
 public void loadAllClass(String packetUrl) throws Exception{
 String url = packetUrl.replace(".","/");
 URL resource = classLoader.getResource(url);
 if (resource == null) {
 return;
 }
 String path = resource.getPath();
 File file = new File(URLDecoder.decode(path, "UTF-8"));
 if (!file.exists()) {
 return;
 }
 if (file.isDirectory()){
 File[] files = file.listFiles();
 if (files == null) {
 return;
 }
 for (File f : files) {
 String classname = f.getName().substring(0, f.getName().lastIndexOf("."));
 if (f.isDirectory()) {
 loadAllClass(packetUrl + "." + classname);
 }
 if (f.isFile() && f.getName().endsWith(".class")) {
 Class clazz = Class.forName(packetUrl + "." + classname);
 dealClass( clazz);
 }
 }
 }
 }
​
 private void dealClass(Class clazz) {
 if ((clazz.isAnnotationPresent(Controller.class))) {
 allClazz.add(clazz);
 }
 }
​
 // 真正应用的时候,依据申请门路及申请办法来获取解决的办法
 public boolean invoke(String url,String requestMethod) {
 for (Class clazz : allClazz){
 Controller controller = (Controller) clazz.getAnnotation(Controller.class);
 Method[] methods = clazz.getDeclaredMethods();
 for (Method method : methods) {
 RequestMapping requestMapping = method.getDeclaredAnnotation(RequestMapping.class);
 if (requestMapping == null) {
 continue;
 }
 for (String m : requestMapping.methods()) {
 m = m.toUpperCase();
 if (!m.toUpperCase().equals(requestMethod.toUpperCase())) {
 continue;
 }
 StringBuilder sb = new StringBuilder();
 String urlItem = sb.append(controller.url()).append(requestMapping.url()).toString();
 if (urlItem.equals(url)) {
 // 获取到用于解决此api接口的办法
 try {
//                            method.getGenericParameterTypes() // 能够依据此办法来判断该办法须要传哪些参数
 method.invoke(clazz.newInstance());
 return true;
 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
 e.printStackTrace();
 return false;
 }
 }
 }
 }
 }
 return false;
 }
​
 @Test
 public void test() throws Exception {
 // 1. 在Filter的动态代码块中实例化
 FileScanner fileScanner = new FileScanner();
 // 2. 启动扫描
 fileScanner.loadAllClass(fileScanner.getPacketUrl());
 // 3. 拦挡到申请后,调用此办法来执行
 // 若该包下没有定义post申请的/test/post 的解决办法,则返回false
 // 执行胜利返回true
 fileScanner.invoke("/test/post","post");
 // 4. 执行失败,返回false,则抛出405 办法未定义。
​
 // 最初 :对于controller的传参,本类未实现
 //       临时想到:依据method获取其参数列表,再传对应参数,就是不太好实现
 }
}

TestController

@Controller(url = "/test")
public class TestController {
​
 @RequestMapping(url = "/get",methods = "GET")
 public void get(){
 System.out.println(111);
 }
 @RequestMapping(url = "/post",methods = {"POST","get"})
 public void post(){
 System.out.println(22);
 }
​
 public void test(HttpServletRequest req, HttpServletResponse res){
 System.out.println(req.getPathInfo());
 }
}

扫描类2.0版

通过1.0版,咱们初步实现递归扫描包下的所有controller,并能通过门路映射实现拜访。但很显著有至多以下问题:

  1. 执行办法时,办法不能有参数。不合乎业务需要
  2. 每次拜访,都要重复解决Class反射来找到门路映射的办法,效率低。

针对以上2个问题,咱们在2.0版进行一下批改:

  1. 将controller、requestmapping对应办法,办法对应参数的可能用到的相干信息寄存在一个容器中。在服务器首次启动时进行扫描,并拆卸到容器中。这样在每次拜访时,遍历这个容器,比1.0版的容器更不便。
  2. 定义参数类型,通过注解@XxgRequestBody以及@XxgParam辨别参数从申请体拿或者从url的?前面拿。从而获取前端传来的数据
  3. 通过ObjectMapper进行不同类型参数的拆卸,最初调用办法的invoke实现带参/不带参的办法解决。

BeanDefinition

/**
 * 用来寄存controller类的相干参数、办法等
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BeanDefinition {
 private Class typeClazz; // 类对象
 private String typeName; // 类名
 private XxgController controller; // controller注解
 private String controllerUrlPath; // controller的path门路
 private List<MethodDefinition> methodDefinitions; // 带有RequestMapping的注解
}

MethodDefinition

/**
 * 形容办法的类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MethodDefinition {
 private Class parentClazz; // 所属父类的class
 private Method method; // 办法
 private String methodName; // 办法名
 private XxgRequestMapping requestMapping; // RequestMapping的注解类
 private String requestMappingUrlPath; // url
 private String[] allowedRequestMethods; // allowedRequestMethods
 private List<ParameterDefinition> parameterDefinitions;  // 参数列表
 private Object result;  // 返回数据
}

ParameterDefinition

/**
 * 形容参数的类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ParameterDefinition {
 private Class paramClazz; // 参数类对象
 private String paramName; // 参数名称
 private Object paramType; // 参数类型
 private boolean isRequestBody; // 是否是获取body中数据
}

单例模式的容器

赋予扫描包及依据uri获取对应办法的办法

/**
 * 用于寄存申请门路 与 controller对应关系的类
 * 设计成单例模型
 */
public class RequestPathContainer {
 private static List<BeanDefinition> requestList = new ArrayList<>();
 private static final ClassLoader classLoader = RequestPathContainer.class.getClassLoader();
 private static volatile RequestPathContainer instance = null;
​
 public static RequestPathContainer getInstance() {
 if (instance == null) {
 synchronized(RequestPathContainer.class){
 if (instance == null) {
 instance = new RequestPathContainer();
 }
 }
 }
 return instance;
 }
​
 private RequestPathContainer() {
​
 }
​
 public List<BeanDefinition> getRequestList() {
 return requestList;
 }
​
 // 扫描包
 public void scanner(String packetUrl) throws UnsupportedEncodingException, ClassNotFoundException {
 String url = packetUrl.replace(".", "/");
 URL resource = classLoader.getResource(url);
 if (resource == null) {
 return;
 }
 String path = resource.getPath();
 File file = new File(URLDecoder.decode(path, "UTF-8"));
 if (!file.exists()) {
 return;
 }
 if (file.isDirectory()){
 File[] files = file.listFiles();
 if (files == null) {
 return;
 }
 for (File f : files) {
 if (f.isDirectory()) {
 scanner(packetUrl + "." + f.getName());
 }
 if (f.isFile() && f.getName().endsWith(".class")) {
 String classname = f.getName().replace(".class", ""); // 去掉.class后缀名
 Class clazz = Class.forName(packetUrl + "." + classname);
 dealClass(clazz);
 }
 }
 }
 }
​
 // 筛选包中的类,并增加到List中
 private void dealClass(Class clazz) {
 if (!clazz.isAnnotationPresent(XxgController.class)) {
 // 没有controller注解
 return;
 }
 List<MethodDefinition> methodDefinitions = new ArrayList<>();
 Method[] methods = clazz.getDeclaredMethods();
 for (Method method : methods) {
 // 办法转 办法形容类
 MethodDefinition methodDefinition = convertMethodToMethodDefinition(method, clazz);
 if (methodDefinition != null) {
 methodDefinitions.add(methodDefinition);
 }
 }
 if (methodDefinitions.size() == 0) {
 return;
 }
 // 设置类形容类
 BeanDefinition beanDefinition = convertBeanToBeanDefinition(clazz, methodDefinitions);
 requestList.add(beanDefinition);
 }
​
 // 依据uri 和 申请办法 获取执行办法
 public MethodDefinition getMethodDefinition(String uri, String method) {
 for (BeanDefinition beanDefinition: requestList) {
 if (!uri.contains(beanDefinition.getControllerUrlPath())) {
 continue;
 }
 List<MethodDefinition> methodDefinitions = beanDefinition.getMethodDefinitions();
 for (MethodDefinition methodDefinition: methodDefinitions) {
 StringBuilder sb = new StringBuilder().append(beanDefinition.getControllerUrlPath());
 sb.append(methodDefinition.getRequestMappingUrlPath());
 if (!sb.toString().equals(uri)) {
 continue;
 }
 String[] allowedRequestMethods = methodDefinition.getAllowedRequestMethods();
 for (String str : allowedRequestMethods) {
 if (str.toUpperCase().equals(method.toUpperCase())) {
 // 申请门路 与 申请办法 均满足,返回该办法形容类
 return methodDefinition;
 }
 }
 }
 }
 return null;
 }
​
 /**
 * 将controller类 转换为 类的形容类
 */
 private BeanDefinition convertBeanToBeanDefinition(Class clazz, List<MethodDefinition> methodDefinitions) {
 BeanDefinition beanDefinition = new BeanDefinition();
 beanDefinition.setTypeName(clazz.getName());
 beanDefinition.setTypeClazz(clazz);
 XxgController controller = (XxgController) clazz.getAnnotation(XxgController.class);
 beanDefinition.setController(controller);
 beanDefinition.setControllerUrlPath(controller.value());
 beanDefinition.setMethodDefinitions(methodDefinitions);// 减少办法体
 return beanDefinition;
 }
​
 /**
 * 将办法 转换为 办法形容类
 */
 private MethodDefinition convertMethodToMethodDefinition(Method method, Class clazz) {
 if (!method.isAnnotationPresent(XxgRequestMapping.class)) {
 // 没有RequestMapping注解
 return null;
 }
 method.setAccessible(true);
 Parameter[] parameters = method.getParameters();
 // 设置参数形容类
 List<ParameterDefinition> parameterDefinitions = new ArrayList<>();
 for ( Parameter parameter : parameters) {
 ParameterDefinition parameterDefinition = convertParamToParameterDefinition(parameter);
 parameterDefinitions.add(parameterDefinition);
 }
 // 设置办法形容类
 MethodDefinition methodDefinition = new MethodDefinition();
 methodDefinition.setParameterDefinitions(parameterDefinitions);  // 减少参数列表
 methodDefinition.setMethod(method);
 methodDefinition.setMethodName(method.getName());
 methodDefinition.setResult(method.getReturnType());
 XxgRequestMapping requestMapping = method.getAnnotation(XxgRequestMapping.class);
 methodDefinition.setRequestMappingUrlPath(requestMapping.value());
 methodDefinition.setRequestMapping(requestMapping);
 methodDefinition.setAllowedRequestMethods(requestMapping.methods());
 methodDefinition.setParentClazz(clazz);
 return methodDefinition;
 }
​
 /**
 * 将参数 转换为 参数形容类
 */
 private ParameterDefinition convertParamToParameterDefinition(Parameter parameter) {
 ParameterDefinition parameterDefinition = new ParameterDefinition();
 if ( parameter.isAnnotationPresent(XxgParam.class)) {
 parameterDefinition.setParamName(parameter.getAnnotation(XxgParam.class).value());
 } else {
 parameterDefinition.setParamName(parameter.getName());
 }
 parameterDefinition.setParamClazz(parameter.getType());
 parameterDefinition.setParamType(parameter.getType());
 parameterDefinition.setRequestBody(parameter.isAnnotationPresent(XxgRequestBody.class));
 return parameterDefinition;
 }
​
}

全局servlet

不应用拦截器,依然应用servlet来进行路由散发。此servlet监听/

public class DispatcherServlet extends HttpServlet {
 private ObjectMapper objectMapper = new ObjectMapper();
​
 @Override
 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
​
 // 编码设置
 resp.setContentType("text/json;charset=utf-8");
 RequestPathContainer requestPathContainer = RequestPathContainer.getInstance();
 MethodDefinition methodDefinition = requestPathContainer.getMethodDefinition(req.getRequestURI(), req.getMethod());
​
 if (methodDefinition == null) {
 resp.setStatus(404);
 sendResponse(R.failed("申请门路不存在"), req, resp);
 return;
 }
​
 List<ParameterDefinition> parameterDefinitions = methodDefinition.getParameterDefinitions();
 List<Object> params = new ArrayList<>(parameterDefinitions.size());
 for (ParameterDefinition parameterDefinition : parameterDefinitions) {
 try {
 Object value = dealParam(parameterDefinition, req, resp);
 params.add(value);
 } catch (ParamException e) {
 resp.setStatus(404);
 sendResponse(R.failed(e.getMessage()), req, resp);
 return ;
 }
 }
​
 try {
 Object result = methodDefinition.getMethod().invoke(methodDefinition.getParentClazz().newInstance(), params.toArray());
 sendResponse(result, req, resp);
 } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
 e.printStackTrace();
 sendResponse(e.getMessage(), req, resp);
 }
​
 }
​
 /**
 * 解决参数
 * @param parameterDefinition
 * @param req
 * @param resp
 */
 private Object dealParam(ParameterDefinition parameterDefinition, HttpServletRequest req, HttpServletResponse resp) throws ParamException, IOException {
 Object value;
 String data = "";
 if (parameterDefinition.isRequestBody()) {
 // 从申请体(request的输出流)中获取数据
 data = getJsonString(req);
 } else if (parameterDefinition.getParamType() == HttpServletRequest.class) {
 return req;
 } else if (parameterDefinition.getParamType() == HttpServletResponse.class) {
 return resp;
 } else if (isJavaType(parameterDefinition)) {
 // 从url中取出参数
 data = req.getParameter(parameterDefinition.getParamName());
 if(data == null) {
 throw new ParamException("服务器无奈拿到申请数据,请查看申请头等");
 }
 } else {
 // 将申请url中的参数封装成对象
 try {
 Object obj = parameterDefinition.getParamClazz().newInstance();
 ConvertUtils.register(new DateConverter(), Date.class);
 BeanUtils.populate(obj, req.getParameterMap());
 return obj;
 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
 throw new ParamException("未找到参数'" + parameterDefinition.getParamName() + "'对应的值");
 }
 }
 try {
 value = objectMapper.readValue(data, parameterDefinition.getParamClazz());
 } catch (JsonProcessingException e) {
 String errMsg = "参数'" + parameterDefinition.getParamName() +
 "'须要'" + parameterDefinition.getParamType() +
 "类型";
 throw new ParamException(errMsg);
 }
 return value;
 }
​
 private void sendResponse(Object result, HttpServletRequest req, HttpServletResponse resp) throws IOException {
 if (result == null) {
 return;
 }
 resp.setContentType("text/json;charset=utf-8");
 objectMapper.writeValue(resp.getWriter(), result);
 }
​
 /**
 * 判断参数是否是一般类型
 * @return
 */
 private boolean isJavaType(ParameterDefinition parameterDefinition) {
 Object[] javaTypes = MyJavaType.getJavaTypes();
 for (Object item : javaTypes) {
 if (item.equals(parameterDefinition.getParamClazz())) {
 return true;
 }
 }
 return false;
 }
​
 /**
 * 获取申请头的json字符串
 */
 private String getJsonString(HttpServletRequest req) throws IOException {
 BufferedReader br = new BufferedReader(new InputStreamReader(req.getInputStream(), "utf-8"));
 char[] chars = new char[1024];
 int len;
 StringBuilder sb = new StringBuilder();
 while ((len = br.read(chars)) != -1) {
 sb.append(chars, 0, len);
 }
 return sb.toString();
 }
}

servletcontext监听器初始化容器

@Override
 public void contextInitialized(ServletContextEvent servletContextEvent) {
 RequestPathContainer requestPathContainer = RequestPathContainer.getInstance();
 String configClassName = servletContextEvent.getServletContext().getInitParameter("config");
 Class appListenerClass = null;
 try {
 appListenerClass = Class.forName(configClassName);
 XxgScanner xxgScanner = (XxgScanner)appListenerClass.getAnnotation(XxgScanner.class);
 if (xxgScanner != null) {
 try {
 requestPathContainer.scanner(xxgScanner.value()); // 扫描controller类,初始化List
 } catch (UnsupportedEncodingException | ClassNotFoundException e) {
 e.printStackTrace();
 }
 }
 } catch (ClassNotFoundException e) {
 e.printStackTrace();
 }
 }

遗留的问题

动态资源也被拦挡了

解决动态资源

default servlet

关上tomcat的conf/web.xml文件,能够发现tomcat默认有个default servlet,有如下配置:

 <servlet>
 <servlet-name>default</servlet-name>
 <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
 <init-param>
 <param-name>debug</param-name>
 <param-value>0</param-value>
 </init-param>
 <init-param>
 <param-name>listings</param-name>
 <param-value>false</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
 </servlet>

然而他并没有匹配servlet-mapping,即解决的门路,那么能够在咱们我的项目的web.xml中做以下配置来解决动态资源:

<!--  将全局拦截器的匹配/* 改成 / 。必须-->
<!--  /示意只解决其余的servlet不能匹配的门路-->
<servlet-mapping>
 <servlet-name>DispatcherServlet</servlet-name>
 <url-pattern>/</url-pattern>
 </servlet-mapping>
<!--  动态资源-->
 <servlet-mapping>
 <servlet-name>default</servlet-name>
 <url-pattern>*.html</url-pattern>
 </servlet-mapping>
 <servlet-mapping>
 <servlet-name>default</servlet-name>
 <url-pattern>*.js</url-pattern>
 </servlet-mapping>
 <servlet-mapping>
 <servlet-name>default</servlet-name>
 <url-pattern>*.css</url-pattern>
 </servlet-mapping>
 <servlet-mapping>
 <servlet-name>default</servlet-name>
 <url-pattern>*.jpg</url-pattern>
 </servlet-mapping>

最初

一.本文其实次要做了以下两个操作

  1. 服务器启动时,扫描controller包,将合乎咱们预期的类、办法、参数拆卸到容器中。
  2. 前端拜访服务器,获取容器中指定门路对应的办法
    2.1 将拜访参数按不同类型拆卸到参数列表中
    2.2 执行对应办法
    2.3 解决办法返回数据

二.参考阐明

1.0版是博主本人思考并实现的。
2.0版是博主的小高老师给博主讲了思路,写进去后又看了小高老师的实现,而后综合着欠缺的。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理