基于Servlet封装的Web层工具

学校课程JavaWeb大作业老师要求写个系统,不得用框架,我当时想,不用框架代码的重复性高,而且文件多且复杂,接口阅读性也低,写着很不舒服。

如果能封装一个能够像SpringMVC一样使用注解来简化代码的web层,就能够提升作业的完成速度,也能更加理解JavaWeb。

于是我封装了几个注解还有监听器过滤器以及创建了几个静态仓库。

这是web层封装的目录:

web.xml 

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>BaseServlet</servlet-name>
        <servlet-class>org.lingdang.web.BaseServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>BaseServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.lingdang.web.BaseServletContextListener</listener-class>
    </listener>
    <filter>
        <filter-name>BaseFilter</filter-name>
        <filter-class>org.lingdang.web.BaseFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>BaseFilter</filter-name>
        <url-pattern>*</url-pattern>
    </filter-mapping>
</web-app>

封装一个请求Bean,包括 匹配路径,执行的方法,所执行方法的类 三个属性

@Getter
@AllArgsConstructor
public class RequestBean {
    private final String path;
    private final Method method;
    private final Class<?> aClass;
}

 创建一个仓库类,放置get,post,put,delete四个请求类型的静态仓库,有初始化方法,每个仓库都有get方法和新增请求Bean的方法

public class RequestRepository {
    private static List<RequestBean> getRequests;
    private static List<RequestBean> postRequests;
    private static List<RequestBean> putRequests;
    private static List<RequestBean> deleteRequests;

    public static void init(){
        getRequests=new ArrayList<>();
        postRequests=new ArrayList<>();
        putRequests=new ArrayList<>();
        deleteRequests = new ArrayList<>();
    }
    public static List<RequestBean> getGetRequests() {
        return getRequests;
    }
    public static List<RequestBean> getPostRequests() {
        return postRequests;
    }
    public static List<RequestBean> getPutRequests() {
        return putRequests;
    }
    public static List<RequestBean> getDeleteRequests() {
        return deleteRequests;
    }
    public static void addGetRequest(RequestBean getRequest) {
        RequestRepository.getRequests.add(getRequest);
    }
    public static void addPostRequest(RequestBean postRequest) {
        RequestRepository.postRequests.add(postRequest);
    }
    public static void addPutRequest(RequestBean putRequest) {
        RequestRepository.putRequests.add(putRequest);
    }
    public static void addDeleteRequest(RequestBean deleteRequest) {
        RequestRepository.deleteRequests.add(deleteRequest);
    }
}

 启动项目时,先走监听器BaseServletContextListener初始化请求仓库

