学校课程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的接口实现类,参数注解注入也可以优化成还能够动态参数注入,只需写一个配置方法或者一个配置类就能注入想要的参数。异常处理也是如此,只能处理一个我自己的自定义异常,如果抛出其他的异常就没办法处理。这个工具也只能满足我自己使用,可以优化得更加灵活。