java的springmvc框架_java的springMvc框架简单实现

前言

在本文中,博主一步步地从servlet到controller层实现一个简单的框架。通过此框架,我们可以像spring那样使用以下基础注解:@XxgController

@XxgRequestMapping

@XxgParam

@XxgRequestBody

观看本文之前,你或许应该先了解以下内容:BeanUtils

ObjectMapper

Servlet相关知识

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

思考:拦截器可以在servlet之前拦截所有请求路径

可以找到注解中路径与请求路径相匹配的那个方法

然后将req,resp转发给该方法来执行

问题:

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

分析:

包扫描,就涉及IO流, 而File类可以递归查询其下面所有的文件,我们

可以过滤一下:只要后缀名为.class的文件,并获取其className(包括包路径)

通过反射获取这个类,判断其是否有指定的注解进而再次过滤

这样在拦截器拦截到请求路径,我们可以进行匹配并调用该方法。

偷个懒:

因为MVC设计模式,我们一般把api接口都放在同一个包下,所以我们可以直接指定要扫描包,其它包就不管

一.扫描类1.0版的实现public class FileScanner {

private final String packetUrl = "com.dbc.review.controller";

private final ClassLoader classLoader = FileScanner.class.getClassLoader();

private List allClazz = new ArrayList<>(10); //存该包下所有用了注解的类

public List 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,并能通过路径映射实现访问。但很明显有至少以下问题:执行方法时,方法不能有参数。不符合业务需求

每次访问,都要反复处理Class反射来找到路径映射的方法,效率低。

针对以上2个问题,我们在2.0版进行一下修改:将controller、requestmapping对应方法,方法对应参数的可能用到的相关信息存放在一个容器中。在服务器初次启动时进行扫描,并装配到容器中。这样在每次访问时,遍历这个容器,比1.0版的容器更方便。

定义参数类型,通过注解@XxgRequestBody以及@XxgParam区分参数从请求体拿或者从url的?后面拿。从而获取前端传来的数据

通过ObjectMapper进行不同类型参数的装配,最后调用方法的invoke实现带参/不带参的方法处理。

BeanDefinition/**

* 用来存放controller类的相关参数、方法等

*/

@Data

@NoArgsConstructor

@AllArgsConstructor

public class BeanDefinition {

private Class typeClazz; // 类对象

private String typeName; // 类名

private Object annotation; // 注解

private String controllerUrlPath; // controller的path路径

private List methodDefinitions; // 带有RequestMapping的注解

}

MethodDefinition/**

* 描述方法的类

*/

@Data

@NoArgsConstructor

@AllArgsConstructor

public class MethodDefinition {

private Class parentClazz; // 所属父类的class

private Method method; // 方法

private String methodName; // 方法名

private Object annotation; // 注解类

private String requestMappingUrlPath; // url

private String[] allowedRequestMethods; // allowedRequestMethods

private List 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 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 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 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 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 methodDefinitions) {

BeanDefinition beanDefinition = new BeanDefinition();

beanDefinition.setTypeName(clazz.getName());

beanDefinition.setTypeClazz(clazz);

XxgController controller = (XxgController) clazz.getAnnotation(XxgController.class);

beanDefinition.setAnnotation(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 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.setAnnotation(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 parameterDefinitions = methodDefinition.getParameterDefinitions();

List 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,有如下配置:

default

org.apache.catalina.servlets.DefaultServlet

debug

0

listings

false

1

但是他并没有匹配servlet-mapping,即处理的路径,那么可以在我们项目的web.xml中做以下配置来处理静态资源:

DispatcherServlet

/

default

*.html

default

*.js

default

*.css

default

*.jpg

最后

一.本文其实主要做了以下两个操作服务器启动时,扫描controller包,将符合我们预期的类、方法、参数装配到容器中。

前端访问服务器,获取容器中指定路径对应的方法

2.1 将访问参数按不同类型装配到参数列表中

2.2 执行对应方法

2.3 处理方法返回数据

二.参考说明项目实现过程

1.0版是博主自己思考并完成的。

2.0版是博主的小高老师给博主讲了思路,写出来后又看了小高老师的实现,然后综合着完善的。在写了文章后,博主对项目中不同类进行了解耦等操作,代码重构了一番,主要为了应付开闭原则、单一职责原则等。

传送门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值