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?
2. 思路分析
针对上面的问题,解决方案也比较简单,就是:
- 加载Spring控制的bean的时候排除掉SpringMVC控制的bean
具体该如何排除:
- 方式一:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等
- 方式二:Spring加载的bean设定扫描范围为com.itheima,排除掉controller包中的bean
- 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中[了解即可]
3. 环境准备
创建一个Web的Maven项目
pom.xml添加Spring依赖
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.15.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.11</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.1</version> <configuration> <port>80</port> <path>/</path> </configuration> </plugin> </plugins> </build> </project>
创建对应的配置类
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer { @Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(SpringMvcConfig.class); return context; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } @Override protected WebApplicationContext createRootApplicationContext() { return null; } }
@Configuration @ComponentScan("com.dcxuexi.controller") public class SpringMvcConfig { }
@Configuration @ComponentScan("com.dcxuexi") public class SpringConfig { }
编写Controller,Service,Dao,Domain类
@Controller public class UserController { @Autowired private UserService userService; @GetMapping("/findAll") @ResponseBody public List<User> selectUserAll(){ return userService.selectUserAll(); } @GetMapping("/find/{id}") @ResponseBody public User selectUserById(@PathVariable Integer id){ return userService.selectUserById(id); } }
public interface UserService { public List<User> selectUserAll(); public User selectUserById(Integer id); } @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public List<User> selectUserAll() { return userDao.selectUserAll(); } @Override public User selectUserById(Integer id) { return userDao.selectUserById(id); } }
public interface UserDao { @Select("select user_id userId,email,user_name userName,branch_name branchName from platform_user ") public List<User> selectUserAll(); @Select("select user_id userId,email,user_name userName,branch_name branchName from platform_user where user_id = #{id}") public User selectUserById(Integer id); }
public class User { private Integer userId; private String email; private String userName; private String branchName; public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getBranchName() { return branchName; } public void setBranchName(String branchName) { this.branchName = branchName; } @Override public String toString() { return "User{" + "userId=" + userId + ", email='" + email + '\'' + ", userName='" + userName + '\'' + ", branchName='" + branchName + '\'' + '}'; } }
最终创建好的项目结构如下:
4. 设置bean加载控制
方式一:修改Spring配置类,设定扫描范围为精准范围。
@Configuration
@ComponentScan({"com.dcxuexi.dao","com.dcxuexi.service"})
public class SpringConfig {
}
说明:
上述只是通过例子说明可以精确指定让Spring扫描对应的包结构,真正在做开发的时候,因为Dao最终是交给MapperScannerConfigurer
对象来进行扫描处理的,我们只需要将其扫描到service包即可。
方式二:修改Spring配置类,设定扫描范围为com.dcxuexi,排除掉controller包中的bean
@Configuration
//@ComponentScan({"com.dcxuexi.dao","com.dcxuexi.service"}) //方法一
@ComponentScan(value = "com.dcxuexi",
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类已经被排除掉了?
public class SpringMvcBean {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
System.out.println(context.getBean(UserController.class));
}
}
如果被排除了,该方法执行就会报bean未被定义的错误
注意:测试的时候,需要把SpringMvcConfig配置类上的@ComponentScan注解注释掉,否则不会报错
出现问题的原因是,
- Spring配置类扫描的包是
com.dcxuexi
- SpringMVC的配置类,
SpringMvcConfig
上有一个@Configuration注解,也会被Spring扫描到 - SpringMvcConfig上又有一个@ComponentScan,把controller类又给扫描进来了
- 所以如果不把@ComponentScan注释掉,Spring配置类将Controller排除,但是因为扫描到SpringMVC的配置类,又将其加载回来,演示的效果就出不来
- 解决方案,也简单,把SpringMVC的配置类移出Spring配置类的扫描范围即可。
最后一个问题,有了Spring的配置类,要想在tomcat服务器启动将其加载,我们需要修改ServletContainersInitConfig
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(SpringMvcConfig.class);
return context;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(SpringConfig.class);
return context;
}
}
对于上述的配置方式,Spring还提供了一种更简单的配置方式,可以不用再去创建AnnotationConfigWebApplicationContext
对象,不用手动register
对应的配置类,如何实现?
//web配置类简化开发,仅设置配置类类名即可
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[]{"/"};
}
}
//public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//
// @Override
// protected WebApplicationContext createServletApplicationContext() {
// AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// context.register(SpringMvcConfig.class);
// return context;
// }
//
// @Override
// protected String[] getServletMappings() {
// return new String[]{"/"};
// }
//
// @Override
// protected WebApplicationContext createRootApplicationContext() {
// AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// context.register(SpringConfig.class);
// return context;
// }
//}
知识点1:@ComponentScan
名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置spring配置类扫描路径,用于加载使用注解格式定义的bean |
相关属性 | excludeFilters:排除扫描路径中加载的bean,需要指定类别(type)和具体项(classes) includeFilters:加载指定的bean,需要指定类别(type)和具体项(classes) |