您的位置:首页 > 编程语言 > Java开发

SpringBoot系列(3)---无配置文件SpringMVC

2017-07-07 16:24 806 查看
之前我在写SpringMVC花了很多篇幅,所以很多关于SpringMVC的很多知识点可以参考我以前的笔记。

Servlet3.0以上可以不使用web.xml进行配置,而是实现Spring提供的WebApplicationInitializer接口进行相关的Web配置。当然我们的web服务器也需要时tomcat7.x以上版本。

现在我们首先看看我们这个实验环境的maven pom文件:

<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>com.tony</groupId>
<artifactId>AnnotationSpringmvc</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>AnnotationSpringmvc Maven Webapp</name>
<url>http://maven.apache.org</url>

<repositories>
<repository>
<id>io.spring.repo.maven.release</id>
<url>http://repo.spring.io/release/</url>
</repository>
<repository>
<id>maven-eu.nuxeo.org</id>
<url>https://maven-eu.nuxeo.org/nexus/content/repositories/public-releases/</url>
</repository>
</repositories>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.9.RELEASE</spring.version>
</properties>

<dependencies>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>

<!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/javax.servlet/jsp-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>AnnotationSpringmvc</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<uriEncoding>UTF-8</uriEncoding>
<server>tomcat8spring</server>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>


先来回顾一下SpringMVC在web.xml的配置是什么样的,我们需要使用Spring提供的DispatcherServlet对所有请求进行拦截。具体的配置如下:

<servlet>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

当然我们今天的目标不是使用XML进行配置而且使用WebInitializer。以下代码就是关于WebInitializer的具体实现

public class WebInitializer implements WebApplicationInitializer {

public void onStartup(ServletContext servletContext) throws ServletException {

AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(ApplicationConfig.class);
context.setServletContext(servletContext);

ServletRegistration.Dynamic servlet = servletContext.addServlet("springMvc",new DispatcherServlet(context));
servlet.addMapping("/");
servlet.setLoadOnStartup(0);

}
}

上面的代码主要完成两件事:

1、在onStartup方法当中,创建Spring ApplicationContext 并 注册到相关的配置类

2、创建一个Servlet,这个Serlvet正是我们平时使用XML上配置的DispatcherServlet。在addMapping方法中,我们选择拦截所有请求。

当然也少不了我们的配置类。在配置类当中我们最重要的是,需要配置一个viewResolver,熟悉SpringMVC的同学应该也知道,一般情况下都是用InternalResourceViewResolver,所以同理我们也需要建立一个方法去创建InternalResourceViewResolver:

@Configuration
@EnableWebMvc
@ComponentScan("com.tony")
public class ApplicationConfig {

@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
viewResolver.setOrder(99);
return viewResolver;
}
}


然后我们来规矩,创建一个Controller,随随便便映射到一个JSP当中去。我已经在WEB-INF/views目录下创建了一个index.jsp。所以下面的controller非常简单做一下跳转即可。

@Controller
public class IndexController {
@RequestMapping("/index")
public String index(){
return "aaa";
}
}


OK!现在估计已经完成了最基本的SpringMVC的搭建了,其他拦截器配置、JSON输出配置、静态资源过滤等我们一个个来说。

一、静态资源

以前我们在Sping静态资源有两种方法

1、 <mvc:default-servlet-handler /> 自动识别静态资源

2、使用mvc:resources配置静态支援映射

以下就是Spring无配置文件的静态资源映射的方法,需要注意的是,我们的配置类需要继承WebMvcConfigurerAdapter类。WebMvcConfigurerAdapter提供非常多的方法,其中有一个方法是public void addResourceHandlers(ResourceHandlerRegistry registry),只要我们在配置类重写这个方法即可:

@Configuration
@EnableWebMvc
@ComponentScan("com.tony")
public class ApplicationConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
viewResolver.setOrder(99);
return viewResolver;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");
}
}


可以看到我们的资源位置为classpath:/assets/ 其实际位置就是resources目录下的assets,当然也可以按照以前常规的讨论直接写上/assets/即可。记得我在之前SpringMVC的笔记当中写了关于动态资源目录防止旧资源被客户端缓存导致新资源无法及时获取的问题,在代码层面上也非常简单。详细可以看看我以前的笔记:SpringMVC
静态资源&拦截器(interceptor and static resource) 笔记(七)

二、拦截器interceptor

跟常规一样,都是实现 HandlerInterceptor 

public class LoginInterceptor implements HandlerInterceptor {

public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println(">>>>>>>>>>>>>>>>"+o.getClass().getName());
String uri = httpServletRequest.getRequestURI();
if(!(uri.contains("/index")  || uri.contains("/login"))) {
User user = (User) httpServletRequest.getSession().getAttribute("loginUser");
if (user == null) {
httpServletResponse.sendRedirect("index");
return false;
}
}
return true;
}

public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

}

public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

}
}

上述代码主要实现了session验证拦截,如果除了访问index和login两个uri 之外都需要验证session。如果验证失败则redirect跳转到index当中。

同样的我们在SpringMVC配置上添加interceptor也是通过在配置类中复写WebMvcConfigurerAdapter的方法,这次我们是重写addInterceptors方法,将我们写的LoginInterceptor添加到registry当中。

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor());
}


三、addViewControllers方法

我们已经写了很多个没有任何业务的controller方法了,事实上我们可以在WebMvcConfigurerAdapter重写addViewControllers方法。

@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("/index");
registry.addViewController("/userinfo").setViewName("userinfo");
}

等价于

