SpringMVC
SpringMVC的核心是DispatcherServlet,所有请求都要通过它转发,当一个用户发起一个请求,DispatcherServlet先找到处理器映射,根据映射找到类中对应的控制器,然后调用控制器中对应的方法,返回数据模型(即各种需要被前端用到的数据,比如实体类)以及视图名称,然后DispatcherServlet在根据控制器返回的视图名称找到视图解析器解析视图,最后输出到前端响应
- DispatcherServlet找到处理器映射
- 根据映射结果找到控制器中对应的方法
- 根据方法返回的模型和视图名找到视图解析器解析
- 然后根据视图解析器找到视图使用模型渲染结果
在这里模型指的是返回给用户并在浏览器显示的数据
1.通过java配置取代web.xml配置
servlet3.0规范定义了一个ServletContainerInitializer接口来初始化类,用于替代web.xml,在servlet容器启动时,会加载该接口的实现类用于加载相关配置,SpringMVC的AbstractAnnotationConfigDispatcherServletInitializer实现了这个接口,继承这个接口即可实现不通过web.xml配置文件,
//当web程序启动时,tomcat会加载实现了ServletContainerInitializer,而下面这个就实现了
//所以。这个就是一个配置类,用于代替web.xml,在该类被初始化时,会同时初始化Dispatcher核心类和ConTextLoaderListener
//后面ContextLoaderListener个类用于getServletConfigClasses方法
public class SpitterWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//根配置,配置数据库等 其他要用到的组件信息
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class};
}
//Dispatcher配置
//使用conTextLoaderListener加载前端控制器的上下文
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
//实现ServletContainerInitializer是用于替代web.xml,而Abstra....是SpringMVC 在替代web.xml的基础上加入MVC特有的组件
//而这个ServletContainer是servlet3.0以后的产物,servlet容器tomcat7或者更高的版本才能这么使用
//拦截
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
RootConfig
package spitter.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@ComponentScan(basePackages = {"spitter"},excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = EnableWebMvc.class)})
public class RootConfig {
}
WebConfig
package spitter.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
@ComponentScan("spitter.web")
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver resourceViewResolver = new InternalResourceViewResolver();
resourceViewResolver.setPrefix("/WEB-INF/view/");
resourceViewResolver.setSuffix(".jsp");
resourceViewResolver.setExposeContextBeansAsAttributes(true);
return resourceViewResolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
其中getServletConfigClass用于获取配置Dispatcher相关属性,而rootConfig用于配置其他类,例如mybatis redies等
最后在创建一个控制器
package spitter.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
static {
System.out.println("初始化成功");
}
@RequestMapping(value = "/",method = RequestMethod.GET)
public String home(){
return "home";
}
}
然后在对应设置的目录创建一个jsp,最后测试就能成功跳转到页面
传递模型数据到视图中
在控制器的方法中,可以指定模型参数,就可以把模型数据带到前端显示,实际这个参数就是一个map,具体如下,在这里传递了一个对象集合
/**
* 给定一个模型参数,用于传递 需要显示在前端的数据,实际上model是一个map,
* 当不给其传递key时,他会自己推断,在这里名字为spittles,如果你不希望使用Model 作为参数
* 你也可以使用Map model作为参数,效果已有
*/
public String home(Model model){
model.addAttribute(createSpittleList(20));
return "home";
}
接收请求的输入
SpringMVC自然也需要支持前端传参数到后台,SpringMVC中可以在方法参数前面添加一个@RequestParam表示要接受的参数的name,代码如下
/**
* 在这里@RequestParam代表必须要传的参数,也可以指定一个默认值,在没有传入参数的时候
* 将赋值为默认值,默认值都必须是String类型的,不过在赋值给max的时候会做对应的转换
* @param max
* @param count
* @return
*/
public List<Spittle> spittles(@RequestParam(value = "max",defaultValue = "3") long max,@RequestParam int count){
System.out.println(max +" " +count);
return findSpittle(max,count);
}
如果在一个页面的form表单上不写action,那么这个表单提交时,就会默认提交到 跳转到这个页面上的路径,意思就是 如果你访问 /index,而Controller跳转到index.jsp,那么这个jsp就会提交到该控制器中去,即可指定一个POST同名方法,参数不一致即可(重载)
如下,其中redirect代表重定向,直接输入controller的url路径
@RequestMapping(value = "view",method = RequestMethod.POST)
public String spittles(@RequestParam(value = "max",defaultValue = "3") long max,@RequestParam int count){
System.out.println(max +" " +count);
return "redirect:/view";
}
@RequestMapping(value = "view",method = RequestMethod.GET)
public String spittles(){
return "form";
}
校验表单
普通的后台校验表单是获取到前端的值,然后做很多逻辑判断,这样既写了很多重复代码,而且如果字段比较多,写起来也比较麻烦,从Spring3.0开始,Spring提供了对java校验api,又称之为JSR-303,实际上是一个接口,而其实现类就是Hibernate Validator,下面是相关注解
- @AssertFalse:所注解的属性必须是false
- @AssertTrue:所注解的属性必须是true
- @DecimalMax:所注解的属性必须是数字,且小于给定的值
- @DecimalMin:所注解的属性必须是数字,且大于给定的值
- @Digits:必须是数字,且必须有指定的数字
- @Future:必须是将来的日期
- @Max:必须是数字,且值要小于或者等于给定的值
- @Min:同上,大于给定的值
- @NotNULL:不能为空
- @NUll:必须为null
- @Past:必须是过去的日期
- @Pattern:必须匹配正则表达式
- @Size:值必须是String、集合、数组,且长度符合要求
用法如下
package spitter;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
public class Spittle {
private Long id;
@NotNull(message = "不能为空")
@Size(min=5,max = 16,message = "不能超过5-16")
private String message;
@NotNull
@Size(min=5,max=25,message = "不能超过5-25")
private String time;
@NotNull
@Size(min=2,max=30,message = "不能超过2-30")
private String latitude;
@NotNull
@Size(min=2,max = 30,message ="不能超过2-30")
private String longitude;
public Spittle(Long id, String message, String time, String latitude, String longitude) {
this.id = id;
this.message = message;
this.time = time;
this.latitude = latitude;
this.longitude = longitude;
}
public Spittle() {
}
public Spittle(String message, String time) {
this.message = message;
this.time = time;
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public String toString() {
return "Spittle{" +
"id=" + id +
", message='" + message + '\'' +
", time='" + time + '\'' +
", latitude='" + latitude + '\'' +
", longitude='" + longitude + '\'' +
'}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getLatitude() {
return latitude;
}
public void setLatitude(String latitude) {
this.latitude = latitude;
}
public String getLongitude() {
return longitude;
}
public void setLongitude(String longitude) {
this.longitude = longitude;
}
}
@RequestMapping(value = "registry",method = RequestMethod.POST)
public String processRegistration( @Valid Spittle spittle, Errors error){
System.out.println(spittle);
if(error.hasErrors()){
System.out.println(error.getAllErrors());
return "form";
}
return "home";
}
建议不要使用高版本,我使用6.0.9报错,版本兼容报错,显示初始化失败,换成5.3版本以后正常执行
使用Apache Tiles视图定义布局
如果需要对每一个布局添加头部和尾部,常规做法是为每一个jsp模版设置头部和尾部,这样明显扩展性不好且增加日后维护成本,所以就需要布局引擎Apache Tiles
没什么卵用,多了些莫名其妙的配置,实际也就和一般的头部引用jsp,没什么差别
文件上传
Spring的文件上传比较简单,只要在Spring 中注册一个文件上传的解析器就行了,Spring中有两个解析器
- ComonsMultipartResolver:通用
- StandardServletMultipartResolver:适用于servlet3.0以后
基本用法
- 在Spring配置文件中实例化一个上传解析器
- 接收用MultipartFile
使用java配置的一般例子
在主配置类中重写方法
/**
* 设置上传文件的临时文件
* @param registration
*/
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
//设置零时文件夹,以及其他相关设置
registration.setMultipartConfig(new MultipartConfigElement("/Users/f7689386/PycharmProjects",2097152,4194304,0));
}
Controller写法
public String processRegistration(@RequestPart("profilePicture")MultipartFile profilePicture){
//获取名字
System.out.println(profilePicture.getOriginalFilename());
return "home";
}
MultipartFile接口相关方法
public abstract interface MultipartFile {
String getName();
String getOriginalFilename();
String getContentType();
boolean isEmpty();
long getSize();
byte[] getBytes() throws java.io.IOException;
InputStream getInputStream() throws java.io.IOException;
void transferTo(File arg0) throws IOException,IllegalStateException;
}
定义异常通知切面
如果有多个控制器抛出同样的异常,要对每一个方法都要进行处理跳转到error页面,这样会有大量存在代码,可以抽取成一个切面,对每一个抛出该异常的方法 作为一个切点
在SpringMVC中,可以给类添加@ControllerAdvice代表这个类是一个通知类,@ExceptionHandler代表这个方法是一个异常通知方法
java
@ControllerAdvice
public class AppWideExceptionHandler{
@ExceptionHandler(DuplicateSprittleException.class)
public String duplicateSplittleHandler(){
return "error";
}
}
上面这个类代表如果有某个类抛出DulicateSprittleException异常,则执行上面那个方法,其中ControllerAdvice里面包含的@Component,所以在启动的时候它也会被扫描进来被Spring管理
重定向传递数据
当用户提交post请求以后,如果不使用重定向跳转而使用请求转发,那么在刷新页面或者后退可能会产生多次提交数据等危险操作,但是在SpringMVC中Model的生命周期都是一次请求,在重定向model存储的数据都会消失
有个方案是把需要访问的数据存储在会话Session中,Spring也认为这是一个不错的方式,但是Spring认为我们不需要管理这些数据,Spring提供了RedirectAttributes设置属性,在跳转到页面以后数据消失,类似一次请求存储的数据
@RequestMapping(value = "redirect",method = RequestMethod.GET)
public String redirect(RedirectAttributes model){
model.addFlashAttribute("a","a");
return "redirect:/spitter/(username)";
}
如果传递的是一个变量名,还可以不设置key,他会默认以变量名作为key
网友评论