1. 入门案例之创建步骤
SpringMVC 的制作过程和上述流程几乎是一致的,具体的实现流程是什么?
1.创建 web 工程(Maven 结构)
2.设置 tomcat 服务器,加载 web 工程(tomcat 插件)
3.导入坐标(SpringMVC Servlet)
4.定义处理请求的功能类(UserController)
5.设置请求映射(配置映射关系)
6.将 SpringMVC 设定加载到 Tomcat 容器中
因为 SpringMVC 是一个 Web 框架,将来是要替换 Servlet,所以先来回顾下以前 Servlet 是如何进行开发的?
1.创建 web 工程(Maven 结构)
2.设置 tomcat 服务器,加载 web 工程(tomcat 插件)
配置 Tomcat 插件
代码语言:javascript复制 <build>
<plugins>
<!-- 配置Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
<uriEncoding>UTF-8</uriEncoding>
</configuration>
</plugin>
</plugins>
</build>
3.导入坐标(Servlet)
代码语言:javascript复制 <dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
说明:servlet 的坐标为什么需要添加
<scope>provided</scope>
?
- scope 是 maven 中 jar 包依赖作用范围的描述,
- 如果不设置默认是
compile
在在编译、运行、测试时均有效 - 如果运行有效的话就会和 tomcat 中的 servlet-api 包发生冲突,导致启动报错
- provided 代表的是该包只在编译和测试的时候用,运行的时候无效直接使用 tomcat 中的,就避免冲突
4.定义处理请求的功能类(UserServlet)
创建配置类
代码语言:javascript复制@Configuration
@ComponentScan("com.north")
public class SpringConfig {
}
创建 Controller 类
代码语言:javascript复制@Controller
public class UserController {
@RequestMapping("/save")
public void save(){
System.out.println("user save ...");
}
}
5.设置请求映射(配置映射关系)
使用配置类替换 web.xml ——> 将 web.xml 删除,换成 ServletContainersInitConfig
代码语言:javascript复制public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
// 加载SpringMVC配置类
protected WebApplicationContext createServletApplicationContext() {
// 初始化WebApplicationContext
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// 加载指定配置类
context.register(SpringConfig.class);
return context;
}
// 设置由SpringMVC控制器处理的请求映射路径
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
// 加载Spring配置类
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
6.将 SpringMVC 设定加载到 Tomcat 容器中
注意事项:
- SpringMVC 是基于 Spring 的,在 pom.xml 只导入了
spring-webmvc
jar 包的原因是它会自动依赖 spring 相关坐标 - AbstractDispatcherServletInitializer 类是 SpringMVC 提供的快速初始化 Web3.0 容器的抽象类
- AbstractDispatcherServletInitializer 提供了三个接口方法供用户实现
- createServletApplicationContext 方法,创建 Servlet 容器时,加载 SpringMVC 对应的 bean 并放入 WebApplicationContext 对象范围中,而 WebApplicationContext 的作用范围为 ServletContext 范围,即整个 web 容器范围
- getServletMappings 方法,设定 SpringMVC 对应的请求映射路径,即 SpringMVC 拦截哪些请求
- createRootApplicationContext 方法,如果创建 Servlet 容器时需要加载非 SpringMVC 对应的 bean,使用当前方法进行,使用方式和 createServletApplicationContext 相同。
- createServletApplicationContext 用来加载 SpringMVC 环境
- createRootApplicationContext 用来加载 Spring 环境
知识点 1:@Controller
名称 | @Controller |
---|---|
类型 | 类注解 |
位置 | SpringMVC 控制器类定义上方 |
作用 | 设定 SpringMVC 的核心控制器 bean |
知识点 2:@RequestMapping
名称 | @RequestMapping |
---|---|
类型 | 类注解或方法注解 |
位置 | SpringMVC 控制器类或方法定义上方 |
作用 | 设置当前控制器方法请求访问路径 |
相关属性 | value(默认),请求访问路径 |
知识点 3:@ResponseBody
名称 | @ResponseBody |
---|---|
类型 | 类注解或方法注解 |
位置 | SpringMVC 控制器类或方法定义上方 |
作用 | 设置当前控制器方法响应内容为当前返回值,无需解析 |
2. 入门案例总结
- 一次性工作
- 创建工程,设置服务器,加载工程
- 导入坐标
- 创建 web 容器启动类,加载 SpringMVC 配置,并设置 SpringMVC 请求拦截路径
- SpringMVC 核心配置类(设置配置类,扫描 controller 包,加载 Controller 控制器 bean)
- 多次工作
- 定义处理请求的控制器类
- 定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回 json 数据(@ResponseBody)
3. SpringMVC 工作流程解析
为了更好的使用 SpringMVC,我们将 SpringMVC 的使用过程总共分两个阶段来分析,分别是启动服务器初始化过程
和单次请求过程
3.1 启动服务器初始化过程
- 服务器启动,执行 ServletContainersInitConfig 类,初始化 web 容器
- 功能类似于以前的 web.xml
- 执行 createServletApplicationContext 方法,创建了 WebApplicationContext 对象
- 该方法加载 SpringMVC 的配置类 SpringMvcConfig 来初始化 SpringMVC 的容器
- 加载 SpringMvcConfig 配置类
- 执行@ComponentScan 加载对应的 bean
- 扫描指定包及其子包下所有类上的注解,如 Controller 类上的@Controller 注解
- 加载 UserController,每个@RequestMapping 的名称对应一个具体的方法
- 此时就建立了
/save
和 save 方法的对应关系
- 执行 getServletMappings 方法,设定 SpringMVC 拦截请求的路径规则
-
/
代表所拦截请求的路径规则,只有被拦截后才能交给 SpringMVC 来处理请求
3.2 单次请求过程
- 发送请求
http://localhost/save
- web 容器发现该请求满足 SpringMVC 拦截规则,将请求交给 SpringMVC 处理
- 解析请求路径/save
- 由/save 匹配执行对应的方法 save()
- 上面的第五步已经将请求路径和方法建立了对应关系,通过/save 就能找到对应的 save 方法
- 执行 save()
- 检测到有@ResponseBody 直接将 save()方法的返回值作为响应体返回给请求方
4. bean 加载控制
4.1 问题分析
入门案例的内容已经做完了,在入门案例中我们创建过一个SpringMvcConfig
的配置类,再回想前面咱们学习 Spring 的时候也创建过一个配置类SpringConfig
。这两个配置类都需要加载资源,那么它们分别都需要加载哪些内容?
我们先来看下目前我们的项目目录结构:
- config 目录存入的是配置类,写过的配置类有:
- ServletContainersInitConfig
- SpringConfig
- SpringMvcConfig
- JdbcConfig
- MybatisConfig
- controller 目录存放的是 SpringMVC 的 controller 类
- service 目录存放的是 service 接口和实现类
- dao 目录存放的是 dao/Mapper 接口
controller、service 和 dao 这些类都需要被容器管理成 bean 对象,那么到底是该让 SpringMVC 加载还是让 Spring 加载呢?
- SpringMVC 加载其相关 bean(表现层 bean),也就是 controller 包下的类
- Spring 控制的 bean
- 业务 bean(Service)
- 功能 bean(DataSource,SqlSessionFactoryBean,MapperScannerConfigurer 等)
分析清楚谁该管哪些 bean 以后,接下来要解决的问题是如何让 Spring 和 SpringMVC 分开加载各自的内容。
在 SpringMVC 的配置类SpringMvcConfig
中使用注解@ComponentScan
,我们只需要将其扫描范围设置到 controller 即可,如
在 Spring 的配置类SpringConfig
中使用注解@ComponentScan
,当时扫描的范围中其实是已经包含了 controller,如:
从包结构来看的话,Spring 已经多把 SpringMVC 的 controller 类也给扫描到,所以针对这个问题该如何解决,就是咱们接下来要学习的内容。
概括的描述下咱们现在的问题就是因为功能不同,如何避免 Spring 错误加载到 SpringMVC 的 bean?
4.2 思路分析
针对上面的问题,解决方案也比较简单,就是:
- 加载 Spring 控制的 bean 的时候排除掉 SpringMVC 控制的 bean
具体该如何排除:
- 方式一:Spring 加载的 bean 设定扫描范围为精准范围,例如 service 包、dao 包等
- 方式二:Spring 加载的 bean 设定扫描范围为 com.itheima,排除掉 controller 包中的 bean
- 方式三:不区分 Spring 与 SpringMVC 的环境,加载到同一个环境中[了解即可]
4.3 设置 bean 加载控制
方式一:修改 Spring 配置类,设定扫描范围为精准范围。
代码语言:javascript复制@Configuration
@ComponentScan({"com.itheima.service","comitheima.dao"})
public class SpringConfig {
}
说明:
上述只是通过例子说明可以精确指定让 Spring 扫描对应的包结构,真正在做开发的时候,因为 Dao 最终是交给MapperScannerConfigurer
对象来进行扫描处理的,我们只需要将其扫描到 service 包即可。
方式二:修改 Spring 配置类,设定扫描范围为 com.itheima,排除掉 controller 包中的 bean
代码语言:javascript复制@Configuration
@ComponentScan(value="com.itheima",
excludeFilters=@ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class
)
)
public class SpringConfig {
}
- excludeFilters 属性:设置扫描加载 bean 时,排除的过滤规则
- type 属性:设置排除规则,当前使用按照 bean 定义时的注解类型进行排除
- ANNOTATION:按照注解排除
- ASSIGNABLE_TYPE:按照指定的类型过滤
- ASPECTJ:按照 Aspectj 表达式排除,基本上不会用
- REGEX:按照正则表达式排除
- CUSTOM:按照自定义规则排除
大家只需要知道第一种 ANNOTATION 即可
- classes 属性:设置排除的具体注解类,当前设置排除@Controller 定义的 bean
如何测试 controller 类已经被排除掉了?
代码语言:javascript复制public class App{
public static void main (String[] args){
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
System.out.println(ctx.getBean(UserController.class));
}
}
如果被排除了,该方法执行就会报 bean 未被定义的错误
注意:测试的时候,需要把 SpringMvcConfig 配置类上的@ComponentScan 注解注释掉,否则不会报错
出现问题的原因是,
- Spring 配置类扫描的包是
com.itheima
- SpringMVC 的配置类,
SpringMvcConfig
上有一个@Configuration 注解,也会被 Spring 扫描到 - SpringMvcConfig 上又有一个@ComponentScan,把 controller 类又给扫描进来了
- 所以如果不把@ComponentScan 注释掉,Spring 配置类将 Controller 排除,但是因为扫描到 SpringMVC 的配置类,又将其加载回来,演示的效果就出不来
- 解决方案,也简单,把 SpringMVC 的配置类移出 Spring 配置类的扫描范围即可。
最后一个问题,有了 Spring 的配置类,要想在 tomcat 服务器启动将其加载,我们需要修改 ServletContainersInitConfig
代码语言:javascript复制public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
}
对于上述的配置方式,Spring 还提供了一种更简单的配置方式,可以不用再去创建AnnotationConfigWebApplicationContext
对象,不用手动register
对应的配置类,如何实现?
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
知识点 1:@ComponentScan
名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置 spring 配置类扫描路径,用于加载使用注解格式定义的 bean |
相关属性 | excludeFilters:排除扫描路径中加载的 bean,需要指定类别(type)和具体项(classes)includeFilters:加载指定的 bean,需要指定类别(type)和具体项(classes) |