这里续写上一章博客(110章博客):
现在我们来学习一下高级的技术,前面的mvc知识,我们基本可以在67章博客及其后面相关的博客可以学习到,现在开始学习精髓:
Spring MVC 高级技术:
拦截器(Inteceptor)使用:
监听器、过滤器和拦截器对⽐(前面两个在53章博客可以学习到,后面只是名称上的解释,如果可以,那么前面两个也可以说成是拦截器,所以存在很多框架的拦截器):
Servlet:处理Request请求和Response响应
过滤器(Filter):对Request请求起到过滤的作用,作用在Servlet之前,如果配置为/*可以对所 有的资源访问(servlet、js/css静态资源等)进行过滤处理
监听器(Listener):实现了javax.servlet.ServletContextListener 接⼝的服务器端组件,它随Web应用的启动⽽启动,只初始化一次,然后会一直运行监视,随Web应用的停⽌⽽销毁
监听器的作用与过滤器虽然都有拦截的意思,但是偏重不同
监听器可以选择对一些数据或者说数据变化进行监听以及拦截,而过滤则是对过来的请求直接拦截,而不是更加里面的数据拦截,所以一般情况下,过滤器通常是在监听器之前进行拦截的
那么说监听器一般有如下的作用:
作用一:做一些初始化工作,web应用中spring容器启动ContextLoaderListener
作用⼆:监听web中的特定事件,⽐如HttpSession,ServletRequest的创建和销毁,变量的创建、 销毁和修改等,可以在某些动作前后增加处理,实现监控,⽐如统计在线⼈数,利⽤HttpSessionLisener等
拦截器(Interceptor):是SpringMVC、Struts等表现层框架自己的,不会拦截jsp/html/css/image的访问等,只会拦截访问的控制器方法(Handler),一般来说,这个拦截在一定程度上使用了过滤器以及监听器,因为需要确定拦截的数据,通常需要先获得,所以mvc的拦截器的实现方式通常在于过滤器或者说监听器(注意,只是因为他需要对应的数据,所以他才会在于其他的器,如过滤和监听,否则拦截器一般只是拦截指定数据的处理,而不是在于什么)
根据上面的说明,其实从配置的⻆度也能够总结发现:serlvet、filter、listener是配置在web.xml中的,⽽interceptor是 配置在表现层框架自己的配置⽂件中的,所以Interceptor一般是框架自己的
根据前面的说明,可以知道如下:
拦截器会在如下的情况可能会发生拦截:
Handler业务逻辑执行之前拦截一次(操作url)
在Handler逻辑执行完毕但未跳转⻚⾯之前拦截一次(操作转发,如果没有,那么一般操作响应数据,如果也没有,那么虽然拦截,但并未做什么)
在跳转⻚⾯之后拦截一次(比如对应的json的处理,或者并未处理)
前面我们知道了这个图:
以及他的说明:
流程说明:
第一步:用户发送请求⾄前端控制器DispatcherServlet
第⼆步:DispatcherServlet收到请求调⽤HandlerMapping处理器映射器(一般是map保存的)
第三步:处理器映射器根据请求Url找到具体的Handler(后端控制器,可以根据xml配置、注解进行查找,因为查找,所以是映射),⽣成处理器对象及处理器拦截器(如果有则⽣成)一并返回DispatcherServlet,他负责创建
第四步:DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
第五步:处理器适配器执⾏Handler(controller的方法,生成对象了,这里相当于调用前面的handle01方法,他负责调用)
第六步:Handler执行完成给处理器适配器返回ModelAndView,即处理器适配器得到返回的ModelAndView,这也是为什么前面我们操作方法时,是可以直接操作他并返回的,而返回给的人就是处理器适配器,就算你不返回,那么处理器适配器或者在之前,即他们两个中间,可能会进行其他的处理,来设置ModelAndView,并给处理器适配器
第七步:处理器适配器向前端控制器返回 ModelAndView(因为适配或者返回数据,所以是适配),ModelAndView 是SpringMVC 框架的一个 底层对 象,包括 Model 和 View
第⼋步:前端控制器请求视图解析器去进行视图解析,根据逻辑视图名来解析真正的视图(加上前后的补充,即前面的配置视图解析器)
第九步:视图解析器向前端控制器返回View
第⼗步:前端控制器进行视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域,改变了servlet,最终操作servlet来进行返回
第⼗一步:前端控制器向用户响应结果(jsp的)
即可以理解:请求找路径并返回(1,2,3),给路径让其判断路径并返回且获得对应对象(4,5,6,7),变成参数解析(如拼接) 进行转发(8,9),然后到jsp(10),最后渲染(11)
所以说:
第一次拦截:在1和5中间处理
第二次拦截:在6到8中间处理
第三次拦截:在9到11中间处理
其实通过图片,我们应该知道,第一次拦截应该是在3和4中在前端控制器旁边处理,而7和8就是第二次拦截,10到11则是第三次拦截(这里也可能是9到10)
所以可以知道,其实mvc自带的有一些拦截,这也是对应注解,比如@RequestBody或者@ResponseBody可以操作的原因
当然,我们也可以进行添加拦截,这在后面会说明的
为了更加的知道拦截器的处理,我们直接来进行实战:
注意:mvc的拦截器是里面的,也就是说,servlet原本的过滤器必然先处理或者后处理
实际上我们学习源码很大程度是必须要有实战的,因为一个框架的源码我们基本是不可能全部读完的,这取决于一个框架是由很长时间的迭代,以及很多人一起开发完成的,当然,如果你的框架够小,那么可以是单独完成,在这种情况下,学习框架中,用阅读源码来学习,我们只能知道他的一点实现方式,所以学习框架通常需要直接的实战来进行学习,来直接的确定他的作用,而不是单独看源码来确定作用(你怎么知道他有没有其他关联,并且要知道这个关联需要看更多的源码),也就是说,实际上源码的解析大多数是让你知道他的实现方式,而不是具体细节(比如,他为什么这样定义变量等等),当然,除了实现方式有时候也需要学习设计模式,这个在以后会单独给一个博客来进行处理的,先了解一些框架的设计模式再说
那么,既然要实战,我们首先需要操作一个项目,项目如下:
对应的依赖,在前面我们已经给过多次了,这里我们继续给出吧
< packaging> war</ packaging> < dependencies> < dependency> < groupId> org.springframework</ groupId> < artifactId> spring-webmvc</ artifactId> < version> 5.1.5.RELEASE</ version> </ dependency> < dependency> < groupId> javax.servlet</ groupId> < artifactId> javax.servlet-api</ artifactId> < version> 3.1.0</ version> </ dependency> < dependency> < groupId> com.fasterxml.jackson.core</ groupId> < artifactId> jackson-databind</ artifactId> < version> 2.9.8</ version> </ dependency> < dependency> < groupId> com.fasterxml.jackson.core</ groupId> < artifactId> jackson-core</ artifactId> < version> 2.9.8</ version> </ dependency> < dependency> < groupId> com.fasterxml.jackson.core</ groupId> < artifactId> jackson-annotations</ artifactId> < version> 2.9.0</ version> </ dependency> </ dependencies>
如果需要补充,自行补充吧
对应的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> dispatcherServlet</ servlet-name> < servlet-class> org.springframework.web.servlet.DispatcherServlet</ servlet-class> < init-param> < param-name> contextConfigLocation</ param-name> < param-value> classpath:springmvc.xml</ param-value> </ init-param> </ servlet> < servlet-mapping> < servlet-name> dispatcherServlet</ servlet-name> < url-pattern> /</ url-pattern> </ servlet-mapping> </ web-app>
index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<button id="btn">ajax提交</button>
<script>$("#btn").click(function () {let url = 'test/in';let data = '[{"id":1,"username":"张三"},{"id":2,"username":"李四"}]';$.ajax({type: 'POST',//大小写可以忽略url: url,data: data,contentType: 'application/json;charset=utf-8',success: function (data) {console.log(data);alert(data)}})})
</script>
</body>
</html>
springmvc.xml:
< beans xmlns = " http://www.springframework.org/schema/beans" xmlns: mvc= " http://www.springframework.org/schema/mvc" xmlns: context= " http://www.springframework.org/schema/context" xmlns: xsi= " http://www.w3.org/2001/XMLSchema-instance" xsi: schemaLocation= " http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd" > < context: component-scan base-package = " com.controller" /> < mvc: annotation-driven> </ mvc: annotation-driven>
</ beans>
entity里面的User类:
package com. entity ; public class User { String id; String username; public String getId ( ) { return id; } public void setId ( String id) { this . id = id; } public String getUsername ( ) { return username; } public void setUsername ( String username) { this . username = username; } @Override public String toString ( ) { return "User{" + "id='" + id + '\'' + ", username='" + username + '\'' + '}' ; }
}
test类:
package com. controller ; import com. entity. User ;
import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. RequestBody ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. ResponseBody ; import java. util. List ; @Controller
@RequestMapping ( "/test" )
public class test { @RequestMapping ( "in" ) @ResponseBody public List < User > ajax ( @RequestBody List < User > list) { System . out. println ( list) ; return list; }
}
自行配置tomcat,启动,运行看看结果,那么我们基础操作搭建完毕,现在我们来操作一下拦截:
在com包下创建Interceptor包,然后创建MyInterceptor类:
package com. Interceptor ; import org. springframework. web. servlet. HandlerInterceptor ; public class MyInterceptor implements HandlerInterceptor {
}
其中HandlerInterceptor接口如下:
package org. springframework. web. servlet ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import org. springframework. lang. Nullable ; public interface HandlerInterceptor { default boolean preHandle ( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true ; } default void postHandle ( HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion ( HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }
}
这三个代表了三个地方,前面的三个拦截,比如:
preHandle是第一个拦截,postHandle是第二个拦截,afterCompletion是第三个拦截,实际上一个框架的编写,除了给出一些功能外,还需要存在扩展功能,有的话最好,而拦截的处理基本就是框架基本的扩展了(所以在spring中也存在多个拦截的处理,包括mybatis,当然,他们可能并没有特别的说明是拦截,但是你也或多或少可以知道,可以操作一些类来实现在中间进行处理的方式,这其实也算是一种拦截,因为是我们自行处理的,所以拦截器在某些方面可以是这样的认为:框架自身给出可以扩展的方式都可以称为拦截器)
实际上通过前面我们也明白,前端控制器底层基本上也是操作get和post,而servlet也是,但是mvc是建立在servlet上的,所以前端控制器通常也是生成了servlet,在前面我们学习了,前端控制器只是生成一个servlet(一般也可以是他自己),其中只是操作了拦截进行的处理,这个拦截或多或少使用了过滤或者监听,所以说,具体的是否监听或者过滤的处理,可能也是对应的配置导致进行的某些配置再处理(因为你删除了配置前端控制器的,拦截也就不复存在了),那么在这种情况下,我们的三次拦截,也只是其中多个拦截的扩展,那么如果这个时候,你操作了传统的拦截,那么就需要看配置的先后顺序
我们继续修改MyInterceptor类的内容:
package com. Interceptor ; import org. springframework. web. servlet. HandlerInterceptor ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ; public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle ( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System . out. println ( handler) ; System . out. println ( "Handler业务逻辑执行之前拦截一次,我是第一次" ) ; return true ; } @Override public void postHandle ( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System . out. println ( handler) ; System . out. println ( modelAndView) ; System . out. println ( "在Handler逻辑执行完毕但未跳转页面之前拦截一次,我是第二次" ) ; } @Override public void afterCompletion ( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System . out. println ( handler) ; System . out. println ( ex) ; System . out. println ( "在跳转页面之后拦截一次,我是第三次" ) ; }
}
我们写好了拦截器,自然需要进行使用,在mvc中使用,是必须需要配置的,并且他是mvc的,所以是需要在mvc的对应的配置文件中进行配置:
对应的springmvc.xml的补充:
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor" > </ bean> </ mvc: interceptor> </ mvc: interceptors>
实际上mvc:interceptors代表可以配置多个,在配置多个时,一般需要操作路径,防止都进行拦截的处理(这个时候不写可能报错,具体可以测试),而不是指定的拦截(因为默认是/**),所以存在mvc:interceptors中存在mvc:interceptor,所以如果你只有一个并且需要是/ * *,那么可以这样写:
< mvc: interceptors> < bean class = " com.Interceptor.MyInterceptor" > </ bean> </ mvc: interceptors>
一般我们建议使用这样的方式来进行编写:
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor" > </ bean> </ mvc: interceptor> </ mvc: interceptors>
当然其实还有功能,这是mvc设计出来的功能,他也可以进行去掉,比如他可以选择性的不拦截一个路径的请求:
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < mvc: exclude-mapping path = " /demo/**" /> < bean class = " com.Interceptor.MyInterceptor" > </ bean> </ mvc: interceptor> </ mvc: interceptors>
这样就能不拦截demo开头的处理了,一般情况下,servlet或者mvc的普通拦截都是在项目路径下的直接拦截,比如前面的测试的路径test/in,就是放在项目路径后面,而不是端口路径
编写好后,我们执行前面的代码,看看后端的结果:
打印如下:
这个时候,你可以选择将返回值变成false,那么我们看看这个打印结果:
我们可以发现,他甚至连对应的方法都不执行了,而方法都不执行,默认情况下,是没有响应体信息的,那么在前端显示的就是空白(这个时候,甚至都不会操作视图,也就是单纯的返回空响应),也就是说,这个true也决定了对应的controller的方法的执行,这也是为什么默认情况下,对应的HandlerInterceptor方法的返回值是true的一个原因
现在我们添加一个前端jsp,test.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<%System.out.println(1);
%>
1
</script>
</body>
</html>
上面的这个地方加上代码:
<%System.out.println(1);
%>
这个是jsp的语法(在51章博客有说明),在转译和编译的情况下,会进行处理的,最终操作拦截,那么我们在springmvc.xml中加上如下的配置:
< bean id = " viewResolver" class = " org.springframework.web.servlet.view.InternalResourceViewResolver" > < property name = " prefix" value = " /" > </ property> < property name = " suffix" value = " .jsp" > </ property> </ bean>
然后再test类中加上这个代码:
@RequestMapping ( "ix" ) public String ix ( ) { return "test" ; }
首先还是false的返回值,然后直接的在url中后面加上test/ix访问即可,这个时候我们查看后端的打印:
因为false不会经过方法,所以在前端是显示空白的
经过这两次的测试,可以发现handler的打印信息包含了,访问权限,返回值,对有包路径的方法及其参数列表等等,我们给ix方法加上两个参数列表,即:
public String ix ( String a, Integer b) {
对应的打印信息如下:
即也的确如此,现在我们给false,变成true的返回值,然后看看打印结果:
也验证了前面注释中的:如果你在jsp中操作了输出语句,那么这个值输出后,这个方法才会进行处理
但是还有一个问题,如果controller对应的方法没有返回值呢,因为没有返回值说明他不会经过视图,而不经过视图,那么第三个拦截是否不会进行了,所以我们修改ix方法:
@RequestMapping ( "ix" ) public void ix ( String a, Integer b) { }
因为当我们没有给出视图名时,会将请求(参数)进行拼接,一般是@RequestMapping的整个拼接
我们还是true,因为false修改与不修改是一样的,反正都不执行,那么他的修改没有意义,我们看看执行后的打印结果:
但是这里还存在一些细节,这是前面并没有说明的,我们看如下:
我们继续修改:
@RequestMapping ( "ix" ) public String ix ( String a, Integer b) throws IOException { return null ; }
执行之后,结果与上面的一样,也就证明了,其实对应的视图默认是null(是默认,虽然会根据组件得到路径的视图),所以当你返回类型为void时,他的视图结果与返回null值是一样的,然而,虽然根据路径得到了视图,但是其实在没有视图时也会得到结果,为什么,我们看这个代码:
@RequestMapping ( "ix" ) public void ix ( String a, Integer b, HttpServletResponse mm) throws IOException { mm. setContentType ( "text/html;charset=utf-8" ) ; PrintWriter writer = mm. getWriter ( ) ; writer. println ( "哈哈哈" ) ; }
后端打印:
可以发现没有视图了,没有视图相当于直接的返回的操作(直接操作响应体信息了),但是为什么加上这些就会没有呢,这是因为如果你要自己加上响应信息的话,那么必然会与原来的如jsp而发生冲突(一般来说io并不能覆盖,只是增加),导致对应的jsp信息出现问题,所以他们是需要分开的,所以只要你存在要自行操作响应体信息的,那么视图就会为null,通过测试,只要你在参数列表中加上HttpServletResponse mm,比如:
@RequestMapping ( "ix" ) public void ix ( String a, Integer b, HttpServletResponse mm) throws IOException {
}
那么视图就为null,即执行的操作响应体信息,如果你没有设置,自然前端显示空白
而打印信息中,第二次和第三次无论是否操作了HttpServletResponse或者无论是否报错,都会出现,也就是说,后面两个并没有互相联系的不能让对方放行的处理,所以他们基本必然都会打印了,但是报错的话,视图是找不到了,那么就不会出现对应的转译编译才对,为什么第三次拦截也会出现呢,在前面我们说过了"10到11则是第三次拦截(这里也可能是9到10)",虽然在第8次报错,但是第9次的操作需要返回报错信息,这个时候是会经过第三次拦截的(简单来说,有返回,就算是空的,他通常也会操作)
至此,我们的细节基本说明完毕
拦截器的执行流程:
在运行程序时,拦截器的执行是有一定顺序的,该顺序与配置⽂件中所定义的拦截器的顺序相关,单个 拦截器,在程序中的执行流程如下图所示:
1:程序先执⾏preHandle()方法,如果该方法的返回值为true,则程序会继续向下执行处理器中的方 法,否则将不再向下执行
2:在业务处理器(即控制器Controller类)处理完请求后,会执⾏postHandle()方法,然后会通过DispatcherServlet向客户端返回响应
3:在DispatcherServlet处理完请求后,才会执⾏afterCompletion()方法
再结合这个图吧:
根据前面的说明,他的流程也可以说是对的
总结一下:
上面是单个拦截器的流程,那么多个拦截器的执行流程呢:
多个拦截器(假设有两个拦截器Interceptor1和Interceptor2,并且在配置⽂件中, Interceptor1拦截 器配置在前),在程序中的执行流程如下图所示:
从图可以看出,当有多个拦截器同时工作时,它们的preHandle()方法会按照配置⽂件中拦截器的配置 顺序执行,⽽它们的postHandle()方法和afterCompletion()方法则会按照配置顺序的反序执行
我们来看例子:
首先创建两个类在Interceptor包中:
package com. Interceptor ; import org. springframework. web. servlet. HandlerInterceptor ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ; public class MyInterceptor1 implements HandlerInterceptor { @Override public boolean preHandle ( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System . out. println ( "preHandle1...." ) ; return true ; } @Override public void postHandle ( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System . out. println ( "postHandle1...." ) ; } @Override public void afterCompletion ( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System . out. println ( "afterCompletion1...." ) ; }
}
package com. Interceptor ; import org. springframework. web. servlet. HandlerInterceptor ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ; public class MyInterceptor2 implements HandlerInterceptor { @Override public boolean preHandle ( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System . out. println ( "preHandle2...." ) ; return true ; } @Override public void postHandle ( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System . out. println ( "postHandle2...." ) ; } @Override public void afterCompletion ( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System . out. println ( "afterCompletion2...." ) ; }
}
我们进行配置(去掉原来的):
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor1" > </ bean> </ mvc: interceptor> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor2" > </ bean> </ mvc: interceptor> </ mvc: interceptors> < bean id = " viewResolver" class = " org.springframework.web.servlet.view.InternalResourceViewResolver" > < property name = " prefix" value = " /" > </ property> < property name = " suffix" value = " .jsp" > </ property> </ bean>
后端(之前处理的):
@RequestMapping ( "ix" ) public String ix ( String a, Integer b) throws IOException { return "test" ; }
现在我们执行(操作上面的test.jsp)看看打印信息:
也的确是第一个是顺序(后配置的在后面,你修改配置中拦截的顺序即可),后面两个是反序,现在我们来测试,让其中一个
配置放在前面:
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor2" > </ bean> </ mvc: interceptor> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor1" > </ bean> </ mvc: interceptor> </ mvc: interceptors>
打印结果:
也的确如此,那么如果其中一个不放行呢,我们将preHandle2…的对应方法的返回值设置为false看看,上面的配置不变,执行后,看看打印结果:
只有这一个,那么我们将配置顺序改变回来,执行看看结果:
感觉到了,如果你的返回值是false,那么你后面的拦截都不能进行处理,包括后面的第一次拦截,但是,如果存在第一次拦截执行完毕,那么允许他执行第三次拦截,虽然是这样说,但是也只是一个结论,我们选择继续添加一个类来进行处理:
package com. Interceptor ; import org. springframework. web. servlet. HandlerInterceptor ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ; public class MyInterceptor3 implements HandlerInterceptor { @Override public boolean preHandle ( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System . out. println ( "preHandle3...." ) ; return false ; } @Override public void postHandle ( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System . out. println ( "postHandle3...." ) ; } @Override public void afterCompletion ( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System . out. println ( "afterCompletion3...." ) ; }
}
将preHandle2…的对应方法的返回值设置为true(修改回来),然后配置如下:
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor1" > </ bean> </ mvc: interceptor> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor3" > </ bean> </ mvc: interceptor> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor2" > </ bean> </ mvc: interceptor> </ mvc: interceptors>
执行看看结果:
可以发现,结论正确,但是为什么会这样,按道理说,postHandle1…应该也会执行啊,为什么没有呢,实际上如果没有false,那么结果应该是:
其中中间的处理基本都是方法的处理,方法没有进行处理,自然没有视图,没有视图,前端自然返回空白信息(响应体没有信息),那么这个false会导致后面的不进行处理,但是afterCompletion1…对应的方法的拦截是在响应哪里,而这个由于preHandle1…是返回true的,他的方法是你造成的不处理,而不是我自己,所以说afterCompletion1…会进行打印,那么这个拦截的关系就有意思了,其中preHandle1…(以后就这样说明了,一般这样说是说他对应的方法,注意即可)的返回值的确影响后面的放行,但是对后面两个方法的放行的影响是不同的,postHandle1…是根据true造成的放行来决定执行的,也就是说,只要你放行了(true影响放行的参数),那么我就会执行,但是如果你没有放行,那么我自然也执行不了,而afterCompletion1…只看你的返回值,而不看你是否放行,所以在单独的拦截器的时候,true和false的结果都是他们两个执行或者不执行,但是存在其他的拦截时,那么放行这个处理和判断true的处理的区别就出现了,这也是为什么afterCompletion1…会执行,但是postHandle1…不会执行
为了验证这样的结果,我们修改配置文件:
< mvc: interceptors> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor1" > </ bean> </ mvc: interceptor> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor2" > </ bean> </ mvc: interceptor> < mvc: interceptor> < mvc: mapping path = " /**" /> < bean class = " com.Interceptor.MyInterceptor3" > </ bean> </ mvc: interceptor> </ mvc: interceptors>
执行看看结果:
可以发现,之前的结论是正确的(后面先打印afterCompletion2…,是反序的原因)
那么还有个问题,为什么mvc的拦截器中,第一次拦截是顺序的,其他两个是反序的或者说倒序的:
解释如下:
这个默认规则的原因是为了在执行拦截器时提供更多的灵活性和可能性,考虑以下情况:
第一个拦截器通常用于做一些准备工作,如日志记录、身份验证等,按顺序执行有助于确保这些准备工作在控制器方法之前完成
控制器方法执行后,倒序执行其他拦截器可以用于清理工作、日志记录和一些其他操作,这确保了在请求处理完毕后执行这些操作,因为这些工作一般先创建的后清理
但是实际上顺序的问题,大多数我们并不需要注意,并且,也存在可以修改顺序的处理,只是可能需要某些版本(好像现在基本都不行了)
处理multipart形式的数据:
前面虽然我们使用mvc处理过问题,但是还是有些操作我们没有说明,比如方便我们操作的api,现在来补充一下:
首先加上依赖:
< dependency> < groupId> commons-fileupload</ groupId> < artifactId> commons-fileupload</ artifactId> < version> 1.3.1</ version> </ dependency>
配置上传文件解析器:
< bean id = " multipartResolver" class = " org.springframework.web.multipart.commons.CommonsMultipartResolver" > < property name = " maxUploadSize" value = " 1000000000" /> </ bean>
对应的前端(index.jsp):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<form method="post" enctype="multipart/form-data" action="demo/upload"><input type="file" name="uploadFile"/><input type="submit" value="上传"/>
</form>
</body>
</html>
对应的后端(我们创建FileController类):
package com. controller ; import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. multipart. MultipartFile ; import javax. servlet. http. HttpServletRequest ;
import java. io. File ;
import java. io. IOException ;
import java. text. SimpleDateFormat ;
import java. util. Date ;
import java. util. UUID ; @Controller
@RequestMapping ( "/demo" )
public class FileController { @RequestMapping ( "upload" ) public String upload ( MultipartFile uploadFile, HttpServletRequest request) throws IOException { String originalFilename = uploadFile. getOriginalFilename ( ) ; String extendName = originalFilename. substring ( originalFilename. lastIndexOf ( "." ) + 1 , originalFilename. length ( ) ) ; String uuid = UUID . randomUUID ( ) . toString ( ) ; String newName = uuid + "." + extendName; String realPath = request. getSession ( ) . getServletContext ( ) . getRealPath ( "/" ) ; System . out. println ( realPath) ; String datePath = new SimpleDateFormat ( "yyyy-MM-dd" ) . format ( new Date ( ) ) ; File floder = new File ( realPath + "/" + datePath) ; if ( ! floder. exists ( ) ) { floder. mkdirs ( ) ; } uploadFile. transferTo ( new File ( floder, newName) ) ; return "success" ; }
}
然后如果你按照流程来的话,对应的视图解析器应该是配置的,所以我们在index.jsp同级别创建一个success.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
1
</body>
</html>
执行后,我们上传一个文件或者说给一个文件,然后看看当前项目是否出现对应的文件或者目录,然而并没有,为什么
这里就需要考虑maven的操作与传统的web的区别了,首先,我们可以选择实现一个传统的web处理(这里可以选择看看第50章博客):
为此这里我也不得不给出三种创建web的方式,空项目,传统web,maven项目
首先我们从空项目来进行处理,由于空项目并没有使用到maven或者并没有进行指定使用maven,所以一般空项目只能到传统的web(即空项目到web一般与传统的web项目是一样的),然而这是一般的说法,基本上任何形式都可以从空项目变化而来,因为他就是在idea中,所以这里应该有五种方式:空项目(操作java程序),空项目到web,空项目到maven,(传统)web,maven等等
创建ser1空项目:
然后进入如下:
空项目是什么都没有,有时也会包括模块,一般情况下,一个项目是对应一个模块的,如果没有模块,我们可以创建模块:
这里了解即可,创建模块一般是如下:
创建后,选择关闭idea,打开我们创建好模块的这个目录即可,然后可以这样:
从上面我们可以看到,这个目录后面有个sources root,这个再后面说明,他代表这个目录下面是专门写java的,所以这个时候创建的目录是包的意思(否则不是)
直接执行看看结果即可(一般sources root的出现除了配置外,执行java代码也会出现,在项目中,他虽然也是文件夹,但是他也由于是项目,所以可以直接创建java文件(虽然不能再其里面的文件夹里面创建java文件),右键可以看到与其里面的目录有是不同的选项的),但是一般情况下,我们需要src这个目录,但是要明白src这个目录其实也是认为创造的,在maven或者web中,使用而已,而单纯的java并不一定使用他,但是为了统一所以我们可以选择创建src这个包括来操作也就是:
至此可以说空项目(操作java程序)操作完毕,现在我们来完成空项目到web:
空项目到web,需要这样的处理,一般情况下,是需要很多的东西,当然我们也可以选择创建一个web项目来观察一下,然后再从空项目到web,归根揭底,web和空项目的区别就是对应目录赋予一些操作属性,让他作为资源文件,以及相关web中的对应文件也赋予属性(由于是赋予的,所以对应的文件名称并非需要固定,比如web对应的资源文件可以变成webb,只是有些插件或者说idea的某些自动处理(比如maven的依赖自动判断文件名称来自动配置)会处理这些文件名称,所以我们大多数都会配置对应的固定文件,比如web中,配置war包,那么对应的文件若是web或者webapp通常会自动配置,这里也要注意,随着时间的推移这些名称可能会变,所以注意即可,一般好像只有webapp了,web已经没有了,如果在以前博客中有说明,那么大概是以前的某个版本或者是以前的操作(以前的版本也是可能会发生改变的,因为官方也是维护的,除非是固定的版本)),这些操作再老版本的idea中可能需要手动的处理,但是新版的一般并不存在这样的处理了,或者忽略一些处理,所以这里我们来操作一些赋予这个操作:
其他的删除,回到这里:
然后这样:
这样src就可以操作java代码,而再xianmu的直接目录里面就不行了,很明显,这个选项是赋予这个目录存放java或者被编译器编译的地方,更加可以说,编译器只会去这个属性中操作java代码,自然导致操作发生改变(如这个时候右键xianmu这个目录时,出现的选项发生变化了),这些操作之所以会这样,其实是idea软件自身处理的,或者idea也处理了jdk的某些配置,以及或者说idea他手动的帮我们选择编译以及执行,然后将结果放在控制台中(这个的显示自然也可以认为是一个文件中,或者直接的显示而不是文件中,这些操作都可以被我们处理,因为二进制就是可以这样,二进制出现显示还是复杂的,就算在操作系统中,显示的处理一般也并没有具体说明,因为是需要通过硬件完成对规定排列的映射的,这里了解即可)
好了,既然src的情况我们说明完毕,但是空项目操作(到)web还不知道如何操作,现在我们来操作一下:
然后操作如下:
这样对应的前面就会出现如下:
这样你就可以操作启动服务器了(具体自行配置,可以看50章博客),如果启动后,出现对应的数据,那么操作成功,这就是空项目到web的处理,那么传统web是如何处理(实际上也就是一次性到这里):
其中idea或者说web项目,通常会有隐藏的设置,即自动读取在WEB-INF/lib/下面的jar包,这里了解即可,当然,其实我们也可以手动的指定,但是一般情况下,新版的可能没有这样的设置的,所以建议指定
一般情况下,传统项目的处理基本只会在老版本的idea中,新版的没有,具体在50章博客中可以看到,所以这里我们就不多说了
现在我们先操作一下这个(前面的"这里就需要考虑maven的操作与传统的web的区别了"):
先加上这个,具体的下载地址是如下:
链接:https://pan.baidu.com/s/1Yyd662YY99X7wEGIZ4FN7w
提取码:alsk
然后配置如下:
点击这个,在目录中选择lib文件或者直接选择jar包都可,选择文件说明是操作里面的所有jar包的
然后总体操作是如下:
在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> ConfigServlet</ servlet-name> < servlet-class> servlet</ servlet-class> </ servlet> < servlet-mapping> < servlet-name> ConfigServlet</ servlet-name> < url-pattern> /config</ url-pattern> </ servlet-mapping>
</ web-app>
补充对应的servlet的部分内容:
@Override public void service ( ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException , IOException { String realPath = servletRequest. getServletContext ( ) . getRealPath ( "/" ) ; System . out. println ( realPath) ; }
启动,访问对应的config(记得加上),看看打印结果(这里就是我们需要查看的区别)
F : \xianmu\out\artifacts\xianmu_Web_exploded\
即他操作的是编译后最终的结果,其实也可以看出来,他的操作在很大程度上是对应的这个结果:
我们可以在启动时(查看下方启动日志即可),查看临时的tomcat(简称为临时处理,或者说tomcat副本),比如我的就是:
C : \Users \33988 \AppData \Local \JetBrains \IntelliJIdea2021 .3 \tomcat\062 b022d- 1657 - 4d 8 b- b4e9- da3eeae3d227\conf\Catalina \localhost
里面的配置文件就是这个:F:\xianmu\out\artifacts\xianmu_Web_exploded",即他还是操作临时的tomcat
那么现在就剩下两个了,即空项目到maven,以及直接的maven,空项目到maven其实也并不是很难:
我们创建如下的空项目:
然后我们操作如此:
配置这些:
那么怎么变成maven项目呢,我们可以思考,要变成对应的这个项目,必然是需要使用到maven,这就需要我们配置使用了,具体如何配置看如下:
先创建pom.xml(与src同级别):
<?xml version="1.0" encoding="UTF-8"?>
< project xmlns = " http://maven.apache.org/POM/4.0.0" xmlns: xsi= " http://www.w3.org/2001/XMLSchema-instance" xsi: schemaLocation= " http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > < modelVersion> 4.0.0</ modelVersion> < groupId> org.example</ groupId> < artifactId> testma</ artifactId> < version> 1.0-SNAPSHOT</ version> < properties> < maven.compiler.source> 11</ maven.compiler.source> < maven.compiler.target> 11</ maven.compiler.target> </ properties> </ project>
但是他并没有进行处理,我们需要如下的配置(maven本身也可以看成一个框架,而idea需要被他支持,就如空项目到web时需要指定jar包一样,当然,也可以添加框架(实际上也就是对应的jar包),但是由于maven的jar比较多,所以这里我们就选择添加框架了):
一般来说,这个文件添加好后,右下角就会出现这个,当然,一般都会出现了,否则可能你需要重新的删除在创建(因为基本没有其他办法来构建maven,因为功能也并非都会提供了)
之后的选择,基本选择第一个即可,当然,还是需要看具体情况,最好翻译一下
然后你可以选择在其内容加上:
< packaging> war</ packaging>
一般这个时候,由于刷新了,那么他基本就出现了对应的显示(M)
某种程度上,点击右下角后,把这里出现的这个勾勾去掉也行:
而maven的创建操作,我们就不处理了,因为我们操作过很多次了
至此,对应的五个方式(空项目(操作java程序),空项目到web,空项目到maven,(传统)web,maven)我们操作完毕,其中
maven操作web我们就不处理了,因为我们可以只设置打包方式即可,比较简单
但是为了看看前面的问题,所以我们还是需要进行处理的,首先创建webapp文件:
如下:
如果前面再pom.xml中没有写上maven,而是testma,那么项目名称后面一般会有[testma]
依赖如下:
< dependencies> < dependency> < groupId> javax.servlet</ groupId> < artifactId> javax.servlet-api</ artifactId> < version> 3.1.0</ version> < scope> provided</ scope> </ dependency> </ dependencies>
即对应的配置与前面的一样,我们看看maven与传统web的区别是什么:
我们经过测试:
当操作方式为如下时会出现不同的结果(配置tomcat时,会出现的):
如图:
至于他们里面的配置,你可以选择的再引入或者添加时,点击来自谁即可,也可以手动处理(一般exploded的基本相同,可能部分不同,但是好像也并不影响,注意即可,在选项中点击+号一般就会知道了)
所以maven:war exploded和xianmu:Web exploded由于对应的类型的对象基本是一样的是临时的,我们可以测试看看:
首先进入maven:war时,对应的临时处理,看看其配置文件的结果:
< Context path = " /maven" docBase = " F:\maven\target\maven-1.0-SNAPSHOT.war" />
F : \Program Files \apache- tomcat- 8.5 .50 \webapps\maven\
进入maven:war exploded,看看其结果:
< Context path = " /maven" docBase = " F:\maven\target\maven-1.0-SNAPSHOT" />
F:\maven\target\maven-1.0-SNAPSHOT\
进入之前测试的xianmu:Web exploded,看看其结果:
<Context path="/xianmu" docBase="F:\xianmu\out\artifacts\xianmu_Web_exploded" />
F:\xianmu\out\artifacts\xianmu_Web_exploded\
很明显,带有exploded的结果与配置文件一致,而没有的,则是操作本来的tomcat,并且放在里面,为什么,这就需要一些隐藏的配置处理了,在明显的指定war时,那么他的操作就会自动在本来的tomcat中处理,而不是临时的处理目录,这也可以说是默认的处理,具体情况还是看tomcat的源码了,可能是因为需要这样的情况才会弄出来吧
如果没有值呢,是空值呢,如对应的代码:
String realPath = servletRequest. getServletContext ( ) . getRealPath ( "/" ) ;
上面中的"/"不加,而是变成:
String realPath = servletRequest. getServletContext ( ) . getRealPath ( "" ) ;
结果如何:
经过测试,默认的结果还是加上"/“的结果一样,所以默认加上”/“,并且经过测试,所以如果是getRealPath(“a”);或者getRealPath(”/a");,他们的结果都是"/a",这种情况得到的结果就是在路径后面加上a(所以这个a我们最好写成一下比较好的目录,比如文件文件相关的目录,比如uploads),所以这里参数的意思也就是加上参数得到的整体路径的意思了,所以大多数为了得到完整的路径,一般都会显示的操作"/"即可
当然,为了在后面解决很多的疑问或者说以前的疑问,我决定统一的将web的mvc相关对于路径的说法以及传统web关于路径的说法进行处理:
一般在web中关于路径的说明存在多种,一般主要是四种,比如:转发,重定向,xml的路径,注解的路径等等
然而这些的说明在以前也说明了,这里就不多说,比如可以到67章博客看看(若有遗漏,也可以选择到50章博客补充,如果还有,那么可以自己进行测试,一般加上50章博客的话是没有遗漏的)
所以关于getRealPath路径的区别我们说明完毕,回到之前的操作:
package com. controller ; import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. multipart. MultipartFile ; import javax. servlet. http. HttpServletRequest ;
import java. io. File ;
import java. io. IOException ;
import java. text. SimpleDateFormat ;
import java. util. Date ;
import java. util. UUID ; @Controller
@RequestMapping ( "/demo" )
public class FileController { @RequestMapping ( "upload" ) public String upload ( MultipartFile uploadFile, HttpServletRequest request) throws IOException { String originalFilename = uploadFile. getOriginalFilename ( ) ; String extendName = originalFilename. substring ( originalFilename. lastIndexOf ( "." ) + 1 , originalFilename. length ( ) ) ; String uuid = UUID . randomUUID ( ) . toString ( ) ; String newName = uuid + "." + extendName; String realPath = request. getSession ( ) . getServletContext ( ) . getRealPath ( "/" ) ; System . out. println ( realPath) ; String datePath = new SimpleDateFormat ( "yyyy-MM-dd" ) . format ( new Date ( ) ) ; File floder = new File ( realPath + "/" + datePath) ; if ( ! floder. exists ( ) ) { floder. mkdirs ( ) ; } uploadFile. transferTo ( new File ( floder, newName) ) ; return "success" ; }
}
执行后,我们上传一个文件或者说给一个文件,然后看看当前项目是否出现对应的文件或者目录,然而并没有,为什么,就是因为由于其操作的是不带有exploded的,所以到原本的tomcat中里面生成了,然而一般在idea中可能并不会显示,因为他可能是只会显示在起始的目录(如target里面,也可能还要里面)或者其他的隐藏(F:\Program Files\apache-tomcat-8.5.50\webapps\maven\),这里了解即可
但是上面的操作中,由于没有到当前项目的路径,那么他是不是有问题的,实际上一般情况下,这只是不同系统中的tomcat的处理而已,而当我们发版(也就是部署到服务器提供给用户使用时)时,一般在linux中,而这个系统下,一般都是当前项目所在,即指向的是当前项目里面(类似于前面的F:\Program Files\apache-tomcat-8.5.50\webapps\maven\),所以我们这个代码是没有问题的,只是环境不同而已,这个时候如果是没有exploded的,就会到当前项目中(实际上发版的也就是这个),所以一般的,我们并不考虑在开发中的路径处理,因为最后一定是没有exploded的,即路径在生产中是基本对的(开发代表在idea中操作(写)代码,生产代表已经发版),当然idea中也可以考虑,只是需要一些设置而已(如路径的处理,以及tomcat选择的处理),这些可以百度查看,这里就不说明了
至此,对处理multipart形式的数据的一些补充,我们补充完毕
实际上对于UUID来说,也是可能出现重复,因为他存在如下的情况:
在控制器中处理异常:
在前面我们知道有HandlerExceptionResolver这样的组件,是来处理异常的,我们自然也会围绕这个来处理(一般来说,对应注解的识别就是由他来处理的)
在controller包下创建GlobalExceptionResolver类:
package com. controller ; import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. ControllerAdvice ;
import org. springframework. web. bind. annotation. ExceptionHandler ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletResponse ;
import java. io. IOException ;
@Controller
public class GlobalExceptionResolver { @ExceptionHandler ( ArithmeticException . class ) public ModelAndView handleException ( ArithmeticException exception, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView ( ) ; modelAndView. addObject ( "msg" , exception. getMessage ( ) ) ; modelAndView. setViewName ( "error" ) ; return modelAndView; } @RequestMapping ( "err" ) public String ix ( String a, Integer b) throws IOException { System . out. println ( 1 ) ; int i = 1 / 0 ; return "err" ; }
}
对应的jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
错误是:${msg}
</body>
</html>
一般情况下,我们访问err(具体访问自己应该知道了),一般来说异常不会经过第二个拦截,这是因为出现异常自然不会考虑数据或者视图的问题,所以一般就会规定不会经过第二次拦截,只会依靠固有的数据或者视图进行处理异常,即:
modelAndView. addObject ( "msg" , exception. getMessage ( ) ) ;
modelAndView. setViewName ( "error" ) ;
所以注释这个:
自然第二个拦截没有,且第三个拦截打印出对应的信息(我们写上的),所以他的作用只是在出现异常时,进一步的处理视图操作,否则按照默认的异常视图进行处理,你可以在浏览器看看注释后的界面就知道了
一般情况下,写在当前controller中的对应的异常处理,只会对自身进行生效,并且,如果存在多个,比如:
@ExceptionHandler ( ArithmeticException . class ) public ModelAndView handleException ( ArithmeticException exception, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView ( ) ; modelAndView. addObject ( "msg" , exception. getMessage ( ) ) ; modelAndView. setViewName ( "error" ) ; return modelAndView; } @ExceptionHandler ( ArithmeticException . class ) public ModelAndView handleException1 ( ArithmeticException exception, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView ( ) ; modelAndView. addObject ( "msg" , exception. getMessage ( ) + "22" ) ; modelAndView. setViewName ( "error" ) ; return modelAndView; }
那么会报错,由上所述,我们应该需要统一的异常处理,所以我们需要一个全局的处理,那么我们再次的创建一个类:
package com. controller ; import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. ExceptionHandler ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletResponse ; @Controller
public class Global { @ExceptionHandler ( ArithmeticException . class ) public ModelAndView handleException ( ArithmeticException exception, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView ( ) ; modelAndView. addObject ( "msg" , exception. getMessage ( ) ) ; modelAndView. setViewName ( "error" ) ; return modelAndView; }
}
然后把GlobalExceptionResolver类中的异常处理都进行删除,然后启动看看,是否在这个类里面处理了异常,发现并没有,说明的确对应的异常只能操作自身的controller的,那么怎么将这个异常处理变成全局呢,我们操作如下:
package com. controller ; import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. ControllerAdvice ;
import org. springframework. web. bind. annotation. ExceptionHandler ;
import org. springframework. web. servlet. ModelAndView ; import javax. servlet. http. HttpServletResponse ; @ControllerAdvice
@Controller
public class Global { @ExceptionHandler ( ArithmeticException . class ) public ModelAndView handleException ( ArithmeticException exception, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView ( ) ; modelAndView. addObject ( "msg" , exception. getMessage ( ) ) ; modelAndView. setViewName ( "error" ) ; return modelAndView; }
}
现在我们继续执行,会发现他处理异常了,也就是说,@ControllerAdvice可以使得这个类里面或者说这个controller里面的异常的那个处理变成全局的处理,当然,并不必须需要他是controller(写上也没有关系),首先controller的主要作用是定位(如果不作为全局异常的话,两个都可以单独写上进行处理),而@ControllerAdvice也是算一个定位,只不过他是异常的定位而已,当然,你写上controller也没有关系,可以在不处理异常时,作为controller使用也行,而在处理异常时,@ControllerAdvice可以作为controller使用的(虽然他并不是),所以这个时候可以不加对应的controller,照样的可以进行操作(因为@ExceptionHandler只有一个,也只能有一个,否则报错)
那么如果存在多个全局呢,谁先使用,答:看下图
经过大量的测试,发现,由于windows中,A和a是一样的,所以可以得到:当字母的数量或者说文件名长度相等时,按照Ascii来决定,越小那么就越优先(上面没有测试数字,实际上数字也是的,由于1<a,那么Gloaa1优先于Gloaaa),否则的话,长度越长越优先
当然,上面的说明并不重要,因为全局的一个就够了
基于Flash属性的跨重定向请求数据传递:
在前面我们知道一个组件FlashMapManager,一般就用于这里
重定向时请求参数会丢失,我们往往需要重新携带请求参数,我们可以进行⼿动参数拼接如下:
我们创建一个类:
package com. controller ; import org. springframework. stereotype. Controller ;
import org. springframework. web. bind. annotation. RequestMapping ; @Controller
public class Redirect { @RequestMapping ( "/re" ) public String re ( ) { return "redirect:ree?name=" + 1 ;
} @RequestMapping ( "/ree" ) public String ree ( String name) { System . out. println ( name) ; System . out. println ( 1 ) ; return "ree" ; }
}
先访问re,看看结果吧
上述拼接参数的方法属于get请求,携带参数⻓度有限制,参数安全性也不⾼(因为在url上),此时,我们可以使⽤SpringMVC提供的flash属性机制,向上下⽂中添加flash属性,框架会在session中记录该属性值,当 跳转到⻚⾯之后框架会⾃动删除flash属性,不需要我们⼿动删除,通过这种方式进行重定向参数传递, 参数⻓度和安全性都得到了保障,如下:
继续在上面的类中加上方法:
@RequestMapping ( "/reee" ) public String reee ( RedirectAttributes redirectAttributes, HttpSession session) { redirectAttributes. addFlashAttribute ( "name" , "22" ) ; Object name = session. getAttribute ( "name" ) ; System . out. println ( name) ; return "redirect:reeee?name=" + 1 ; } @RequestMapping ( "/reeee" )
public String reeee ( String name, HttpSession session, @ModelAttribute ( "name" ) String na, ModelAndView modelAndView) { System . out. println ( modelAndView) ; System . out. println ( na) ; String name2 = ( String ) session. getAttribute ( "name" ) ; System . out. println ( name2) ; System . out. println ( 44444444 ) ; System . out. println ( name) ; Object name1 = session. getAttribute ( "name" ) ; System . out. println ( name1) ; return "ree" ; }
执行后,测试一下吧,但是为什么flash(英文是显示的意思)类型属性只会在两次请求之间有效,解释如下:
至此解释完毕,现在我们来⼿写 MVC 框架,上面虽然说明了很多知识,但是基本只有手写出来,我们才能知道更加深层次的东西,现在开始手写MVC框架:
回顾SpringMVC执行的⼤致原理,后续根据这个模仿⼿写自己的mvc框架
spring的维护是必须的,因为他最终保存的对象(数据,内容)是用来判断映射的,难道是凭空保存或者直接保存吗,总需要一个总地方吧(spring的地方)
具体的说明就是:前端控制器进行初始化的配置,而扫描交给spring来处理(springmvc有spring的),然后根据前端控制器初始化的信息(在一个servlet中,是可以操作同一个请求头和响应头的,或者说请求信息和响应信息),来决定调用谁,也就是映射,后面进行一系列的处理(如jsp的响应),最终得到我们的结果,具体说明看后面就知道了,我们手写一个类似的即可
手写MVC框架之注解开发(也可以存在对应的xml开发,只是对与mvc来说,通常是需要注解的,单纯的xml并不好处理,具体可以百度,一般mybatis,spring,springmvc基本都是需要xml和注解一起的,反正那个方便使用那个):
通常来说,注解的性能会比xml慢点(通常指启动的时候,有时候运行时也会,但是方便许多,特别的是考虑到注解的扫描(当包非常多时,有些不需要的可能也会扫描到))
在手写之前,我们创建一个项目:
web.xml中是:
< web-app> </ web-app>
现在开始编写,首先,我们创建com.mvc.framework包,然后再该包下创建DispatcherServlet类:
上面是需要服务器的包的,自然需要引入,也就是:
< dependencies> < dependency> < groupId> javax.servlet</ groupId> < artifactId> javax.servlet-api</ artifactId> < version> 3.1.0</ version> < scope> provided</ scope> </ dependency> </ dependencies>
然后我们创建这个类(包名自行创建,可以看下面的这个:package com.mvc.framework;来创建):
package com. mvc. framework ; import javax. servlet. http. HttpServlet ;
public class DispatcherServlet extends HttpServlet {
}
然后修改web.xml(补充服务器的读取方式,这是固定的,除非你修改服务器,也就是tomcat的处理(servlet),当然,由于框架是建立在这个上面的,所以自然是保留的):
<?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" > < display-name> create</ display-name> < servlet> < servlet-name> mvc</ servlet-name> < servlet-class> com.mvc.framework.DispatcherServlet</ servlet-class> </ servlet> < servlet-mapping> < servlet-name> mvc</ servlet-name> < url-pattern> /*</ url-pattern> </ servlet-mapping>
</ web-app>
这个超级熟悉了吧,但是我们需要明白,这里是操作原生的servlet的(我们mvc是建立在他之上的,具体到50章博客学习),我们在操作映射之前,首先需要得到对应的对象来操作方法,从而操作映射,所以我们需要定义一些注解,或者说,完成Spring相关的操作
现在,我们在framework包下创建servlet包,将DispatcherServlet移动到这个包里面,然后再在framework包下创建annotations包,然后在该包下创建几个注解:
package com. mvc. framework. annotations ; import java. lang. annotation. * ; @Documented
@Target ( ElementType . TYPE )
@Retention ( RetentionPolicy . RUNTIME )
public @interface Controller { String value ( ) default "" ;
}
package com. mvc. framework. annotations ; import java. lang. annotation. * ; @Documented
@Target ( ElementType . TYPE )
@Retention ( RetentionPolicy . RUNTIME )
public @interface Service { String value ( ) default "" ;
}
package com. mvc. framework. annotations ; import java. lang. annotation. * ; @Documented
@Target ( { ElementType . TYPE , ElementType . METHOD } )
@Retention ( RetentionPolicy . RUNTIME )
public @interface RequestMapping { String value ( ) default "" ;
}
package com. mvc. framework. annotations ; import java. lang. annotation. * ; @Documented
@Target ( ElementType . FIELD )
@Retention ( RetentionPolicy . RUNTIME )
public @interface Autowired { String value ( ) default "" ;
}
很明显,我们需要操作Controller到Service中进行处理的,注解我们定义好了,现在我们开始进行开发
为了操作到Service的注解的处理,我们需要在framework包下,创建service包,再创建如下的两个类或者接口:
package com. mvc. framework. service ; public interface DemoService { String get ( String name) ;
}
package com. mvc. framework. service. impl ; import com. mvc. framework. service. DemoService ; public class DemoServiceImpl implements DemoService { @Override public String get ( String name) { System . out. println ( "打印:" + name) ; return name; }
}
回到DispatcherServlet,在这里自然是需要进行包的扫描的,并且这个扫描需要知道往那里进行扫描(这里考虑在mvc相关xml中进行处理),然后Spring管理扫描后的结果,最后进行映射的处理,现在首先是处理扫描:
补充DispatcherServlet:
package com. mvc. framework. servlet ; import javax. servlet. ServletException ;
import javax. servlet. http. HttpServlet ;
import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import java. io. IOException ;
public class DispatcherServlet extends HttpServlet { @Override public void init ( ServletConfig config) throws ServletException { } @Override protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException { doPost ( req, resp) ; } @Override protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException { }
}
由于这个是主要的操作,所以相关的代码就应该写在对应的doGet或者doPost的,具体细节在前面可能有些许的说明,但是还是需要看这里怎么处理
我们继续补充或者修改(可以将get也到post,因为post可以包含get的处理,一般来说mvc也是如此,但是他一般是get操作get,post操作post(但是是同一个方法),与HttpServlet相关,具体是操作了service,只是在中间可能会操作注解(前提是设置了),判断是否是对应的请求方式而进行报错的,比如进入到了get,那么在里面判断注解是否是get相关,可以选择再次的得到请求,而进行补充判断):
@Override public void init ( ServletConfig config) throws ServletException { }
在资源文件夹下补充mvc的配置文件mvc.xml:
< beans>
< component-scan base-package = " com.mvc.framework" />
</ beans>
在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/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version = " 4.0" > < display-name> create</ display-name> < servlet> < servlet-name> mvc</ servlet-name> < servlet-class> com.mvc.framework.servlet.DispatcherServlet</ servlet-class> < init-param> < param-name> contextConfigLocation</ param-name> < param-value> mvc.xml</ param-value> </ init-param> </ servlet> < servlet-mapping> < servlet-name> mvc</ servlet-name> < url-pattern> /*</ url-pattern> </ servlet-mapping>
</ web-app>
回到后端初始化,在这里写上如下:
package com. mvc. framework. servlet ; import javax. servlet. ServletConfig ;
import javax. servlet. ServletException ;
import javax. servlet. http. HttpServlet ;
import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import java. io. IOException ;
public class DispatcherServlet extends HttpServlet { @Override public void init ( ServletConfig config) throws ServletException { String contextConfigLocation = config. getInitParameter ( "contextConfigLocation" ) ; String s = doLoadconfig ( contextConfigLocation) ; doScan ( s) ; doInstance ( ) ; doAutoWired ( ) ; initHandlerMapping ( ) ; System . out. println ( "初始化完成...,等待请求与映射匹配了" ) ; } private void initHandlerMapping ( ) { } private void doAutoWired ( ) { } private void doInstance ( ) { } private void doScan ( String path) { } private String doLoadconfig ( String contextConfigLocation) { return "" ; } @Override protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException { doPost ( req, resp) ; } @Override protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException { }
}
实际上在上面使用静态块也可以操作,只是没有对应的请求数据,所以我们使用初始化,一般情况下,我们需要xml相关依赖来读取对应的xml信息:
那么我们加上如下的依赖:
< dependency> < groupId> dom4j</ groupId> < artifactId> dom4j</ artifactId> < version> 1.6.1</ version> </ dependency> < dependency> < groupId> jaxen</ groupId> < artifactId> jaxen</ artifactId> < version> 1.1.6</ version> </ dependency>
在真正编写之前,我们需要补充一些代码,在framework包下,创建controller包,然后在里面创建如下的类:
package com. mvc. framework. controller ; import com. mvc. framework. annotations. Autowired ;
import com. mvc. framework. annotations. Controller ;
import com. mvc. framework. annotations. RequestMapping ;
import com. mvc. framework. service. DemoService ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ; @Controller
@RequestMapping ( "/demo" )
public class DemoController { @Autowired private DemoService demoService; @RequestMapping ( "/query" ) public String query ( HttpServletRequest request, HttpServletResponse response, String name) { String s = demoService. get ( name) ; return s; } }
然后在DispatcherServlet中添加如下的代码:
package com. mvc. framework. servlet ; import com. mvc. framework. annotations. Autowired ;
import com. mvc. framework. annotations. Controller ;
import com. mvc. framework. annotations. RequestMapping ;
import com. mvc. framework. annotations. Service ;
import org. dom4j. Document ;
import org. dom4j. Element ;
import org. dom4j. io. SAXReader ; import javax. servlet. ServletConfig ;
import javax. servlet. ServletException ;
import javax. servlet. http. HttpServlet ;
import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import java. io. File ;
import java. io. IOException ;
import java. io. InputStream ;
import java. lang. reflect. Field ;
import java. lang. reflect. Method ;
import java. net. URLDecoder ;
import java. nio. charset. StandardCharsets ;
import java. util. ArrayList ;
import java. util. HashMap ;
import java. util. List ;
import java. util. Map ;
public class DispatcherServlet extends HttpServlet { private static List < String > classNames = new ArrayList < > ( ) ; private static Map < String , Object > map = new HashMap < > ( ) ; private static List < String > fieldsAlreayProcessed = new ArrayList < > ( ) ; private static Map < String , Method > handlerMapping = new HashMap < > ( ) ; @Override public void init ( ServletConfig config) { String contextConfigLocation = config. getInitParameter ( "contextConfigLocation" ) ; String s = doLoadconfig ( contextConfigLocation) ; doScan ( s) ; doInstance ( ) ; doAutoWired ( ) ; initHandlerMapping ( ) ; System . out. println ( "初始化完成...,等待请求与映射匹配了" ) ; } private void initHandlerMapping ( ) { if ( map. isEmpty ( ) ) { return ; } for ( Map. Entry < String , Object > entry : map. entrySet ( ) ) { Class < ? > aClass = entry. getValue ( ) . getClass ( ) ; if ( aClass. isAnnotationPresent ( Controller . class ) ) { String baseUrl = "" ; if ( aClass. isAnnotationPresent ( RequestMapping . class ) ) { String value = aClass. getAnnotation ( RequestMapping . class ) . value ( ) ; baseUrl += value; } Method [ ] methods = aClass. getMethods ( ) ; for ( int j = 0 ; j < methods. length; j++ ) { Method method = methods[ j] ; if ( method. isAnnotationPresent ( RequestMapping . class ) ) { RequestMapping annotation = method. getAnnotation ( RequestMapping . class ) ; String value = annotation. value ( ) ; String url = baseUrl; url += value; handlerMapping. put ( url, method) ; } } } } } private void doAutoWired ( ) { if ( map. isEmpty ( ) ) { return ; } for ( Map. Entry < String , Object > entry : map. entrySet ( ) ) { try { doObjectDependancy ( entry. getValue ( ) ) ; } catch ( Exception e) { e. printStackTrace ( ) ; } } } private static void doObjectDependancy ( Object object) { Field [ ] declaredFields = object. getClass ( ) . getDeclaredFields ( ) ; if ( declaredFields == null || declaredFields. length == 0 ) { return ; } for ( int i = 0 ; i < declaredFields. length; i++ ) { Field declaredField = declaredFields[ i] ; if ( ! declaredField. isAnnotationPresent ( Autowired . class ) ) { continue ; } if ( fieldsAlreayProcessed. contains ( object. getClass ( ) . getName ( ) + "." + declaredField. getName ( ) ) ) { continue ; } Object dependObject = null ; Autowired annotation = declaredField. getAnnotation ( Autowired . class ) ; String value = annotation. value ( ) ; if ( "" . equals ( value. trim ( ) ) ) { dependObject = map. get ( declaredField. getType ( ) . getName ( ) ) ; if ( dependObject == null ) { dependObject = map. get ( lowerFirst ( declaredField. getType ( ) . getSimpleName ( ) ) ) ; } } else { dependObject = map. get ( value+ declaredField. getType ( ) . getName ( ) ) ; } fieldsAlreayProcessed. add ( object. getClass ( ) . getName ( ) + "." + declaredField. getName ( ) ) ; if ( dependObject != null ) { doObjectDependancy ( dependObject) ; } declaredField. setAccessible ( true ) ; try { declaredField. set ( object, dependObject) ; } catch ( Exception e) { e. printStackTrace ( ) ; } } } private void doInstance ( ) { if ( classNames. size ( ) == 0 ) return ; if ( classNames. size ( ) <= 0 ) return ; try { for ( int i = 0 ; i < classNames. size ( ) ; i++ ) { String className = classNames. get ( i) ; Class < ? > aClass = Class . forName ( className) ; if ( aClass. isAnnotationPresent ( Controller . class ) ) { String simpleName = aClass. getSimpleName ( ) ; String s = lowerFirst ( simpleName) ; Object o = aClass. newInstance ( ) ; map. put ( s, o) ; } if ( aClass. isAnnotationPresent ( Service . class ) ) { String beanName = aClass. getAnnotation ( Service . class ) . value ( ) ; Object o = aClass. newInstance ( ) ; int ju = 0 ; if ( "" . equals ( beanName. trim ( ) ) ) { beanName = lowerFirst ( aClass. getSimpleName ( ) ) ; } else { ju= 1 ; } if ( ju== 1 ) { UtilGetClassInterfaces . getkeyClass ( beanName, aClass, map, o) ; } else { map. put ( beanName, o) ; } Class < ? > [ ] interfaces = aClass. getInterfaces ( ) ; if ( interfaces != null && interfaces. length > 0 ) { for ( int j = 0 ; j < interfaces. length; j++ ) { Class < ? > anInterface = interfaces[ j] ; map. put ( anInterface. getName ( ) , aClass. newInstance ( ) ) ; } } } } } catch ( Exception e) { e. printStackTrace ( ) ; } } private static String lowerFirst ( String str) { char [ ] chars = str. toCharArray ( ) ; if ( 'A' <= chars[ 0 ] && chars[ 0 ] <= 'Z' ) { chars[ 0 ] += 32 ; } return String . valueOf ( chars) ; } private void doScan ( String scanPackage) { try { String scanPackagePath = Thread . currentThread ( ) . getContextClassLoader ( ) . getResource ( "" ) . getPath ( ) + scanPackage. replaceAll ( "\\." , "/" ) ; scanPackagePath = URLDecoder . decode ( scanPackagePath, StandardCharsets . UTF_8 . toString ( ) ) ; File pack = new File ( scanPackagePath) ; File [ ] files = pack. listFiles ( ) ; for ( File file : files) { if ( file. isDirectory ( ) ) { doScan ( scanPackage + "." + file. getName ( ) ) ; continue ; } if ( file. getName ( ) . endsWith ( ".class" ) ) { String className = scanPackage + "." + file. getName ( ) . replaceAll ( ".class" , "" ) ; classNames. add ( className) ; } } } catch ( Exception e) { e. printStackTrace ( ) ; } } private String doLoadconfig ( String contextConfigLocation) { InputStream resourceAsStream = DispatcherServlet . class . getClassLoader ( ) . getResourceAsStream ( contextConfigLocation) ; SAXReader saxReader = new SAXReader ( ) ; try { Document document = saxReader. read ( resourceAsStream) ; Element rootElement = document. getRootElement ( ) ; Element element = ( Element ) rootElement. selectSingleNode ( "//component-scan" ) ; String attribute = element. attributeValue ( "base-package" ) ; return attribute; } catch ( Exception e) { e. printStackTrace ( ) ; } return "" ; } @Override protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException { doPost ( req, resp) ; } @Override protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException { System . out. println ( "请求资源的路径为:" + req. getRequestURI ( ) ) ; System . out. println ( "请求资源的完整路径为:" + req. getRequestURL ( ) ) ; String requestURI = req. getRequestURI ( ) ; Method method = handlerMapping. get ( requestURI) ; }
}
上面的代码在保存实例时,保存了与spring完全不一样的模式,是保存相关的任何处理:
在给出之前,先看看如下:
package com. mvc. framework. servlet ; public class a extends b implements f { public static void main ( String [ ] args) { getkeyClass ( a. class ) ; } private static void getkeyClass ( Class a) { Class superclass = a. getSuperclass ( ) ; if ( "java.lang.Object" . equals ( superclass. getName ( ) ) ) { System . out. println ( a. getName ( ) ) ; } else { System . out. println ( a. getName ( ) ) ; getkeyClass ( superclass) ; } Class [ ] interfaces = a. getInterfaces ( ) ; for ( Class anInterface : interfaces) { getkeyInterface ( anInterface) ; } } private static void getkeyInterface ( Class a) { Class [ ] interfaces1 = a. getInterfaces ( ) ; if ( interfaces1. length <= 0 ) { System . out. println ( a. getName ( ) ) ; return ; } System . out. println ( a. getName ( ) ) ; Class aClass = interfaces1[ 0 ] ; getkeyInterface ( aClass) ; }
}
package com. mvc. framework. servlet ; public class b implements c{
}
package com. mvc. framework. servlet ; public interface c extends d{
}
package com. mvc. framework. servlet ; public interface d {
}
package com. mvc. framework. servlet ; public interface f extends g{
}
package com. mvc. framework. servlet ; public interface g {
}
对应执行的结果是拿取其所有父类情况的全限定名,这个时候我们看这里:
dependObject = map. get ( value+ declaredField. getType ( ) . getName ( ) ) ;
他是根据对应的类型来的,而由于多态,那么可能存在非常多的类型,这也是我们这里的处理方式,而不是spring的遍历,或者指定名称(注意:在spring中,指定多个相同实例名称时也会报错,在id哪里,你也会或多或少的知道什么)
那么在保存时应该是在这里:
在这之前,我们首先在framework包下,创建util包,然后在里面创UtilGetClassInterfaces类加上如下:
package com. mvc. framework. util ; import java. util. Map ; public class UtilGetClassInterfaces { public static void getkeyClass ( String beanName, Class a, Map map, Object o) { Class superclass = a. getSuperclass ( ) ; if ( "java.lang.Object" . equals ( superclass. getName ( ) ) ) { map. put ( beanName + a. getName ( ) , o) ; } else { map. put ( beanName + a. getName ( ) , o) ; getkeyClass ( beanName, superclass, map, o) ; } Class [ ] interfaces = a. getInterfaces ( ) ; for ( Class anInterface : interfaces) { getkeyInterface ( beanName, anInterface, map, o) ; } } private static void getkeyInterface ( String beanName, Class a, Map map, Object o) { Class [ ] interfaces1 = a. getInterfaces ( ) ; if ( interfaces1. length <= 0 ) { map. put ( beanName + a. getName ( ) , o) ; return ; } map. put ( beanName + a. getName ( ) , o) ; Class aClass = interfaces1[ 0 ] ; getkeyInterface ( beanName, aClass, map, o) ; } }
在对应的代码中可以看到:
if ( ju== 1 ) { UtilGetClassInterfaces . getkeyClass ( beanName, aClass, map, o) ; } else { map. put ( beanName, o) ; }
这里就是解决的办法,但是如果这种办法是好的,为什么spring不使用呢,其实我们也可以看到,他们是各有利弊的,但是spring并没有隐患,或者隐患很小,而这种有隐患,取决于对应保存的相同的对象非常多,那么出现操作相同对象时也会出现共享的问题,当然,这些问题并不大,因为实例通常只是提供方法操作而已,所以这也算是解决的办法
上面的代码,需要仔细看看,当然,如果出现问题,后面也会给出的,通过上面的说明,可以发现,在doPost方法中,出现问题了,其中就是路径的问题,一般我们可以这样的解决:
String requestURI = req. getRequestURI ( ) ; String contextPath = req. getContextPath ( ) ; System . out. println ( "项目名称:" + contextPath) ; String substring = requestURI. substring ( contextPath. length ( ) , requestURI. length ( ) ) ; System . out. println ( "拿取的路径:" + substring) ; Method method = handlerMapping. get ( substring) ;
当然,一般来说,对应的/demo/query中,可能存在就算你不写/demo,只是写demo的情况(只需要判断开头的情况,因为其他情况会默认看成路径的),而在mvc中,通常是默认加上的,所以我们可以修改initHandlerMapping方法:
在这之前,我们需要考虑一件事,你可能也从来没有考虑过,也就是说,不在方法上加上RequestMapping注解,只在类上加上,并且单纯的访问类上的路径,那么他会访问到这个方法吗,答:并不会,一般会报错,在mvc中就是如此,而这里,我们可以看到,如果类上的注解有,那么继续往下走,只有存在对应的注解(方法上)的才会进行保存映射,然而这里我们还没有进行处理是否报错的问题,因为我们还没有写上,这里我们后面考虑,先考虑"/"的情况:
String value = aClass. getAnnotation ( RequestMapping . class ) . value ( ) ; if ( "/" . equals ( value. substring ( 0 , 1 ) ) == false ) { value = "/" + value; }
String value = annotation. value ( ) ; if ( "/" . equals ( value. substring ( 0 , 1 ) ) == false ) { value = "/" + value; }
这两个地方写上,来完成默认加上"/"的处理
针对上面的考虑的一件事的问题,其实最终的得到的值是null(因为只有存在对应的注解(方法上)的才会进行保存映射,而map在没有对于的key时,返回值的value自然就是null),考虑null是否报错即可
通过上面我们得到了一个Method,我们先看一个案例:
package com. mvc. framework. servlet ; import java. lang. reflect. Method ; public class a { public void fa ( String a, Integer b) { System . out. println ( 1 + a + b) ; } public void fb ( ) { System . out. println ( 1 ) ; } public static void main ( String [ ] args) { try { Class < a> aClass = a. class ; a a = aClass. newInstance ( ) ; Method [ ] methods = aClass. getMethods ( ) ; for ( int i = 0 ; i < methods. length; i++ ) { System . out. println ( methods[ i] . getName ( ) ) ; } Method fa = aClass. getMethod ( "fb" ) ; fa. invoke ( a) ; Method fb = aClass. getMethod ( "fa" , String . class , Integer . class ) ; fb. invoke ( a, "2" , 1 ) ; } catch ( Exception e) { e. printStackTrace ( ) ; } }
}
我们可以发现,要执行invoke的处理来调用类方法,我们需要一个类对象,也就是当前的对应类对象,对应与mvc中的DemoController类对象,并且有时候也需要参数,所以我们需要思考这里,由于map中,几乎只能保存一个信息,那么这里很明显,我们需要创建一个(实体)类:
我们在framework包下,创建pojo包,然后创建Handler类:
package com. mvc. framework. pojo ; import java. lang. reflect. Method ;
import java. util. HashMap ;
import java. util. Map ;
import java. util. regex. Pattern ; public class Handler { private Object controller; private Method method; private Pattern pattern; private Map < String , Integer > paramIndexMapping; public Handler ( Object controller, Method method, Pattern pattern) { this . controller = controller; this . method = method; this . pattern = pattern; this . paramIndexMapping = new HashMap < > ( ) ; } public Object getController ( ) { return controller; } public void setController ( Object controller) { this . controller = controller; } public Method getMethod ( ) { return method; } public void setMethod ( Method method) { this . method = method; } public Pattern getPattern ( ) { return pattern; } public void setPattern ( Pattern pattern) { this . pattern = pattern; } public Map < String , Integer > getParamIndexMapping ( ) { return paramIndexMapping; } public void setParamIndexMapping ( Map < String , Integer > paramIndexMapping) { this . paramIndexMapping = paramIndexMapping; }
}
现在我们修改initHandlerMapping方法的内容(我这里将注释去掉了,自己对应一下):
前提,我们将之前创建的private static Map<String, Method> handlerMapping = new HashMap<>();注释掉,因为不使用这个了,而是使用这个:
private List < Handler > handlerMapping = new ArrayList < > ( ) ;
还有一个前提,我们需要知道这个:
package com. mvc. framework. servlet ; import java. util. regex. Matcher ;
import java. util. regex. Pattern ; public class a { public static void main ( String [ ] args) { String a = "[0-9]{3}" ; String b = "122" ; CharSequence aa = b; System . out. println ( b. matches ( a) ) ; Pattern pattern = Pattern . compile ( "[0-9]{3}" ) ; Matcher m = pattern. matcher ( "122" ) ; boolean matches = m. matches ( ) ; System . out. println ( matches) ; System . out. println ( pattern) ; }
}
现在开始修改:
if ( method. isAnnotationPresent ( RequestMapping . class ) ) { RequestMapping annotation = method. getAnnotation ( RequestMapping . class ) ; String value = annotation. value ( ) ; if ( "/" . equals ( value. substring ( 0 , 1 ) ) == false ) { value = "/" + value; } String url = baseUrl; url += value; Handler handler = new Handler ( entry. getValue ( ) , method, Pattern . compile ( url) ) ; Parameter [ ] parameters = method. getParameters ( ) ; for ( int i = 0 ; i < parameters. length; i++ ) { Parameter parameter = parameters[ i] ; if ( parameter. getType ( ) == HttpServletRequest . class || parameter. getType ( ) == HttpServletResponse . class ) { handler. getParamIndexMapping ( ) . put ( parameter. getType ( ) . getSimpleName ( ) , i) ; } else { handler. getParamIndexMapping ( ) . put ( parameter. getName ( ) , i) ; } } handlerMapping. add ( handler) ; }
自己对应一下吧,然后我们修改doPost方法:
到这里我们可以知道框架的作用了,他是定义编写代码方式以及方便代码编写,而中间件是在编写好的代码基础上的一个补充或者增强
由于单纯的代码都写在doPost方法里面比较麻烦,所以我们定义一个方法(然而我也只是定义了一个小的方法),看如下就知道了:
在之前,我们需要知道这个:
package com. mvc. framework. servlet ; import java. lang. reflect. Method ; public class a { public void fa ( String a, Integer b, int j) { } public static void main ( String [ ] args) throws Exception { Class < a> aClass = a. class ; Method fa = aClass. getMethod ( "fa" , String . class , Integer . class , int . class ) ; Class < ? > [ ] parameterTypes = fa. getParameterTypes ( ) ; for ( int i = 0 ; i < parameterTypes. length; i++ ) { Class < ? > parameterType = parameterTypes[ i] ; System . out. println ( parameterType. getSimpleName ( ) ) ; } }
}
@Override protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException { Handler handler = getHandler ( req) ; if ( handler == null ) { resp. getWriter ( ) . write ( "404 not found" ) ; return ; } Class < ? > [ ] parameterTypes = handler. getMethod ( ) . getParameterTypes ( ) ; Object [ ] objects = new Object [ parameterTypes. length] ; int [ ] ii = new int [ parameterTypes. length] ; Map < String , String [ ] > parameterMap = req. getParameterMap ( ) ; Set < Map. Entry < String , String [ ] >> entries = parameterMap. entrySet ( ) ; for ( Map. Entry < String , String [ ] > param : entries) { String value = "" ; for ( int i = 0 ; i < param. getValue ( ) . length; i++ ) { if ( i >= param. getValue ( ) . length - 1 ) { value = param. getValue ( ) [ i] ; continue ; } value = param. getValue ( ) [ i] + "," ; } if ( ! handler. getParamIndexMapping ( ) . containsKey ( param. getKey ( ) ) ) { continue ; } Integer integer = handler. getParamIndexMapping ( ) . get ( param. getKey ( ) ) ; if ( "String" . equals ( parameterTypes[ integer] . getSimpleName ( ) ) ) { objects[ integer] = value; } if ( "Integer" . equals ( parameterTypes[ integer] . getSimpleName ( ) ) || "int" . equals ( parameterTypes[ integer] . getSimpleName ( ) ) ) { value = value. split ( "," ) [ 0 ] ; Integer i = null ; try { i = Integer . parseInt ( value) ; } catch ( Exception e) { e. printStackTrace ( ) ; throw new RuntimeException ( "String转换Integet报错,参数名称是:" + param. getKey ( ) ) ; } objects[ integer] = i; } ii[ integer] = 1 ; } Integer integer = handler. getParamIndexMapping ( ) . get ( HttpServletRequest . class . getSimpleName ( ) ) ; objects[ integer] = req; ii[ integer] = 1 ; integer = handler. getParamIndexMapping ( ) . get ( HttpServletResponse . class . getSimpleName ( ) ) ; objects[ integer] = resp; ii[ integer] = 1 ; for ( int i = 0 ; i < ii. length; i++ ) { if ( ii[ i] == 0 ) { if ( "int" . equals ( parameterTypes[ i] . getSimpleName ( ) ) ) { objects[ i] = 0 ; } } } try { handler. getMethod ( ) . invoke ( handler. getController ( ) , objects) ; } catch ( Exception e) { e. printStackTrace ( ) ; } } private Handler getHandler ( HttpServletRequest req) { if ( handlerMapping. isEmpty ( ) ) { return null ; } String requestURI = req. getRequestURI ( ) ; String contextPath = req. getContextPath ( ) ; String substring = requestURI. substring ( contextPath. length ( ) , requestURI. length ( ) ) ; for ( Handler handler : handlerMapping) { Matcher matcher = handler. getPattern ( ) . matcher ( substring) ; if ( ! matcher. matches ( ) ) { continue ; } return handler; } return null ; }
上面解决了mvc中int的默认赋值null的错误,但是我们也可以发现,他需要循环,mvc使用报错来解决也并不是不行,节省一点性能,当然,这点性能也并不大,所以可以认为是mvc的一个疏忽,然而这些疏忽并不是很影响具体开发,所以mvc就不操作改变了
至此,我们可以选择来测试一下:
我们回到这里:
package com. mvc. framework. controller ; import com. mvc. framework. annotations. Autowired ;
import com. mvc. framework. annotations. Controller ;
import com. mvc. framework. annotations. RequestMapping ;
import com. mvc. framework. service. DemoService ; import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ; @Controller
@RequestMapping ( "/demo" )
public class DemoController { @Autowired private DemoService demoService; @RequestMapping ( "/query" ) public String query ( HttpServletRequest request, HttpServletResponse response, String name) { String s = demoService. get ( name) ; return s; } }
修改一下:
@RequestMapping ( "/query" ) public String query ( HttpServletRequest request, HttpServletResponse response, String name) { System . out. println ( request) ; System . out. println ( response) ; String s = demoService. get ( name) ; return s; }
然后修改这个:
package com. mvc. framework. service. impl ; import com. mvc. framework. annotations. Service ;
import com. mvc. framework. service. DemoService ; @Service
public class DemoServiceImpl implements DemoService { @Override public String get ( String name) { System . out. println ( "打印:" + name) ; return name; }
}
然后启动服务器,访问这个:http://localhost:8080/springmvc_xie/demo/query?name=1234,其中springmvc_xie是项目名称
访问后你会发现,打印的值是null,为什么,通过我的调试,对应的这个地方得到的名称不是参数名称:
if ( parameter. getType ( ) == HttpServletRequest . class || parameter. getType ( ) == HttpServletResponse . class ) { handler. getParamIndexMapping ( ) . put ( parameter. getType ( ) . getSimpleName ( ) , i) ; } else { handler. getParamIndexMapping ( ) . put ( parameter. getName ( ) , i) ; }
经过上面的问题,我们来创建一个类:
package com. mvc. framework. servlet ; import java. lang. reflect. Method ;
import java. lang. reflect. Parameter ; public class a { public void fa ( String nam2, String name1) { } public void fb ( String nam2, String name1) { } public static void main ( String [ ] args) { Class < a> aClass = a. class ; Method [ ] fa = aClass. getMethods ( ) ; for ( int i = 0 ; i < fa. length; i++ ) { Method method = fa[ i] ; System . out. println ( method. getName ( ) ) ; Parameter [ ] parameters = method. getParameters ( ) ; for ( int ii = 0 ; ii < parameters. length; ii++ ) { Parameter parameter = parameters[ ii] ; System . out. println ( parameter. getName ( ) ) ; } } }
}
为什么会这样,这一般需要看版本,一般在jdk8开始就这样处理了,当然,这样的还有私有的,也就是设置可以访问private变量的变量值,在jdk8之前可能不用设置,但是之后(包括jdk8)不能直接的访问私有属性了(可能随着时间的推移,也会改变),因为需要进行设置这个,所以不能直接访问私有属性了
为什么要这样处理:这是为了提高字节码的紧凑性和安全性,参数名称在编译后被抹去,主要有以下原因:
隐私和安全性:在字节码中包含参数名称可能泄漏程序的细节信息,可能会被滥用,这对于某些应用来说是不可接受的,因为它可以暴露敏感信息
字节码紧凑性:字节码文件通常被用于分发和执行,如果包含了大量参数名称,将会增加文件大小,在资源受限的环境中,较小的字节码文件可以提高性能和减小存储需求
字节码的紧凑型还比较合理,但是为什么字节码会影响安全,举例:
反编译和逆向工程:如果参数名称包含在字节码中,恶意用户或攻击者可以更容易地反编译和逆向工程你的应用程序,以获取关于应用程序的内部结构和逻辑的信息,这可以帮助他们发现潜在的漏洞或弱点,甚至滥用应用程序
敏感信息泄露:在某些情况下,方法的参数名称可能包含敏感信息,如果这些名称泄露到字节码中,可能会暴露敏感数据、业务逻辑或应用程序内部细节,从而引发安全风险
安全性问题披露:如果方法参数名称包含有关应用程序内部的信息,攻击者可能会更容易发现应用程序的潜在漏洞,从而加大应用程序受攻击的风险
当然,spring是解决的或者说mvc是解决的(这里我们称为spring,因为最终是使用spring的主要内容的),但是他是通过其他方式,所以并不会暴露,然而,对方也可以通过spring来获取该参数,所以也只是避免了方便的获取,那么在不使用spring时,他既然会出现arg0这样的名称,那么有没有操作让他不这样做,其实是有的,只需要加上这个操作:
- parameters,编译选项
一般情况他是操作编译的,那么应该是如此的:javac -parameters YourClass.java,然而我们是操作项目,不可能这样处理,应该需要配置来使得项目默认这样处理,这里我们就以maven为例:
在maven中的pom.xml中操作如下:
< build> < plugins> < plugin> < groupId> org.apache.maven.plugins</ groupId> < artifactId> maven-compiler-plugin</ artifactId> < version> 3.8.0</ version> < configuration> < source> 11</ source> < target> 11</ target> < compilerArguments> < parameters /> </ compilerArguments> </ configuration> </ plugin> </ plugins> </ build>
执行后,可以发现,他获得了对应的参数名称,我们试一下修改版本,将上面的11修改成1.8,执行后,可以发现,也获得了参数名称了,当然,也并不是说任何版本都可以这样,可能有些版本这样的操作是不行的(现在还不确定,看以后,并且随着jdk的变化(并不是说对方不会对1.8或者11继续修改),可能1.8或者11也会不行),但是我们也可以发现,在之前操作mvc中,我们并没有处理这个,也就是说明了spring并不是使用这个方式,那么他是使用什么方式呢:
spring是使用字节码分析库(如 ASM)来动态分析类文件,而不依赖于编译器生成的字节码,这种技术使 Spring 能够获取参数名称,因为他是直接分析文件的,也就可以说,他内部的代码是相当于跳过了操作了-parameters选项,而访问并处理字节码文件(实际上再怎么处理字节码文件中都会保留原始参数名称,否则的话,也不可能使用该名称的变量了,他只是不让你直接获取即可,那么如果说选项中他-parameters是解决某一个开关导致的不让获取,那么spring是直接从文件找,也自然就跳过了这个开关)
那么就明白了,也就是直接的分析字节码文件,好吧,这对现在的我们来说几乎不可能手动写好,因为需要学习太多知识,并且细节非常多,基本不是我们个人能够完成的,所以我们拿取spring提供给我们的这个操作代码,但是引入spring一般我们并不能找到对应的类,所以我们选择使用第三方的代码,思考:为什么main的参数需要是对应的args数组:一般他代表命令行参数,这取决于对应的main是程序的入口,一般我们可以在命令行中执行java时传递,比如我们有一个java文件:
public class ceshi { public static void main ( String [ ] args) { for ( String arg : args) { System . out. println ( arg) ; } }
}
在命令行中(在目录下),执行javac ceshi.java,然后执行java ceshi,发现什么都没有,但是如果是这样的执行:java ceshi 1 2 3,那么会打印1和2和3,具体自行测试,也就是说他存在这样的作用:
命令行参数的传递:命令行参数是用户在终端或命令提示符中输入的,它们以字符串的形式传递给程序,因此,main函数需要一个字符串数组来接收这些参数,以便程序能够解析和使用它们
灵活性:通过使用命令行参数,程序可以根据用户提供的输入进行不同的操作,参数的个数和内容可以根据需要进行调整,从而增加程序的灵活性
标准化:采用参数数组的方式,使得命令行参数的处理在不同的编程语言和操作系统中更加一致和标准化,这有助于开发人员编写跨平台的代码
现在我们修改1.8回到11(看你自己,可以选择修改),开始使用spring的方式,这里我们选择拿取第三方的代码(这里我们使用ASM,spring也是使用类似的或者相同的),先在framework包下创建包config,然后在该包下我们创建几个类(在spring中有类似的这样的处理,这里我们手动写一个),这几个类在后面会给出
在手写之前,我们使用上面的配置,启动运行一下,然后操作mvc看看结果,如果出现了打印的值不是null,而是1234,那么我们可以说初步完成(这个时候,你可以选择给@Service操作value,然后@Autowired对应(在spring中,我们并没有这样的处理,这里我们是这样处理的),经过测试,也打印了1234,也可以测试将String name变成int name,如果打印为0,说明也操作成功(我们的方式,而不是Spring的mvc的报错),然后我们将路径中,比如:/demo的开头的/去掉,访问,可以发现,也可以,那么说明我们的操作代码编写完毕,当然,可能有些地方有问题,但是暂时没有,且功能都正确,如果出现了,请自行修改一下吧),然后我们使用spring对应的方式来解决:
首先我们需要引入依赖:
< dependency> < groupId> org.ow2.asm</ groupId> < artifactId> asm</ artifactId> < version> 9.2</ version> </ dependency>
然后去掉这个依赖配置:
< build> < plugins> < plugin> < groupId> org.apache.maven.plugins</ groupId> < artifactId> maven-compiler-plugin</ artifactId> < version> 3.8.0</ version> < configuration> < source> 1.8</ source> < target> 1.8</ target> < compilerArguments> < parameters /> </ compilerArguments> </ configuration> </ plugin> </ plugins> </ build>
执行服务器,先看看是否得到结果,很明显,没有得到,并且加上这个类来测试时,也是没有得到参数名称,这个类是之前的:
package com. mvc. framework. servlet ; import java. lang. reflect. Method ;
import java. lang. reflect. Parameter ; public class a { public void fa ( String nam2, String name1) { } public void fb ( String nam2, String name1) { } public static void main ( String [ ] args) { Class < a> aClass = a. class ; Method [ ] fa = aClass. getMethods ( ) ; for ( int i = 0 ; i < fa. length; i++ ) { Method method = fa[ i] ; System . out. println ( method. getName ( ) ) ; Parameter [ ] parameters = method. getParameters ( ) ; for ( int ii = 0 ; ii < parameters. length; ii++ ) { Parameter parameter = parameters[ ii] ; System . out. println ( parameter. getName ( ) ) ; } } }
}
这个时候,我们使用上面的依赖来解决不加这个配置的问题:
首先我们在framework包下,找到前面我们创建的config包(没有,现在创建也行),然后创建下面两个(前面的几个)类:
package com. mvc. framework. config ; import org. objectweb. asm. ClassVisitor ;
import org. objectweb. asm. Label ;
import org. objectweb. asm. MethodVisitor ;
import org. objectweb. asm. Opcodes ; import java. util. HashMap ;
import java. util. Map ; public class ParameterNameVisitor extends ClassVisitor { private String methodName; private Map map; public ParameterNameVisitor ( String methodName) { super ( Opcodes . ASM7 ) ; this . methodName = methodName; } @Override public MethodVisitor visitMethod ( int access, String name, String descriptor, String signature, String [ ] exceptions) { if ( name. equals ( methodName) ) { return new MethodParameterVisitor ( ) ; } return null ; } public Map getParameterNames ( ) { return map; } class MethodParameterVisitor extends MethodVisitor { public MethodParameterVisitor ( ) { super ( Opcodes . ASM7 ) ; } @Override public void visitLocalVariable ( String name, String descriptor, String signature, Label start, Label end, int index) { if ( index >= 0 ) { if ( map == null ) { map = new HashMap < > ( ) ; } map. put ( index - 1 , name) ; } } }
}
package com. mvc. framework. config ; import org. objectweb. asm. * ; import java. util. Map ; public class ParameterNameExtractor { public static Map getParameterNames ( String className, String methodName) throws Exception { ClassReader reader = new ClassReader ( className) ; ParameterNameVisitor visitor = new ParameterNameVisitor ( methodName) ; reader. accept ( visitor, 0 ) ; return visitor. getParameterNames ( ) ; }
}
然后我们改造这个a类:
package com. mvc. framework. servlet ; import com. mvc. framework. config. ParameterNameExtractor ;
import java. util. Map ; public class a { String name; public a ( String name) { this . name = name; } public a ( String name, String j) { this . name = name; } public void fa ( String nam2, String name1) { } public void fb ( String nam2, String name1) { } public static void main ( String [ ] args) throws Exception { String className = "com.mvc.framework.servlet.a" ; String methodName = "fa" ; Map map = ParameterNameExtractor . getParameterNames ( className, methodName) ; for ( int i = 0 ; i < map. size ( ) ; i++ ) { System . out. println ( "参数列表的位置:" + i + ",名称是: " + map. get ( i) ) ; } methodName = "fb" ; map = ParameterNameExtractor . getParameterNames ( className, methodName) ; for ( int i = 0 ; i < map. size ( ) ; i++ ) { System . out. println ( "参数列表的位置:" + i + ",名称是: " + map. get ( i) ) ; } }
}
执行后,可以发现,得到了参数列表,那么我们修改mvc的对应的方法,也就是这里:
Map < String , String > prmap = null ; try { prmap = ParameterNameExtractor . getParameterNames ( aClass. getName ( ) , method. getName ( ) ) ; } catch ( Exception e) { e. printStackTrace ( ) ; } Parameter [ ] parameters = method. getParameters ( ) ; for ( int i = 0 ; i < parameters. length; i++ ) { Parameter parameter = parameters[ i] ; if ( parameter. getType ( ) == HttpServletRequest . class || parameter. getType ( ) == HttpServletResponse . class ) { handler. getParamIndexMapping ( ) . put ( parameter. getType ( ) . getSimpleName ( ) , i) ; } else { handler. getParamIndexMapping ( ) . put ( prmap. get ( i) , i) ; } } handlerMapping. add ( handler) ;
执行服务器,访问后,可以发现,打印值是null,即没有出来,为什么:通过调试,我们可以发现,对应的值是完全可以的,但是报错了,为了找到问题,这里可以给出测试,首先在a类里面操作或者修改如下:
package com. mvc. framework. servlet ; import com. mvc. framework. config. ParameterNameExtractor ; import java. util. Map ; public class a { public static void main ( String [ ] args) throws Exception { String className = "com.mvc.framework.controller.DemoController" ; String methodName = "query" ; Map map = ParameterNameExtractor . getParameterNames ( className, methodName) ; for ( int i = 0 ; i < map. size ( ) ; i++ ) { System . out. println ( "参数列表的位置:" + i + ",名称是: " + map. get ( i) ) ; } }
}
执行后,可以得到对应的参数列表,然后再mvc(我们的模拟)中的init中加上如下(注意:先把他里面的调用都注释):
@Override public void init ( ServletConfig config) { String className = "com.mvc.framework.controller.DemoController" ; String methodName = "query" ; Map map = ParameterNameExtractor . getParameterNames ( className, methodName) ; for ( int i = 0 ; i < map. size ( ) ; i++ ) { System . out. println ( "参数列表的位置:" + i + ",名称是: " + map. get ( i) ) ; } }
然后执行,发现就是对应我们启动服务器的处理,那么肯定的是对应的服务器的关系,并且一般这个错误是Class not found,且错误发生再ClassReader reader = new ClassReader(className);:
我们看看他的源码:
public ClassReader ( String className) throws IOException { this ( readStream ( ClassLoader . getSystemResourceAsStream ( className. replace ( '.' , '/' ) + ".class" ) , true ) ) ; } private static byte [ ] readStream ( InputStream inputStream, boolean close) throws IOException { if ( inputStream == null ) { throw new IOException ( "Class not found" ) ; } else { . . .
可以发现这样的:
经过上面的说明,我们还需要修改对应的方法,修改如下:
private void initHandlerMapping ( ServletConfig config) { String ba = aClass. getName ( ) . replace ( '.' , '/' ) + ".class" ; prmap = ParameterNameExtractor . getParameterNames ( config. getServletContext ( ) . getRealPath ( "/" ) + "WEB-INF\\classes\\" + ba, method. getName ( ) ) ; InputStream inputStream = new FileInputStream ( className) ; ClassReader reader = new ClassReader ( inputStream) ;
修改后,我们执行,访问一下看看结果,发现打印对应的值出来了,说明我们操作成功
至此,我们可以说mvc的模拟框架编写完成,到这里你会发现,但凡与结构相关的,基本上反射都可以做到,也就是说注解他是一个结构,那么可以绝对的说,注解就是由反射来完成操作关联的(其实被注解操作的方法大多数只是定义(如参数列表),具体可能由反射来调用或者说操作),并且但凡需要与结构信息相关联的,反射也都可以做到,所以以后,如果出现与结构进行关联操作的,那么使用反射吧
到这里,应该很明显知道,比spring要困难多了吧,spring只需要考虑循环依赖,而这里除了考虑spring,还需要考虑很多的细节,特别的,是字节码文件的解析问题,以及反射的细节使用,还有保存对应的关联
当然,上面的编写只是mvc的一部分,特别的,关于视图方面的以及拦截方面的(如我们好像并没有判断处理/*的操作)我们也没有给出,所以说才只是一部分
由于博客字数限制,其他内容,请到下一篇博客(112章博客)去看