@Controller
public class IndexController {
@RequestMapping("/index")
public String index(){
return "index";
}


四、全局配置

在我之前的关于SpringMVC的笔记当中写了很多关于controller类的全局配置,例如:

1、@ExceptionHandler  异常处理

2、@ModelAttribute 我都不知道怎么描述这个ModelAttribute?标记了@ModelAttribute的方法会为每一个属于同一个Controller类的方法添加添加属性

3、@InitBinder 这个我也不熟悉,我也是看书看到的。标记了@InitBinder的方法会在参数中注入一个WebDateBinder对象,详细可以看看这个类的API,(随后我还是看看WebDateBinder和DataBinder的源码发现内容还是很多的,里面有很多不少的功能,有时间的同学可以研究一下,并在评论分享出一些有用的方法~)

但是这次比较特别,我们通过@ControllerAdvice,为所有controller添加全局的配置。


@ControllerAdvice
public class GlobalHandlerAdvice {

//当前我选择拦截所有Exception
@ExceptionHandler(value = Exception.class)
public ModelAndView exception(Exception exception, WebRequest request){
//全局异常配置,如果有controller发生了异常。跳转到error 并添加errorMessage的VieModel属性
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("errorMessage",exception.getMessage());
return modelAndView;
}

@ModelAttribute
public void addAttributes(Model model){
//为全部controller的ViewModel添加msg参数
model.addAttribute("msg","other message from GlobalHandlerAdvice !!!");
}

@InitBinder
public void initBinder(WebDataBinder webDataBinder){
//拦截otherMsg的request参数,在任何controller方法中读取otherMsg参数将为null
webDataBinder.setDisallowedFields("otherMsg");
}

}


五、文件上传

说到文件上传,就是创建一个CommonsMultipartResolver。和我们以前配置XML一样,在配置类创建一个multipartResolver的bean。

@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(10000000);
return resolver;
}

然后就没有什么好说的就是写个controller的方法接收multipartFile参数就可以了,还是贴一下代码吧···

@RequestMapping("/uploadAction")
public ModelAndView uploadFile(@RequestParam(name = "file") MultipartFile file,HttpSession session) throws IOException, URISyntaxException {
ModelAndView modelAndView = new ModelAndView();
if (file == null || file.isEmpty()) {
String fileType = file.getContentType().toLowerCase();
if (!(fileType.contains("jpg") || fileType.contains("jpeg"))) {
modelAndView.setViewName("redirect:upload");
}
} else {
//获得classPath
String targetFilePath = ResourceUtils.getFile("classpath:assets/").getPath();
//获得项目路径
//            String targetFilePath = session.getServletContext().getRealPath("/");
System.out.println(">>>>>>>"+targetFilePath);
File targetFile = new File(targetFilePath+File.separator+file.getOriginalFilename());
FileCopyUtils.copy(file.getBytes(), targetFile);
modelAndView.setViewName("redirect:assets/" + file.getOriginalFilename());
}
return modelAndView;
}


六、HttpMessageConvert

关于HttpMessageConvert 我之前花了一篇笔记来介绍,想重温一下HttpMessageConvert的知识可以到这里:SpringMVC之HttpMessageConverter&RestTemplate笔记(二)

笔者在之前的笔记也写过,默认Spring已经将我们常用的HttpMessageConvert都加载了,但是在使用Spring4.X这种无配置文件的SpringMVC下 使用我之前笔记上的com.fasterxml.jackson.core 的2.1.4版本时,出现 java.lang.ClassNotFoundException: com.fasterxml.jackson.core.util.DefaultIndenter 的错误。这个错误是由于jackson的版本问题所导致的,所以我这里使用了Jackson的2.5.1版本

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.1</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.5.1</version>
</dependency>


当然这个Json输入输出没有什么好说的,因为本来SpringMVC已经帮我们加载了非常多的Convert了。其实笔者也没有怎么在工作中定义convert,老说我也不是很记得怎么定义了。所以在回忆一下吧····

主要我们需要继承AbstractHttpMessageConverter 然后实现下面的方法,主要是三个方法

1、支付那些class的转换

2、输入转换(String->User)

3、输出转换 (User - > String)

注意:这里是inputstream就是说可以二进制流转换都可以,我是转成String再去转换而已。你完全可以通过序列化的方式去转换~

public class MyConvert extends AbstractHttpMessageConverter<User> {

public MyConvert() {
super(new MediaType("application","x-tony"));
}

//检查是否支持目标转换,这里只支持User类型
protected boolean supports(Class<?> clazz) {
return User.class.isAssignableFrom(clazz);
}

//转换成对象
protected User readInternal(Class<? extends User> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
String inputString = StreamUtils.copyToString(inputMessage.getBody(), Charset.forName("utf-8"));
String[] targetArray = inputString.split("#");
User user = new User(targetArray[0],targetArray[1]);
return user;
}

//对象转成字符串
protected void writeInternal(User user, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
String outputString = user.getUsername() + "#" + user.getUserpwd();
outputMessage.getBody().write(outputString.getBytes());
}

}


当然也和我们平时使用XML配置一样,也需要添加我们自定义的MessageConvert,在我们的配置类当中重写extendMessageConverters,并定义我们MyConvert的bean



@Bean
public MyConvert myConvert(){
return new MyConvert();
}

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(myConvert());
}

最后添加一个测试的Controller方法:

@RequestMapping(value = "/testConvert.tony",produces = {"application/x-tony"})
public @ResponseBody User testMessageConvert(@RequestBody User user){
System.out.println("user:"+user);
return user;
}


需要注意在测试的时候一定要将content-type改成你们定义的,我这里不要脸的定义成application/x-tony,请求的http 头为:

POST /AnnotationSpringmvc/testConvert.tony HTTP/1.1

Content-Type: application/x-tony

Host: localhost:8080

Connection: close

User-Agent: Paw/3.0.12 (Macintosh; OS X/10.12.5) GCDHTTPRequest

Content-Length: 12

user#tonypwd
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息