public class BaseServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        try {
            //初始化请求仓库
            RequestRepository.init();
            //得到controller层的所有类
            List<Class<?>> classes = getCls();
            for (Class<?> aClass : classes) {
                //判断是否有Controller注解
                if (aClass.isAnnotationPresent(Controller.class)) {
                    Controller controller = aClass.getAnnotation(Controller.class);
                    Method[] methods = aClass.getDeclaredMethods();
                    //把有Controller注解的类的所有方法遍历
                    for (Method method : methods) {
                        //有Get注解就把此路径,此类,此方法封装成RequestBean,添加进Get请求仓库
                        if (method.isAnnotationPresent(Get.class)) {
                            Get get = method.getAnnotation(Get.class);
                            //路径是Controller注解的路径加上Get注解的路径
                            String url = controller.value() + get.value();
                            RequestBean requestBean = new RequestBean(url, method, aClass);
                            RequestRepository.addGetRequest(requestBean);
                        } else if (method.isAnnotationPresent(Post.class)) {
                            Post post = method.getAnnotation(Post.class);
                            String url = controller.value() + post.value();
                            RequestBean requestBean = new RequestBean(url, method, aClass);
                            RequestRepository.addPostRequest(requestBean);
                        } else if (method.isAnnotationPresent(Put.class)) {
                            Put put = method.getAnnotation(Put.class);
                            String url = controller.value() + put.value();
                            RequestBean requestBean = new RequestBean(url, method, aClass);
                            RequestRepository.addPutRequest(requestBean);
                        } else if (method.isAnnotationPresent(Delete.class)) {
                            Delete delete = method.getAnnotation(Delete.class);
                            String url = controller.value() + delete.value();
                            RequestBean requestBean = new RequestBean(url, method, aClass);
                            RequestRepository.addDeleteRequest(requestBean);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

向后端发送请求时,先走拦截器BaseFilter,把编码格式设置成utf-8

public class BaseFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        String encoding = "utf-8";
        servletRequest.setCharacterEncoding(encoding);
        servletResponse.setCharacterEncoding(encoding);
        filterChain.doFilter(servletRequest, servletResponse);
    }
    @Override
    public void destroy() {}
}

向后端发送请求时,走BaseServlet

先判断是什么请求方法,然后获取对应请求方法的请求仓库,然后调用doWeb方法

public class BaseServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        List<RequestBean> getRequests = RequestRepository.getGetRequests();
        doWeb(getRequests, req, resp);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        List<RequestBean> postRequests = RequestRepository.getPostRequests();
        doWeb(postRequests, req, resp);
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        List<RequestBean> putRequests = RequestRepository.getPutRequests();
        doWeb(putRequests, req, resp);
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        List<RequestBean> deleteRequests = RequestRepository.getDeleteRequests();
        doWeb(deleteRequests, req, resp);
    }

doWeb是利用路径反射

用url去匹配所要执行的方法

赋值方法参数执行方法,并得到方法的返回值

中途如果抛出自定义错误进行失败处理,顺利进行返回值成功处理

private void doWeb(List<RequestBean> getRequests, HttpServletRequest req, HttpServletResponse resp) {
    try {
        //获取请求路径
        String url = req.getRequestURI();
        for (RequestBean request : getRequests) {
            //路径匹配
            if (urlPath(request.getPath(), url)) {
                //匹配成功后获取RequestBean中的类和方法
                Method method = request.getMethod();
                Class<?> aClass = request.getaClass();
                //获取此方法的所有参数
                Parameter[] parameters = method.getParameters();
                //无参构造controller类
                Object o = aClass.newInstance();
                Object[] args = new Object[parameters.length];
                //遍历参数,如果参数有对应注解,赋对应的值,否则为null
                for (int i = 0; i < parameters.length; i++) {
                    //request
                    if (parameters[i].isAnnotationPresent(Req.class)) {
                        args[i] = req;
                    }
                    //response
                    else if (parameters[i].isAnnotationPresent(Resp.class)) {
                        args[i] = resp;
                    }
                    //session
                    else if (parameters[i].isAnnotationPresent(Session.class)) {
                        args[i] = req.getSession();
                    }
                    //Body:前端传来json数据
                    else if (parameters[i].isAnnotationPresent(Body.class)) {
                        //如果body注解没值,把json转换为实体类
                        if (parameters[i].getAnnotation(Body.class).value().equals("")) {
                            Class<?> parameterClass = parameters[i].getType();
                            Object body = getBody(req, parameterClass);
                            args[i] = body;
                        }
                        //如果body有值,则获取所对应的值
                        else {
                            String body = getBody(req, parameters[i].getAnnotation(Body.class).value());
                            args[i] = body;
                        }
                    }
                    //Param
                    else if (parameters[i].isAnnotationPresent(Param.class)) {
                        String value = parameters[i].getAnnotation(Param.class).value();
                        args[i] = req.getParameter(value);
                    }
                    //PathVariable:路径参数
                    else if (parameters[i].isAnnotationPresent(PathVariable.class)) {
                        String value = parameters[i].getAnnotation(PathVariable.class).value();
                        //获得此路径中参数的所有路径参数
                        Map<String, String> map = getPathVariableMap(request.getPath(), url);
                        args[i] = map.get(value);
                    } else {
                        args[i] = null;
                    }
                }
                //执行方法
                Object result = method.invoke(o, args);
                //没报InvocationTargetException这个错误,则进行成功返回
                successResult(resp, result);
            }
        }
    } catch (InvocationTargetException e) {
        //报InvocationTargetException错误,如果反射中抛出的错误是自定义异常
        if (e.getTargetException().getClass().equals(MyException.class)) {
            //进行错误返回
            errorResult(resp, Result.error(e.getTargetException().getMessage()));
        }
    } catch (InstantiationException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

成功返回,把映射方法的返回值转化为json返回给前端

private void successResult(HttpServletResponse response, Object result) {
    try {
        String json = JSON.toJSONString(result);
        response.getWriter().println(json);
    } catch (IOException e) {
        System.out.println("响应失败");
    }
}

错误返回,封装返回结果的信息转化为json返回给前端,状态码设置为400

private void errorResult(HttpServletResponse response, Result<?> result) {
    try {
        String json = JSON.toJSONString(result);
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        response.getWriter().println(json);
    } catch (IOException e) {
        System.out.println("响应失败");
    }
}

路径匹配算法

private boolean urlPath(String path, String url) {
    if (url.contains("?")) {
        int i = url.indexOf("?");
        url = url.substring(0, i);
    }
    String[] urls = url.split("/");
    String[] paths = path.split("/");
    if (urls.length != paths.length) return false;
    for (int i = 1; i < urls.length; i++) {
        if (!urls[i].equals(paths[i])) {
            if (paths[i].charAt(0) != '{' || paths[i].charAt(paths[i].length() - 1) != '}') {
                return false;
            }
        }
    }
    return true;
}

获取路径参数算法

private Map<String, String> getPathVariableMap(String path, String url) {
    HashMap<String, String> map = new HashMap<>();
    if (url.contains("?")) {int i = url.indexOf("?");url = url.substring(0, i);}
    String[] urls = url.split("/");
    String[] paths = path.split("/");
    if (urls.length != paths.length) return map;
    for (int i = 1; i < urls.length; i++) {
        if (paths[i].charAt(0) != '{' && paths[i].charAt(paths[i].length() - 1) != '}') {
            String substring = paths[i + 1].substring(1, paths[i + 1].length() - 1);
            map.put(substring, urls[i + 1]);
        }
    }
    return map;
}

获取body值方法,解析json

private <T> T getBody(HttpServletRequest req, Class<T> t) {
    try {
        //request获取输入流
        BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8));
        String str;
        StringBuffer sb = new StringBuffer();
        while ((str = reader.readLine()) != null) {
            sb.append(str);
        }
        reader.close();
        T object = JSON.parseObject(sb.toString(), t);
        System.out.println(object);
        return object;
    } catch (Exception e) {
        return null;
    }
}

private String getBody(HttpServletRequest req, String s) {
    try {
        InputStreamReader inputStreamReader = new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8);
        BufferedReader reader = new BufferedReader(inputStreamReader);
        String str;
        StringBuffer sb = new StringBuffer();
        while ((str = reader.readLine()) != null) {
            sb.append(str);
        }
        reader.close();
        inputStreamReader.close();
        JSONObject json = JSON.parseObject(sb.toString());
        return json.getString(s);
    } catch (Exception e) {
        return null;
    }
}

因此我的web层这样写

@Controller("/customer")
public class CustomerController {
    CustomerService customerService = new CustomerServiceImpl();
    
