SpringMVC
核心流程
1.客户端发送请求
2.DispatcherServlet统一接收请求,需要维护一个Spring容器(ApplicationContext),使用init方法完成ApplicationContext的初始化,首先需要看servletContext中是否已有容器,如果有就直接用,如果没有就初始化(有可能会在listener中维护),容器维护Controller组件,这些组件中包含Handler方法
3.访问到doGet和doPost方法,经过doService然后再合并到doDispatch中
4.由doDispatch来获得url,通过HandlerMapping建立映射关系
5.经过HandlerAdapter,最终执行到HandlerMethod(容器中的组件的方法)
init方法
public final void init() throws ServletException {
this.initServletBean();
}
protected final void initServletBean() throws ServletException {
try {
this.webApplicationContext = this.initWebApplicationContext();
}
}
WebApplicationContext也是容器,是ApplicationContext的子接口;还增加对Web应用的支持,在WebApplicationContext中可以使用ServletContext
doDispatch
分发请求
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.processRequest(request, response);
}
protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doService(request, response);
}
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
this.doDispatch(request, response);
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerExecutionChain mappedHandler = null;
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
// HandlerMapping通过url获得对应的Handler方法
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
// 适配器HandlerAdapter
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行Handler方法 → 根据HandlerMapping的执行结果去执行的
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
// 处理执行后的结果
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
创建一个完整的SpringMVC应用
引入依赖
spring-web、spring-webmvc、spring相关依赖(5+1)
servlet-api(provided)
jackson-annotations、core、databind(json)
只需要引入标红的即可
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0-alpha-1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
配置DispatcherServlet
<?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>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--全局(根路径下的jsp → 类似于html 👉 会将jsp编译为Servlet)-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
还需要提供Spring的配置文件所在的位置
public void setContextConfigLocation(@Nullable String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
在servlet标签中使用init-param标签 👉 set方法
<init-param>
<!--set方法是谁 👉 setContextConfigLocation-->
<param-name>contextConfigLocation</param-name>
<!--set方法的形参是什么-->
<param-value>classpath:application.xml</param-value>
</init-param>
配置文件
<context:component-scan base-package="com.cskaoyan"/>
<!--springmvc的注解驱动 👉 类型转换、参数校验、Json-->
<mvc:annotation-driven/>
运行流程
访问hello请求进入了helloController里的hello方法
我们打断点查看doDispatch方法的流程,断点位置如下
搭建应用的步骤
1.pom.xml→3行依赖
2.web.xml→创建模板
3.application.xml→创建模板
常见的依赖:
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0-alpha-1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
</dependencies>
@RequestMapping
建立URL和Handler方法之间的映射关系
与EE中的Servlet类似,可以处理分发过来的请求
// 可以窄化请求,可以写在类上或方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
// value属性是字符串数组,可以映射多个URL,也可以使用通配符
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
// 请求方法限定 → 关系是or,引申@GetMapping、@PostMapping
RequestMethod[] method() default {};
// 请求参数限定 → 关系是and
String[] params() default {};
// 请求头限定 → 关系是and
String[] headers() default {};
// Content-Type请求头的值 → 关系是and
String[] consumes() default {};
// Accept请求头的值 → 关系是and
String[] produces() default {};
}
属性值中有s的代表的就是and的关系
***URL路径映射 value属性 → String[]
value属性是字符串数组
多个URL映射到同一个Handler方法上
/index 、/home
/hello、/helloworld
//注解的属性值为数组,如果数组中只有一个值,可以省略掉{}
@RequestMapping({"hello", "helloworld"})
@ResponseBody
public String hello() {
return "hello world 2.0";
}
窄化请求
将请求中共有的部分提取到类上,达到窄化的效果
/user/login
/user/register
/user/modify
最终方法中映射的url就是:类上的@RequestMapping的value属性+方法上的@RequestMapping的value属性
分隔符会自动补充
@Controller
@RequestMapping("user")
public class UserController {
//@RequestMapping("user/login")
@RequestMapping("login") // → user/login
@ResponseBody
public String login() {
return "login";
}
}
使用通配符*
/goodbye/songge
/goodbye/ligenli
/goodbye/xueqie
/goodbye/nanfeng
/goodbye/*
@RequestMapping("goodbye/*")
@ResponseBody
public String goodbye() {
return "byebye";
}
请求方法限定 method属性 → RequestMethod[]
限定了Handler方法只能处理特定的请求方法的请求
两个方法之间是or的关系
@Controller//("method")
@RequestMapping("method")
public class MethodController {
@RequestMapping(value = "get",method = RequestMethod.GET) //method/get
@ResponseBody
public String methodGet() {
return "Method GET";
}
@RequestMapping(value = "post",method = RequestMethod.POST)
@ResponseBody
public String methodPost() {
return "Method POST";
}
//增加两个值,这两个值之间的关系 → or → 也就是满足其中一项即可
@RequestMapping(value = "double",method = {RequestMethod.GET,RequestMethod.POST})
@ResponseBody
public String methodDouble() {
return "Method double";
}
}
引申注解
@GetMapping、@PostMapping
增强了方法限定的功能
@RequestMapping(
method = {RequestMethod.GET}
)
public @interface GetMapping {}
@RequestMapping(
method = {RequestMethod.POST}
)
public @interface PostMapping {}
使用注解改造
//@RequestMapping(value = "get",method = RequestMethod.GET) //method/get
@GetMapping("get")
@ResponseBody
public String methodGet() {
return "Method GET";
}
//@RequestMapping(value = "post",method = RequestMethod.POST)
@PostMapping("post")
@ResponseBody
public String methodPost() {
return "Method POST";
}
请求参数限定 params属性 → String[]
限定的是请求参数要有哪一些,只能多,不能少
//params → 多个值是and的关系,全都要
@RequestMapping(value = "register",params = {"username","password"})
@ResponseBody
public String register() {
return "register";
}
如果状态码出现400,那么一定要检查请求参数的封装有没有出问题
请求头限定 headers属性 → String[]
限定的是请求头要有哪一些
//多个值之间的关系是and,全都要
@RequestMapping(value = "limit",headers = {"aaa","bbb"})
@ResponseBody
public String headerLimit() {
return "header limit";
}
如果不符合要求,这里报的是404
consumes → Content-Type
限定的值Content-Type这个请求头对应的值,值的格式是xxx/xxx
@RequestMapping(value = "consumes", consumes = "aaa/bbb")
@ResponseBody
public String contentTypeLimit() {
return "ContentType limit";
}
produces → Accept
限定的Accept这个请求头对应的值,值的格式是xxx/xxx
@RequestMapping(value = "produces", produces = "ccc/ddd")
@ResponseBody
public String acceptLimit() {
return "Accept limit";
}
Handler方法的返回值
ModelAndView
单体应用中会去使用的结果:里面包含了视图信息,以及数据信息
@RequestMapping("hello") //没有写@ResponseBody
public ModelAndView hello() {
//想要访问webapp路径下hello.jsp 并且给里面的username赋值
ModelAndView modelAndView = new ModelAndView();
// 通过ModelAndView要设定访问视图名为hello.jsp
modelAndView.setViewName("/hello.jsp");
// 通过ModelAndView要设定username对应的值
modelAndView.addObject("username", "songge");
return modelAndView;
}
String
没有@ResponseBody:把返回值的字符串作为ModelAndView的viewName
有@ResponseBody:直接响应字符串
//返回值字符串作为视图名
@RequestMapping("hello2")
public String hello2(Model model) {
//相当于
//ModelAndView modelAndView = new ModelAndView();
//modelAndView.setViewName("/hello.jsp");
model.addAttribute("username", "ligenli"); //和上面的hello方法做的事情是一样的
return "/hello.jsp";
}
***响应Json
- jackson依赖
- mvc:annotation-driven
- Handler方法返回值写为需要转换为Json字符串的实例,即提供一个对象,@ResponseBody
这里要注意:返回的实例要提供无参构造方法和get\set方法
@RequestMapping("hello/json")
@ResponseBody
public User helloJson() {
User user = new User();
user.setUsername("松哥");
user.setPassword("李艮隶");
return user;
}
引申注解:@RestController = @ResponseBody + @Controller
意味着该类下所有的方法响应的都是Json(该类不响应视图)
//@Controller
//@ResponseBody
@RestController
public class JsonController {
}
这里会由Handler自动将实例转换为json数据
Handler方法的形参
主要做的是请求参数的封装,请求参数使用Handler方法的形参来封装
***请求参数接收
直接接收
在形参中直接接收:请求的参数名必须和Handler方法的形参名一致
字符串
任何参数都可以直接用字符串来接收
// 直接接收请求参数
@RequestMapping("register")
public BaseRespVo register(String username,String password,String age,String gender) {
return BaseRespVo.ok(username + ":" + password);
}
基本类型和对应的包装类
直接写在形参中即可,MVC提供了类型转换器
// 直接接收请求参数
// 在形参中可以直接使用基本类型,或者其包装类来接收 String → 你接收的类型
// SpringMVC给我们提供了类型转换器
@RequestMapping("register2")
//public BaseRespVo register2(String username,String password,int age,String gender) {
public BaseRespVo register2(String username,String password,Integer age,String gender) {
//
return BaseRespVo.ok(username + ":" + password + ";age = " + age);
}
接收这些值建议使用包装类来接收,避免接收的过程中出现null值的转换错误
数组
即多个同名的参数,在接收的时候可以用一个数组来接收
localhost:8080/user/register3?username=songge&password=ligenli&age=28&gender=male
&hobbys=sing&hobbys=dance&hobbys=rap&hobbys=basketball
// 接收数组 → 请求参数名和Handler方法的形参名一致,数组的类型根据你的参数的类型选择
@RequestMapping("register3")
public BaseRespVo register3(String username,String password,
Integer age,String gender,
String[] hobbys) {
//
return BaseRespVo.ok(hobbys);
}
Date日期
&birthday=1991-06-13
// 接收日期 → 请求参数名和Handler方法的形参名一致
@RequestMapping("register4")
public BaseRespVo register4(String username, String password,
Integer age, String gender,
String[] hobbys, Date birthday) {
return BaseRespVo.ok(hobbys);
}
自动提供的日期类型转换器只支持yyyy/MM/dd的格式
&birthday=1991/06/13
@RequestMapping("reg3")
public BaseRespVo reg3(Date date){
return BaseRespVo.ok(date);
}
SpringMVC提供的类型转换器
// 接收日期 → 请求参数名和Handler方法的形参名一致
//指定接收的日期格式
//birthday=1991-06-13
@RequestMapping("register5")
public BaseRespVo register5(String username, String password,
Integer age, String gender,
String[] hobbys,
@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday) {
return BaseRespVo.ok(hobbys);
}
自定义的类型转换器
1.写一个自定义的类型转换器
提供了接口,直接实现即可
//第一个泛型:作为convert方法的形参类型;第二个泛型作为convert方法的返回值类型
//其实做的就是一个由S类型转换为T类型的转换器
//在convert方法中就要写自定义的转换业务
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S var1);
}
// 接收日期
//自定义的类型转换器 → 请求参数名和Handler方法的形参名一致,来看类型转换器列表里是否包含形参类型的转换器
//birthday=1991-06-13
@Component
public class String2DateConvert implements Converter<String, Date> {
@Override
public Date convert(String s) {
SimpleDateFormat simpleDateFormat = null;
Date date = null;
if (s.contains("-")&&s.length()==10){
simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
}else if (s.length()==19){
simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}else {
simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
}
try {
date = simpleDateFormat.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
2.类型转换器的配置
<!--conversionService还要提供给SpringMVC-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--可以使用注解来注册这个组件-->
<!--<bean id="string2DateConverter" class="com.cskaoyan.converter.String2DateConverter"/>-->
<!--注册一个组件conversionService → 提供新增的converter-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<!--注册局部组件-->
<!--<bean class="com.cskaoyan.converter.String2DateConverter"/>-->
<!--引用全局组件 bean属性引用组件id-->
<ref bean="string2DateConverter"/>
</set>
</property>
</bean>
3.@JsonFormat
将返回值标记为需要的日期格式
@JsonFormat(pattern = "yyyy-MM-dd")
T data;
写需要作为返回数据的属性上,同时也需要使用pattern属性。
要注意这里是返回参数,要和接收进行区别。这里转换也只会转换date类型,不会影响其他类型的数据的转换
文件 MultipartFile
文件上传
MultipartFile提供了一个方法 → 保存文件到指定位置
1.引入依赖commons-io、commons-fileupload
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
2.注册MultipartResolver组件
<!--组件id为固定值,不能写为其他值-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="512000000"/> //这里是限制文件上传的大小
</bean>
3.构造一个文件上传的请求,并且接收文件
<form action="upload/file" method="post" enctype="multipart/form-data">
<%--name属性值就是请求参数名--%>
<input type="file" name="file"><br>
<input type="submit">
</form>
4.提供一个Handler方法,接收上传的文件参数
// 类型使用MultipartFile,形参名要和请求参数名一致
@RequestMapping("file")
public BaseRespVo uploadFile(MultipartFile file) throws IOException {
//保存在指定位置
// transferTo方法的形参提供的就是保存的位置以及名字
//如果这个destFile已经存在,会覆盖 → 解决这个问题,就是new一个不存在的file
// → 使用一个不会重复的文件名 UUID
//也可以做这样一件事:上传的时候是啥名字,就以啥名字来接收
String originalFilename = file.getOriginalFilename();//获得原始文件名
String name = file.getName(); //获得是请求参数名 → 这里是file,也就是形参名
String contentType = file.getContentType(); //正文类型 → 文件类型
long size = file.getSize(); //文件大小
//InputStream inputStream = file.getInputStream(); //输入流
File destFile = new File("D:\\tmp", originalFilename);
file.transferTo(destFile);
return BaseRespVo.ok();
}
//上传多个文件
@RequestMapping("files")
public BaseRespVo uploadFile(MultipartFile[] files) throws IOException {
//可以通过遍历的方式保存起来
for (MultipartFile file : files) {
String originalFilename = file.getOriginalFilename();
File destFile = new File("D:\\tmp", originalFilename);
file.transferTo(destFile);
}
return BaseRespVo.ok();
}
注意
不管接收到的是什么类型的参数,请求参数名必须和Handler方法的形参名一致
JavaBean
将请求参数封装到JavaBean的实例中,相当于是将形参名写成了JavaBean中的属性名,这时候也要注意属性名与请求参数名必须一致
并且需要提供无参构造和set方法,因为转换工具是通过set方法来赋值的
@Data
public class User {
// 原先Handler方法的形参变更为了成员变量
// 成员变量的类型和原先在Handler方法的形参中的写法是一样的
String username;
String password;
Integer age;
String gender;
String[] hobbys;
//@DateTimeFormat(pattern = "yyyy-MM-dd")
Date birthday;
}
// 以JavaBean来接收请求参数 → 请求参数名和JavaBean的成员变量名(set方法)一致
@RequestMapping("register7")
public BaseRespVo register7(User user) {
//以JavaBean来进行接收 → 实际上使用的JavaBean的无参构造方法和set方法来封装的
//User user1 = new User();
//user1.setUsername();
//user1.setPassword();
return BaseRespVo.ok();//做baseRespVo实例转换为字符串的过程中
}
接收方式的选择
有些情况下直接用形参来接收,有些情况下用JavaBean来接收,也有可能两者都用
混用
一部分参数是经常使用的,一部分参数使用不频繁
@RequestMapping("user/query")
public BaseRespVo queryUsers(Integer age, String gender, PageInfo pageInfo){
return BaseRespVo.ok();
}
@Data
class PageInfo{
Integer page;
Integer limit;
}
继承JavaBean
如果需要增加请求参数,且这部分也是常有的,在不修改原来的代码的情况下,可以使用一个新的JavaBean来继承原来的对象
@RequestMapping("user/query")
public BaseRespVo queryUsers( UserPageInfo pageInfo){
return BaseRespVo.ok();
}
// 改编方式1
@Data
class UserPageInfo{
Integer age;
String gender;
Integer page;
Integer limit;
}
// 改编方式2
@Data
class UserPageInfo extends PageInfo{
Integer age;
String gender;
}
Json
Json请求
请求方法:Post
Content-Type: application/json
data: Json字符串
构造Json请求
使用Postman来构造
引入依赖
jackson
JavaBean或Map来接收
//{"username":"admin123","password":"admin123"}
@RequestMapping("login")
//public BaseRespVo login(JsonUser user) {//localhost:8080/json/login?username=admin123&password=admin123
public BaseRespVo login(@RequestBody JsonUser user) {
return BaseRespVo.ok();
}
@RequestMapping("login2")
public BaseRespVo login2(@RequestBody Map user) {
return BaseRespVo.ok();
}
场景
使用Map来接收,主要场景是参数比较少。参数类型不容易固定
其他场景都用JavaBean
@RequestMapping("login2")
public BaseRespVo login2(@RequestBody Map user) {
Object age = user.get("age");
//age究竟是什么类型呢?
return BaseRespVo.ok();
}
其他参数
Request/Response
如果你需要使用request和response可以直接放到Handler方法的形参中,可以直接使用,原因是会经过doGet或doPost,这个时候就会拿到request和response
//localhost:8080/other/hello?username=songge
@RequestMapping("hello")
public BaseRespVo hello(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter("username");
return BaseRespVo.ok(username);
}
Model
Handler方法的返回值为字符串,如果没有增加@ResponseBody,意味着要处理ModelAndView,返回值字符串作为视图名,Model直接写在Handler方法的形参上
Cookie
不能直接放在形参中,可以通过request获得cookie
@RequestMapping("cookie")
public BaseRespVo cookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
System.out.println("cookie:" + cookie.getName() + " → " + cookie.getValue());
}
return BaseRespVo.ok();
}
通过浏览器构造cookie
postman构造cookie
Session
session也可以通过request来获得;另外session可以直接写在形参中 → HttpSession
//localhost:8080/other/session/put?username=songge
@RequestMapping("session/put")
public BaseRespVo put(HttpServletRequest request,String username) {
HttpSession session = request.getSession();
session.setAttribute("username", username);
return BaseRespVo.ok();
}
//localhost:8080/other/session/get
@RequestMapping("session/get")
public BaseRespVo get(HttpSession session) {
Object username = session.getAttribute("username");
return BaseRespVo.ok(username);
}
RESTful
表述性状态传递Representational State Transfer
使用Json数据做交互,更侧重于响应
建议大家请求URL:资源(名词)+操作(动词)
/user/query
/user/insert
/user/update
/user/delete
注解:获得值给到Handler方法的形参,这些值是和请求相关的
即使用注解完成相关的编程流程
请求URL→ @PathVariable
localhost:8080/article/details?username=xxx&id=xxx
提供占位符,一个占位符在article之前,一个占位符在details之后;占位符写{xxx},xxx是占位符的名称
这样就可以根据传入的参数来动态的生成url,类似于vue中的插值表达式,{}中的内容是和注解对应的属性绑定的
//localhost:8080/article/details
//提供占位符,一个占位符在article之前,一个占位符在details之后;占位符写{xxx},xxx是占位符的名称
//使用的是@PathVariable注解的value属性来获得对应占位符的值
@RequestMapping("{aaa}/article/details/{bbb}")
public BaseRespVo articleDetails(@PathVariable("aaa")String username,
@PathVariable("bbb")Integer id) {
return BaseRespVo.ok(username + ":" + id);
}
根据userid做用户信息的查询的url → /user/query/5
@RequestMapping("query/{id}")
public BaseRespVo queryUser(@PathVariable("id") Integer id){
User user = userMapper.selectUserById(id);
return BaseRespVo.ok(user);
}
请求参数 → @RequestParam
无用
//@RequestParam注解的value属性和形参名对应起来,形参就可以接收到对应的值
// 同时会限定要携带对应的请求参数
//而这个过程我们直接让形参名和请求参数名一致就可以做到
//localhost:8080/user/login?username=songge&password=niupi
@RequestMapping("user/login")
public BaseRespVo login(@RequestParam("username") String usernamex,
@RequestParam("password") String passwordy) {
return BaseRespVo.ok();
}
请求头 → @RequestHeader
获得指定请求头的值
指定的冒号左边的部分,获得是冒号右边的部分
//可以通过String或String[]来接收对应的值
//如果以数组来接收,其实就是将字符串根据逗号作为分隔符转换数组
@RequestMapping("header")
public BaseRespVo header(@RequestHeader("Accept")String[] accept,
@RequestHeader("Host") String host) {
//String usernamesString = "songge,ligenli,xuejia";
//String[] split = usernamesString.split(",");
return BaseRespVo.ok();
}
注解value属性的值需要与请求头对应
Cookie → @CookieValue
如果要获得指定name所对应的value,你要拿到所有的Cookie去遍历
可以通过注解直接拿到指定name所对应的value
@RequestMapping("cookie/value")
public BaseRespVo cookieValue(@CookieValue("songge")String value) {
return BaseRespVo.ok(value);
}
Session → @SessionAttribute
直接拿到指定attributeName对应的value
//放入的时候放入的是什么类型,接收的时候就是什么类型
//简化的是session.getAttribute
@RequestMapping("session/get")
public BaseRespVo sessionGet(@SessionAttribute("username")String username) {
return BaseRespVo.ok(username);
}
静态资源处理
在整合了MVC之后,web资源路径下的静态资源就无法正常访问了
原因:静态资源本应该由default的Servlet来处理,但在SpringMVC中都会交给DispatcherServlet来处理,由于没有写对应的处理静态资源的Servlet,就会造成访问不到的情况
解决的办法:
-
配置default servlet
-
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern> </servlet-mapping>
-
-
使用DispatcherServlet给出的默认配置
-
x <mvc:default-servlet-handler/>
-
以上两种方式只能访问到web资源根路径下的静态资源,不能访问其他地方的,有局限性
-
-
静态资源映射
-
<!--ResourceHandler的配置--> <!--mapping属性:映射的url--> <!--location属性:文件所处的真实路径--> <!--访问静态资源的请求URL:分为了两部分,mapping的属性值 + 静态资源相对于location的位置--> <mvc:resources mapping="/pic/**" location="/"/><!--web资源根路径--> <mvc:resources mapping="/pic2/**" location="classpath:/def/"/><!--classpath路径--> <mvc:resources mapping="/pic3/**" location="file:D:/spring/"/><!--文件路径,建议大家使用的路径--> <!--注意事项:location最后位置是/-->
-
前面的/pic类似于替代后面location中的目录,接下来只需要写相对于location目录下的资源路径即可
示意图
上传并访问
使用配置文件来管理文件相对路径,便于管理
<context:property-placeholder location="classpath:param.properties"/>
加载上传的驱动,使用配置文件配置路径映射
<mvc:resources mapping="/pic4/**" location="file:${file.path}"/><!--文件路径,建议大家使用的路径-->
<!--注意事项:location最后位置是/-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
编写上传servlet
@RestController
@RequestMapping("upload")
public class uploadController {
@Value("${file.path}")
String path;
@RequestMapping("file")
public BaseRespVo upload(MultipartFile file) throws IOException {
file.transferTo(new File(path,file.getOriginalFilename()));
return BaseRespVo.ok();
}
}
异常处理
向上抛出异常,可以通过异常处理器进行统一的异常处理,也提供了个性化的处理,个性化的处理是根据异常的类型做个性化。
HandlerExceptionResolver
对异常进行统一的处理
接口,接口中提供了resolveException方法
范围:全部类型
如何生效:只需要注册到容器中就生效了
如果要做个性化的处理需要你自己来写业务
返回值:ModelAndView
如果需要返回Json,就需要调用response
public interface HandlerExceptionResolver {
/**
* var3: handler
* var4: 抛出的异常
*/
@Nullable
ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, @Nullable Object var3, Exception var4);
}
@Component
public class CustomHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("/exception.jsp");
if (e instanceof ArithmeticException){
modelAndView.setViewName("/arithmetic.jsp");
}
return modelAndView;
}
}
***@ExceptionHandler
注解,注解的value属性Class<? extends Throwable>[],写的是异常类的Class
范围:value属性中的异常类型
如何生效:ElementType.method,方法要处于ControllerAdvice组件中
返回值:ModelAndView、Json
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
Class<? extends Throwable>[] value() default {};
}
ModelAndView
直接返回String就是视图名
//@ExceptionHandler(value = {ArithmeticException.class})
/*@ExceptionHandler(ArithmeticException.class)
public ModelAndView arithmetic() {
ModelAndView modelAndView = new ModelAndView("/arithmetic.jsp");
return modelAndView;
}*/
@ExceptionHandler(ArithmeticException.class)
public String arithmetic(){
return "/arithmetic.jsp";//viewName
}
Json
增加@ResponseBody注解
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public BaseRespVo arithmetic() {
return BaseRespVo.fail("算术异常3.0");
}
@ResponseBody可以写在类上,意味着该类下所有的方法响应都是Json
引申注解:@RestControllerAdvice = @ResponseBody + @ControllerAdvice
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {}
//@ControllerAdvice
//@ResponseBody
@RestControllerAdvice
public class ExceptionControllerAdvice {
@ExceptionHandler(ArithmeticException.class)
//@ResponseBody
public BaseRespVo arithmetic() {
return BaseRespVo.fail("算术异常3.0");
}
}
可以将异常写在形参中,获取抛出异常的对象
@ExceptionHandler(ArithmeticException.class)
//@ResponseBody
public BaseRespVo arithmetic(ArithmeticException exception) {
//return BaseRespVo.fail("算术异常3.0");
return BaseRespVo.fail(exception.getMessage());//by zero
}
写业务时,可以自定义异常,通过抛出自定义的异常,做对应的异常处理,就可以响应对应的json数据
拦截器
filter
主要是解决Post请求乱码的问题
配置自带的CharacterEncodingFilter
它的作用就是判断forceRequestEncoding和forceResponseEncoding是否为true,如果为true则设置字符集为encoding对应的值
配置的过程
<!--Filter配置 class、映射范围-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--使用其set方法,用到了init-param标签-->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
<!--<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>-->
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<!--全部请求都来设置字符集-->
<url-pattern>/*</url-pattern>
</filter-mapping>
HandlerInterceptor
是一个拦截器接口,其中提供了3个方法
public interface HandlerInterceptor {
/**
* 返回值为boolean:返回值决定是否继续执行
* handler:HandlerMethod
*/
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
/**
* 是在Handler方法执行之后
* ModelAndView 获得Handler方法的执行结果
* 响应给用户之前
*/
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
/**
* 处理结果之后
* ex:可以获得Handler方法抛出的异常,即使抛出异常,也可以为true
* 如果当前的preHandle执行到了,并且呢结果为true,则一定可以执行到对应的afterCompletion
*/
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
HandlerInterceptor和Handler之间的关系是什么样子?
通过HandlerExecutionChain(链)来维护的,并且Handler和chain之间是一对一的关系
public class HandlerExecutionChain {
private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
private final Object handler;
@Nullable
private HandlerInterceptor[] interceptors;
@Nullable
private List<HandlerInterceptor> interceptorList;
}
HandlerExcutionChain如何来的 → doDispatch → getHandler → 是通过HandlerMapping来获得的
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping mapping = (HandlerMapping)var2.next();
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
获得HandlerExecutionChain,接下来做了啥事情
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerExecutionChain mappedHandler = this.getHandler(processedRequest);
//执行到了HandlerExecutionChain的applyPreHandle,如果返回值为false,则结束这个流程
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行的是Handler方法
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 在Handler方法执行之后执行的 → 并且第三个参数传入的Handler方法的执行结果mv
mappedHandler.applyPostHandle(processedRequest, response, mv);
//处理最终结果
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
finally{
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
如果applyPreHandle返回值为false,则结束这个流程,我们来看下它做了什么
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
//获得该Handler对应的多个HandlerInterceptor
HandlerInterceptor[] interceptors = this.getInterceptors();
//非空判断,如果为空,则直接return true
if (!ObjectUtils.isEmpty(interceptors)) {
// interceptors不为空,则遍历,i++ → 正序的遍历 → 给interceptorIndex赋值
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
// 获得当前正在遍历的HandlerInterceptor
HandlerInterceptor interceptor = interceptors[i];
//如果HandlerInterceptor的preHandle为false,则满足判断条件
if (!interceptor.preHandle(request, response, this.handler)) {
//如果preHandle返回值为false → 这里return了false
//→ 导致了applyPreHandle返回值为false
this.triggerAfterCompletion(request, response, (Exception)null);
//意味着HandlerInterceptors中只要有一个Interceptor的preHandle为false
//则中断doDispatch
//我们又发现在中断doDispatch之前,执行了第14行代码 → 执行的afterCompletion
return false;
}
}
}
return true;
}
在结束之前执行到了14行this.triggerAfterCompletion(request, response, (Exception)null);
执行的是对应的afterCompletion
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {
//先去获得HandlerInterceptors
HandlerInterceptor[] interceptors = this.getInterceptors();
//做了非空判断
if (!ObjectUtils.isEmpty(interceptors)) {
//执行遍历
//从interceptorIndex开始执行的,执行到哪一个HandlerInterceptor的preHandle
// --i 倒序
for(int i = this.interceptorIndex; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
} catch (Throwable var8) {
logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
}
}
}
}
如果有5个HandlerInterceptor,如果第4个preHandle为false,执行情况是什么样子的
preHandle1234、afterCompletion321
如果applyPreHandle所有的HandlerInterceptor的preHandle都返回true,则继续流程
执行applyPostHandle
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//非空判断,--i 倒序遍历,遍历的是全部的HandlerInterceptor的postHandle
for(int i = interceptors.length - 1; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
注意:一旦preHandle为false则一个postHandle也执行不到。所有的preHandle都为true,则会执行所有的postHandle
后面的流程接着保证afterCompletion的执行
完整的执行流程
1.通过执行doDispatch中的getHandler方法,在其中通过HandlerMapping建立映射关系并执行getHandler方法获得了HandlerExecutionChain,Handler和Chain之间是一对一的关系,在Chain中维护了一个interceptor的list,所以handler和interceptor是一对多的关系。
2.执行HandlerExecutionChain的applyPreHandle方法,如果返回值是false的话则结束这个流程,在这个方法中遍历了interceptor数组,并执行每一个interceptor的preHandle方法,如果这个preHandle方法的返回值是fasle那么最终就会导致applyPreHandle的返回值是false,这样就会终止整个流程,也就是说如果遍历的数组中只要有一个的preHandle方法的返回值是false,那么就会中断doDispatch的流程。这里在中断之前,执行了triggerAfterCompletion方法,这里会根据interceptorIndex进行倒序的遍历,这个Index就是执行到的数组中的interceptor的个数,如果此时执行到第五个返回了false,那么就会执行4321个AfterCompletion方法,因为只有返回值是true才能保证AfterCompletion一定执行,如果返回false那么当前的就不会执行。
3.如果applyPreHandle所有的HandlerInterceptor的preHandle都返回true,则继续流程,执行applyPostHandle,执行全部Interceptor的postHandle方法,这里也是倒序执行。这里要注意只有所有的preHandle返回true才会执行到这里
4.最后有各种手段来执行AfterCompletion方法
如果要使用HandlerInterceptor我们需要做什么?
- HandlerInterceptor是谁
- HandlerInterceptor的作用范围
- 直接写bean标签或ref标签:全局
- mvc:interceptor标签:局部
- HandlerInterceptor的顺序
- 书写顺序
首先定义一个类来实现接口,实现自己需要的方法即可
@Component
public class UserInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
Object username = session.getAttribute("username");
if (username==null){
return false;
}
return true;
}
}
然后在配置文件中配置
<mvc:interceptors>
<!--bean标签和ref标签配置HandlerInterceptor时,其实指定了作用范围:全局-->
<!--注意:这个全局是DispatcherServlet作用范围下的全局-->
<!--bean标签意味着局部组件-->
<!--<bean class="com.cskaoyan.interceptor.CustomHandlerInterceptor"/>-->
<!--ref标签意味着引用组件的id-->
<!--<ref bean="customHandlerInterceptor"/>-->
<!--也可以指定局部范围-->
<mvc:interceptor>
<!--指定作用范围 **代表多级url-->
<mvc:mapping path="/hello/**"/>
<!--bean标签或ref标签指定HandlerInterceptor-->
<ref bean="customHandlerInterceptor"/>
</mvc:interceptor>
<ref bean="customHandlerInterceptor2"/>
<ref bean="customHandlerInterceptor3"/>
</mvc:interceptors>
执行的顺序就是注册组件的顺序
HibernateValidation
校验框架,请求参数的合法性校验
以JavaBean来接收请求参数。请求参数名和JavaBean的成员变量名是对应起来,对请求参数做校验,转移到对成员变量做校验。
通过注解来建立关系 ,如果不同的校验逻辑提供了不同的注解,就是对成员变量(请求参数)增加不同的功能的校验注解,就意味着增加了校验功能
引入依赖
hibernate-validator使用6版本的
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0.Final</version>
</dependency>
注册组件
validator
LocalValidatorFactoryBean,但是它是虚假的FactoryBean,没有实现FactoryBean接口
<mvc:annotation-driven validator="validator"/>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
</bean>
使用注解
参数校验声明@Valid或@Validated
//localhost:8080/user/register2?username=songge&password=niupi&age=30
@RequestMapping("register2")
public BaseRespVo register2(@Validated User user) {
return BaseRespVo.ok();
}
校验功能注解
@Data
public class User {
@Length(min = 6)
String username;
@Length(min = 6,max = 10)
String password;
@Max(100)
@Min(18)
Integer age;
}
常见的注解 (Bean Validation 中内置的 constraint)
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期 Date
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator 附加的 constraint
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
没有通过校验的警告信
http://localhost:8080/user/register2?username=songge&password=niupile&age=120
Field error in object ‘user’ on field ‘age’: rejected value [120]; codes [Max.user.age,Max.age,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.age,age]; arguments []; default message [age],100]; default message [最大不能超过100]]
自行处理校验结果
//在形参中增加一个BindingResult
//localhost:8080/user/register3?username=songge&password=niupi&age=30
@RequestMapping("register3")
public BaseRespVo register3(@Validated User user, BindingResult bindingResult) {
//如何知道校验没有通过
if (bindingResult.hasFieldErrors()) {//只要有一个成员变量没有通过校验,返回true
//如果校验没有通过是谁导致的 → 获得校验错误
FieldError fieldError = bindingResult.getFieldError();
//校验没有通过我们能获得哪些信息
String field = fieldError.getField();//获得成员变量名 → 请求参数名
Object rejectedValue = fieldError.getRejectedValue();//导致校验失败的值
String defaultMessage = fieldError.getDefaultMessage();
String msg = "请求参数" + field + "没有通过校验:" + rejectedValue + "; " + defaultMessage;
return BaseRespVo.fail(msg);
}
return BaseRespVo.ok();
}
这里要注意,如果获取bindingResult的同时又要使用Session,那么要将bindingResult写在前面,否则会出现问题
修改默认的消息
校验功能注解的属性中,message属性
@Length(min = 6,max = 10,message = "length between 6 and 10")
String password;
JavaConfig
使用Java代码和注解完成对应的配置
application.xml → 配置类
***MVC配置类
@ComponentScan(“com.cskaoyan”) → context:component-scan
@EnableWebMvc → mvc:annotation-driven
SpringMVC这里我们做过哪些配置
- mvc:resources
- mvc:interceptors
- mvc:annotation-driven conversion-service
- mvc:annotation-driven validator
- multipartResolver
WebMvcConfigurer接口提供了这些方法 → 配置类实现这个接口
@ComponentScan("com.cskaoyan")
@EnableWebMvc
public class MvcConfiguration implements WebMvcConfigurer {
}
mvc:resources静态资源映射
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//静态资源映射mapping location
registry.addResourceHandler("/pic1/**").addResourceLocations("/");
registry.addResourceHandler("/pic2/**").addResourceLocations("classpath:/");
registry.addResourceHandler("/pic3/**").addResourceLocations("file:d:/spring/");
}
mvc:interceptors HandlerInterceptor配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
//HandlerInterceptor是谁、作用范围是什么、顺序
registry.addInterceptor(new CustomHandlerInterceptor());//作用范围是全局
registry.addInterceptor(new CustomHandlerInterceptor2()).addPathPatterns("/hello/**");
registry.addInterceptor(new CustomHandlerInterceptor3()).addPathPatterns("/goodbye/**");
}
类型转换服务
取出、增加、放回
@Autowired
ConfigurableConversionService conversionService;
//利用生命周期 → 来执行方法
@PostConstruct
public void addCustomConverter() {
conversionService.addConverter(new String2DateConverter());
}
@Bean
@Primary
public ConfigurableConversionService conversionService() {
return conversionService;
}
简单的方式
//添加Converter可以使用addFormatters
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new String2DateConverter());
}
校验器
mvc:annotation-driven validator
@Override
public Validator getValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setProviderClass(HibernateValidator.class);
return validator;
}
文件上传的处理器
//要么通过方法名指定组件id,要么通过@Bean的value属性指定;组件id是固定值
@Bean
public MultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
web.xml → 配置类
AACDSI
public class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() { //spring的配置文件
return new Class[]{SpringConfiguration.class};
}
@Override
protected Class<?>[] getServletConfigClasses() { //mvc配置文件
return new Class[]{MvcConfiguration.class};
}
@Override
protected String[] getServletMappings() { //dispatchServlet作用范围
return new String[]{"/"};
}
}
Spring配置类
//额外扫描到SpringMVC的配置类和Controller组件
//Filter里的type可以不写,默认值就是FilterType.ANNOTATION
@ComponentScan(value = "com.cskaoyan",
excludeFilters = {@ComponentScan.Filter(/*type = FilterType.ANNOTATION,*/
value = {Controller.class, EnableWebMvc.class})})
@Configuration
public class SpringConfiguration {
}
SpringMVC的配置类中可以使用Spring配置类中的组件