1. 依赖
<!--JavaEE最基本的依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<!--SpringMVC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
Tomcat自带javax.servlet-api、jsp-api,所以设置scope=provided即可。
依赖变灰色(红框灰色),是因为外面有(红框白色)。
2. 利用反射获取参数名
从JDK8开始,可以通过java.lang.reflect.Parameter类获取参数名;
- 前提:在编译
*.java的时候保留参数名信息到*.class中,比如javac -parameters *.java - 可以通过
javap -v *.class查看class文件的参数名信息 java *执行class文件,比如有主类,且有main函数。
编写TestParam.java放在桌面。
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class TestParam {
public void run(String name, int age) {}
public static void main(String[] args) throws NoSuchMethodException {
Method method = TestParam.class.getMethod("run", String.class, int.class);
for (Parameter parameter : method.getParameters()) {
System.out.println(parameter.getName());
}
}
}
注意:package com.xxx 去掉
-
javac TestParam.java编译TestParam.java文件java TestParam运行TestParam.class (注意不能是java TestParam.class)
查看打印结果是:
arg0
arg1
获取不到参数名。javap -v TestParam.class查看这个class的参数名信息。
看不到参数名信息。
-
javac -parameters TestParam.java编译TestParam.java文件java TestParam打印结果:name age。可以获取到参数名javap -v TestParam.class查看这个class的参数名信息。
对比javac、javac -parameters 和Maven编译的差异
项目中通过Maven编译,会添加一些其他的信息。
package com.xxx;
import org.junit.Test;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class TestParam {
public void run(String name, int age) {}
@Test
public void test() throws NoSuchMethodException {
Method method = TestParam.class.getMethod("run", String.class, int.class);
for (Parameter parameter : method.getParameters()) {
System.out.println(parameter.getName());
}
}
}
运行下test方法,找到通过Maven编译后的TestParam.class
javap -v xxx.class 对比javac、javac -parameters 和Maven编译的差异
javac
public void run(java.lang.String, int);
descriptor: (Ljava/lang/String;I)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=0, locals=3, args_size=3
0: return
LineNumberTable:
line 7: 0
javac -parameters
public void run(java.lang.String, int);
descriptor: (Ljava/lang/String;I)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=0, locals=3, args_size=3
0: return
LineNumberTable:
line 7: 0
MethodParameters:
Name Flags
name
age
Maven
public void run(java.lang.String, int);
descriptor: (Ljava/lang/String;I)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=0, locals=3, args_size=3
0: return
LineNumberTable:
line 10: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/scc/TestParam;
0 1 1 name Ljava/lang/String;
0 1 2 age I
观察得知,通过Maven编译的,会多了局部变量表。
SpringMVC获取参数名的方法
SpringMVC内部通过PrioritizedParameterNameDiscoverer获取参数名。
@Override
@Nullable
public String[] getParameterNames(Method method) {
for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) {
String[] result = pnd.getParameterNames(method);
if (result != null) {
return result;
}
}
return null;
}
SpringMVC通过多种方法获取参数名,一个parameterNameDiscoverer就是一个方案。
点击ParameterNameDiscoverer ctrl + H:
会优先使用StandardReflectionParameterNameDiscoverer反射的方式去获取。然后通过 LocalVariableTableParameterNameDiscoverer局部变量的方式去获取。
3. Tomcat内置的2个默认Servlet
可以再 TOMCAT_HOME/conf/web.xml中找到
- org.apache.catalina.servlets.DefaultServlet url-pattern是/,可以处理静态资源
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
- org.apache.jasper.servlet.JspServlet url-pattern是*.jsp
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
不是用SPringMVC的Servlet,使用@WebServlet注解的接口,jsp、静态资源html等依然可以请求到,Tomcat内部有Servlet会拦截请求,处理。 比如请求一个html:http://localhost:8080/mvc01/test.html. Tomcat拦截请求后,会交给Tomcat内部的Servlet处理, 可能这样,内部会把静态资源转换为二进制流返回给客户端。
静态资源被拦截的解决方案
SpringMVC的Servlet的url匹配,*.do、/、/*的区别
-
*.do: 不会拦截动态资源(比如*.jsp)、静态资源(比如 .html、.js) -
/: 会拦截静态资源(比如 .html、.js),不会拦截动态资源(比如*.jsp) -
/*: 会拦截动态资源(比如*.jsp)、静态资源(比如 .html、.js)。 一般用于Filter中。
解决方案1
如果SpringMVC的DispatcherServlet的url-pattern设置为/,会导致静态资源拦截
解决方案:配置<mvc:default-servlet-handler/>,将静态资源交回给Tomcat的DefaultServlet去处理
原理:配置后会通过DefaultServletHttpRequestHandler对象将静态资源转发给Tomcat的DefaultServlet处理。
源码
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Assert.state(this.servletContext != null, "No ServletContext set");
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
if (rd == null) {
throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" +
this.defaultServletName + "'");
}
rd.forward(request, response);
}
使用了mvc:default-servlet-handler/后会导致controller无法处理请求
加上<mvc:annotation-driven/>(注解驱动)即可保证controller正常使用
解决方案2
由SpringMVC框架来处理静态资源(内部通过ResourceHttpRequestHandler对象)
<!--**代表所有子路径-->
<!--mapping是请求路径-->
<!--location是静态资源的位置-->
<!--同样需要加上<mvc:annotation-driven/>-->
<mvc:resources mapping="/asset/**" location="/asset/" />
同样需要加上<mvc:annotation-driven/>,确保controller正常使用。
InternalResourceViewResolver
受InternalResourceViewResolver影响的有
- 通过返回值 ModelAndView 设置 viewName
- 通过返回值 String设置的 viewName
- 通过
<mvc:view-controller>设置的 viewName
可以配置多个InternalResourceViewResolver order值越小,优先级越高
忽略受InternalResourceViewResolver影响
方法一: 在viewName前面加上 "forward:"、"redirect"
方法二: 通过ModelAndView的setView方法
实际上,之前通过返回值String、ModelAndView设置viewName之后
SpringMVC内部会根据具体情况创建对应的View对象
InternalResourceView、JstlView、RedirectView
- InternalResourceViewResolvery影响的是没有带 "forward:"、"redirect" 的viewName
自定义InternalResourceViewResolver
InternalResourceViewResolver原理是会根据viewName创建响应的view(InternalResourceView、JstlView、RedirectView),然后view会调checkResource判断是否有相关文件,由于checkResource方法没有复写,调用的是父类的方法
public boolean checkResource(Locale locale) throws Exception {
return true;
}
父类方法永远返回true,所以InternalResourceViewResolver的order属性是无效的,有资源文件会处理,没有就返回404(解析不了的返回505),不会调优先级较低的InternalResourceViewResolver。
我们可以自定义InternalResourceViewResolver,返回真实的checkResource。
public class MyResourceView extends InternalResourceView {
@Override
public boolean checkResource(Locale locale) throws Exception {
String path = getServletContext().getRealPath(getUrl());
File file = new File(path);
return file.exists();
}
}
如果checkResource返回false,会交给优先级次之的InternalResourceViewResolver处理,以此类推:
1.如果文件不存在,但优先级最低的返回true,返回404
2.如果文件不存在,但优先级最低的返回false,返回500
jsp7不存在
@RequestMapping("/jsp2")
public ModelAndView jsp2() {
ModelAndView modelAndView = new ModelAndView("jsp2");
modelAndView.setViewName("jsp7");
return modelAndView;
}
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page2/"/>
<property name="suffix" value=".jsp"/>
<property name="order" value="1"/>
</bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
<property name="order" value="0"/>
<property name="viewClass" value="com.xxx.view.MyResourceView"/>
</bean>
返回404错误
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page2/"/>
<property name="suffix" value=".jsp"/>
<property name="order" value="1"/>
<property name="viewClass" value="com.xxx.view.MyResourceView"/>
</bean>
返回500错误。
Java代码中路径问题总结
在Java代码中,路径问题总结
- 假设请求路径是:"http://IP地址:端口/context_path/path1/path2/path3"
- 假设转发路径是:"/page/test.jsp" 1> 以斜线(/)开头,参考路径是context_path 2> 所以最终转发路径是:"http://IP地址:端口/context_path" + "/page/test.jsp"
- 假设转发路径是:"page/test.jsp" 1> 不以斜线(/)开头,参考路径是当前请求路径的上一层路径 2> 所以最终转发路径是:"http://IP地址:端口/context_path/path1/path2/" + "page/test.jsp"
重定向同样适用
mv.setViewName("redirect:/page/jsp4.jsp?test=10");\ return "redirect:/page/jsp3.jsp";,
例外:
modelAndView.setView(new RedirectView("/page/jsp3.jsp")); 和 return "redirect:/page/jsp3.jsp";的区别
modelAndView.setView(new RedirectView("/page/jsp3.jsp")); 为 http://IP地址:端口/page/jsp3.jsp
context_path没了
在jsp、html代码中,路径问题总结
- 假设请求路径是:"http://IP地址:端口/context_path/path1/path2/path3"
- 假设跳转路径是:"/page/test.jsp"
1> 以斜线(/)开头,参考路径是"http://IP地址:端口"
2> 所以最终转发路径是:"http://IP地址:端口" + "/page/test.jsp" - 假设转发路径是:"page/test.jsp"
1> 不以斜线(/)开头,参考路径是当前请求路径的上一层路径
2> 所以最终转发路径是:"http://IP地址:端口/context_path/path1/path2/" + "page/test.jsp"