    @Post("/add")
    public Result<?> addCustomer(@Body CustomerCreateDto customerCreateDto, @Session HttpSession session) {
        User user = (User) session.getAttribute("user");
        this.customerService.addCustomer(customerCreateDto.rules(), user);
        return Result.successMsg("客户添加成功");
    }

    @Get("/page")
    public Result<?> findPageCustomers(@Param("currentPage") Integer currentPage, @Param("pageSize") Integer pageSize,
                                       @Param("search") String search) {
        PageDto<Customer> pageDto = new PageDto<>(currentPage, pageSize, search);
        PageResult<Customer> pageCustomers = this.customerService.findPageCustomers(pageDto.rules());
        return Result.success(pageCustomers);
    }

    @Get("/my")
    public Result<?> findMyCustomers(@Param("currentPage") Integer currentPage, @Param("pageSize") Integer pageSize,
                                     @Param("search") String search, @Session HttpSession session) {
        PageDto<Customer> pageDto = new PageDto<>(currentPage, pageSize, search);
        User user = (User) session.getAttribute("user");
        PageResult<Customer> pageCustomers = this.customerService.findMyCustomers(pageDto.rules(), user.getId());
        return Result.success(pageCustomers);
    }

    @Put("/update/{id}")
    public Result<?> updateCustomer(@PathVariable("id") Integer id, @Body CustomerUpdateDto customerUpdateDto,
                                    @Session HttpSession session) {
        User user = (User) session.getAttribute("user");
        this.customerService.updateCustomer(id, customerUpdateDto.rules(), user.getId());
        return Result.successMsg("客户修改成功");
    }

    @Delete("/delete/{id}")
    public Result<?> deleteCustomer(@PathVariable("id") Integer id) {
        this.customerService.deleteCustomer(id);
        return Result.successMsg("删除成功");
    }
    
}

是不是有点SpringMVC的感觉,原来增删改查需要4个Servlet类现在只需要一个Controller类

获得body数据本来是一个需要一大堆代码才能获得,如今只需要一个参数注解就能获得

路径参数也只需要一个带值的注解就能获得

session和param参数也是如此

异常处理也变得简便,直接throw我的自定义异常,并输入错误信息,就能直接走异常返回。

总结:这只是封装的一个web层工具,还有很多地方需要优化,性能也没有正式测试过,不知会不会反而比原生Servlet低,但是大大提高了开发效率是肉眼可见的,能够让我写代码偷懒,少些一些代码。最大的收获了练习了反射和注解这两大Java基础的难点,学会了反射的很多方法的使用以及注解配合反射的使用。也复习了Servlet以及监听器过滤器的使用。

还有一些细节可以做得更好,可以模仿spring的依赖注入在controller层自动注入service的接口实现类,参数注解注入也可以优化成还能够动态参数注入,只需写一个配置方法或者一个配置类就能注入想要的参数。异常处理也是如此,只能处理一个我自己的自定义异常,如果抛出其他的异常就没办法处理。这个工具也只能满足我自己使用,可以优化得更加灵活。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值