SpringMVC
HelloWorld
XML配置
-
创建webapp工程
-
pom.xml中添加依赖spring-web, spring-webmvc
-
配置web.xml
<!-- 配置中文乱码过滤器 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 配置controller控制分发器 --> <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*:spring-mvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
-
配置spring-mvc.config
<!--扫包, 默认打开annotation-config--> <context:component-scan base-package="packageName" />
-
配置servlet
//注册bean, 使得@RequestMapping被扫描到 @Controller //@@RequestMapping("/user") public class UserServlet { //此注解可以写在类上, 后续访问路径则为 /user/save @RequestMapping("/save")//一般与方法名相同, 可随意 public String save(){ System.out.println("userServlet"); return "index.jsp"; } }
技术架构图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EbhGo7hq-1598342120682)(F:\develop\Z_JavaEE\level_3_springMVC.assets\1595240212144.png)]
核心组件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M0ernMcQ-1598342120690)(F:\develop\Z_JavaEE\level_3_springMVC.assets\1595243801630.png)]
Controller层bean加载过滤
<context:component-scan base-package="com.cyy">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
@ComponentScan(value = "com.cyy.controller", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
静态资源放行
<!-- web.xml -->
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<!-- 拦截所有资源,包括静态资源,所以需要配置静态资源的放行 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- springmvc配置文件-->
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/js/**" location="/js/"/>
<!-- 使用默认,放行所有普通资源-->
<mvc:default-servlet-handler/>
开启mvc相关注解
<mvc:annotation-driven />
注解驱动
配置类
@Configuration
@ComponentScan(
value = "com.cyy",
excludeFilters = {
@ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = {Controller.class})
})
public class SpringConfig {
}
基于servlet3.0规范, 自定义Servlet容器初始化配置类,加载springmvc核心配置类
public class MyServletInitConfig extends AbstractDispatcherServletInitializer {
//配置
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext aca = new AnnotationConfigWebApplicationContext();
aca.register(SpringConfig.class);
return aca;
}
//配置路径
@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
//配置过滤器
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
CharacterEncodingFilter cf = new CharacterEncodingFilter();
cf.setEncoding("UTF-8");
FilterRegistration.Dynamic filter = servletContext.addFilter("characterEncoding", cf);
filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.INCLUDE, DispatcherType.FORWARD),
false, "/*");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gqruZJOs-1598342120694)(F:\develop\Z_JavaEE\level_3_springMVC.assets\1595245865783.png)]
Request
请求参数
普通参数
//参数名与形参一致或者配置@RequestParam注解
//required默认为true, 没有参数则报错, name属性与value属性互为别名, 随意写
@RequestMapping("/add")
public String add(@RequestParam(value = "hobby", required = true, defaultValue = "jack") String[] sa) {
for (String s : sa) {
System.out.println(s);
}
//返回为要访问的资源,默认为forword
return "/index.jsp";
}
POJO类型(使用getter方法,与EL类似)
1. 基本类型与属性名相同
?ame=jack
2. 引用类型使用 变量名.属性 传递数据
?address.city=cq
3. 集合使用 list[index].属性 传递数据(list.get(index))
?hobby=play&hobby=swim
?list[0].hobby=play&list[1].hobby=swim
4. map使用 map['key'].属性 传递数据(map.get(key))
?map['jack'].hobby=play&map['rose'].hobby=swim
数组(存储的是普通类型)
//uri: ?hobby=play&hobby=swim 请求参数数量需要大于1
public String add(@RequestParam("hobby") String[] sa) {
for (String s : sa) {
System.out.println(s);
}
return "/index.jsp";
}
集合(存储的是普通类型)
//uri: ?hobby=play&hobby=swim 请求参数数量需要大于1
public String add(@RequestParam("hobby") List<String> sa) {
for (String s : sa) {
System.out.println(s);
}
return "/index.jsp";
}
类型转换器
package org.springframework.core.convert.converter;
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S source);
}
ObjectToObjectConverter
xml配置
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<set>
<bean class="org.springframework.format.datetime.DateFormatter">
<property name="pattern" value="yyyy-MM-dd"/>
</bean>
</set>
</property>
</bean>
注解
//形参前, 或成员变量上
@RequestMapping("date")
public String dateFormat(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date) {
return "/index.jsp";
}
自定义类型转换器
//指定转换前后类型
public class MyDateConverter implements Converter<String, Date> {
@Override
public Date convert(String s) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = dateFormat.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
注册自定义类型转换器
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.cyy.MyDateConverter"/>
</set>
</property>
</bean>
请求映射
注解
//方法上指定访问uri
//类上指定公共访问uri前缀
//请求返回的地址默认为当前路径,需要修改返回值
@RequestMapping
//常用属性
value="/user"
method="RequestMethod.GET"
params="name" //设定请求参数条件
headers="content-type=text/*" //设定请求头消息条件
consumes="text/*" //指定可以接收的请求正文类型(MIME类型)
produces="text/*" //指定可以生成的响应正文类型(MIME类型)
Response
无数据页面跳转设定
当返回值为String时, 跳转至返回值指定页面
@RequestMapping("/save")
public String save(String name) {
//默认为forword
return "forword:index.jsp";
//return "redirect:index.jsp";
}
设定通用的访问路径简化配置(此时返回值字符串中不能使用forword和redirect)
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
</bean>
如果返回值为void, 默认使用方法名作为页面地址的前缀后缀
携带数据页面跳转
- 使用HttpServletRequest
//使用HttpServletRequest
@RequestMapping("date")
public String request01(HttpServletRequest request) {
request.setAttribute("key", "value");
return "/index.jsp";
}
- 使用Model类型形参进行数据传递
@RequestMapping("date")
public Model request01(Model model) {
//addAttribute(String, Object);
model.addAttribute("key", "value");
return model;
}
- 使用ModelAndView
@RequestMapping("/save")
public ModelAndView save(ModelAndView mv) {
mv.addObject("key", "value");
mv.setViewName("redirect:/index.jsp");
return mv;
}
返回数据, 无页面跳转
-
返回基本数据
- 使用HttpServletResponse
- @ResponseBody
-
返回JSON数据
-
使用第三方资源转化对象为json数据, 基于response返回
-
使用@ResponseBody, 集合也直接返回
xml配置消息类型转换器: (使用@EnableWebMvc在配置类上, 替换xml)
-
FastJson
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json"/>
<property name="features">
<list>
<value>WriteMapNullValue</value>
<value>WriteDateUseDateFormat</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
Jackson
添加依赖即为使用jackson作为序列化和反序列化的第三方组件
自定义ObjectMapper
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
super();
//去掉默认的时间戳格式
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//设置为东八区
setTimeZone(TimeZone.getTimeZone("GMT+8"));
//设置日期转换yyyy-MM-dd HH:mm:ss
setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 设置输入:禁止把POJO中值为null的字段映射到json字符串中
configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
//空值不序列化
setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 反序列化时,属性不存在的兼容处理
getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 序列化枚举是以toString()来输出,默认false,即默认以name()来输出
configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
}
}
配置自定义ObjectMapper
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<!-- 自定义Jackson的objectMapper -->
<property name="objectMapper" ref="customObjectMapper" />
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean name="customObjectMapper" class="com.cyy.CustomObjectMapper"/>
Servlet相关接口
- reques/response/session通过形参声明即可使用
- Head数据获取
- 通过@RequestHeader(“headName”) 在处理器类中方法的形参处声明
- cookie数据获取
- @CookieValue(“cookieName”)在处理器类中方法的形参处声明
- session数据获取
- @SessionAttribute(“key”)在处理器类中方法的形参处声明
- session设置参数
- @SessionAttributes(names={“name”,…}) 在处理器类上声明放入session的变量名, 适用于Model类型数据传参
模拟SpringBoot内嵌tomcat
实现 ServletContainerInitializer
配置文件META-INF下的javax.servlet.ServletContainerInitializer中配置全限定类名
异步请求响应
使用json前提导入jackson资源依赖
contentType:application/text
@RequestBody String message //普通数据传递
contentType:application/json
@RequestBody User user //对象数据传递
contentType:application/json
@RequestBody List<User> list //对象数据传递
@ReqeustBody//形参注解 前端必须指定contentType, 必须是POST提交,json数据格式必须为json字符串
@ResponseBody//方法注解, 返回值注解
public @RequestBody List<User> getUsers(){}
跨域访问
ip端口协议域名有一个不同
//测试
//修改hosts,如
// 127.0.0.1 www.cyy.com
//刷新dns ipcongfig /displaydns
// ipconfig /flushdns
@CrossOrigin//方法注解, 类注解 设置方法允许跨域访问
拦截器
类似于过滤器,但是属于springmvc,核心思想为AOP
- filter属于servlet技术, Interceptor属于springmvc技术
- filter对所有访问拦截, interceptor仅对springmvc的访问进行增强
自定义拦截器
public class MyInterceptor implements HandlerInterceptor {
//前置拦截方法,执行此方法后执行业务方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//handler是被调用的处理器对象, 本质上是反射中Method的一个包装类
System.out.println("preHandle");
return true;
//返回false则拦截不进入原方法,不进入postHandle(),不进入afterCompletion()
}
//后置拦截方法,业务方法执行后再执行此方法
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
//modelAndView对被拦截方法的返回值进行读取数据和页面信息,可以进行调整
System.out.println("postHandle");
}
//完成后拦截方法
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
xml中配置
<mvc:interceptors>
<mvc:interceptor>
<!-- 先配置执行位置, 再配置执行类
*表示任意名称, ** 表示当前路径及子路径
-->
<mvc:mapping path="/get*"/>
<mvc:mapping path="/**"/>
<mvc:mapping path="/save*"/>
<!-- exclude-mapping 表排除 -->
<mvc:exclude-mapping path="/get*" />
<bean class="com.cyy.config.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
安全校验
token
session.setAttribute("token", token);
拦截器链
-
多个拦截器配置顺序即为执行顺序
-
所有后置在执行完才执行afterCompletion
-
只要前置返回true, afterCompletion就会执行.
异常处理
@Component
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) {
ModelAndView modelAndView = new ModelAndView();
if (e instanceof NullPointerException){
modelAndView.setViewName("page.jsp");
} else if (e instanceof ArithmeticException) {
modelAndView.setViewName("index.jsp");
} else {
modelAndView.setViewName("index.html");
}
return modelAndView;
}
}
@Component
@ControllerAdvice//类注解,设置当前类为异常处理类
public class AnnotationMyExceptionHandler {
@ExceptionHandler(Exception.class)//方法注解, 指定异常的处理方式
public GeneralResult exceptionHandler(Exception e) {
GenaralResult result = new GenaralResult();
if (e instanceof NullPointerException){
...
} esle {
...
}
result.setCode(5001);
result.setFlag(false);
result.setMsg(e.getMessage());
return result;
}
}
注解处理器可以拦截到入参类型转换异常, 非注解无法拦截
项目异常处理方案
业务异常: 发送信息给用户,提醒规范操作
规范的用户行为产生的异常
不规范的用户行为操作产生的异常
系统异常: 发送固定信息给用户,安抚用户&发送消息给运维,提醒维护&记录日志
项目运行过程中无法预计且无法避免的异常
其他异常: 发送固定信息给用户,安抚用户&发送消息给编程人员, 提醒维护(纳入预期范围)&记录日志
编程人员未预期到的异常
通过自定义异常将所有的异常分类管理,已统一的格式对外呈现异常消息
实用技术
文件上传
springmvc提供MultipartResolver接口对通用操作进行封装
CommonsMultipartResolver 调用了apache的上传下载组件
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1024000000"/>
</bean>
@RequestMapping("/fileupload")
@ResponseBody
//形参使用MultipartFile对象, fileName和前端表单中的name一致
public String fileupload(MultipartFile file) throws IOException {
file.getOriginalFilename();//获取上传文件名, 解析文件名及其扩展名
imgfile.transferTo(new File("name.png"));
return "index.html";
}
服务器上传文件问题
- 文件命名问题
- 文件名过长问题(数据库保存文件名,本地另存文件名)
- 文件保存路径
- 重名问题
Restful
Representation State Transfer 一种网络资源的访问风格, 定义了网络资源的访问方式
优点: 隐藏资源的访问行为,通过地址栏无法得知具体操作/简化书写
行为约定: 并非规范, 可以打破
get : 查询
post : 保存
put : 更新
delete : 删除
@RestController
@RequestMapping("/user")
public class UserServlet{
@PutMapping("{id}")
//@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
@ResponseBody
public void test001(@PathVariable String id){
//
}
}
//@GetMapping
//@DeleteMapping
//@PostMapping
web.xml配置开启表单restful风格的访问支持过滤器
<filter>
<filter-name>hiddenHttpMenthodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMenthodFilter</filter-name>
<servlet-name>DispatcherServlet</servlet-name>
</filter-mapping>
页面表单使用隐藏域提交请求类型,参数命名固定为"_method", 必须配合提交类型method=post使用
<form action="url" method="post">
<input type="hidden" name="_method" value="put" />
<inout type="submit" />
</form>