写点什么

[SpringMVC]bean 加载控制

  • 2022 年 9 月 21 日
    黑龙江
  • 本文字数:4237 字

    阅读完需:约 14 分钟



🏳️‍🌈bean 加载控制

🍸问题分析

我们先来看下目前我们的项目目录结构:



  • 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?

🍸思路分析

针对上面的问题,解决方案也比较简单,就是:


  • 加载 Spring 控制的 bean 的时候排除掉 SpringMVC 控制的 bean


具体该如何排除:


  • 方式一:Spring 加载的 bean 设定扫描范围为精准范围,例如 service 包、dao 包等

  • 方式二:Spring 加载的 bean 设定扫描范围为 com.nefu,排除掉 controller 包中的 bean

  • 方式三:不区分 Spring 与 SpringMVC 的环境,加载到同一个环境中[了解即可]

🍸环境准备

  • 创建一个 Web 的 Maven 项目

  • pom.xml 添加 Spring 依赖


  <?xml version="1.0" encoding="UTF-8"?>    <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/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>      <groupId>com.itheima</groupId>    <artifactId>springmvc_02_bean_load</artifactId>    <version>1.0-SNAPSHOT</version>    <packaging>war</packaging>      <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>      <dependency>        <groupId>com.alibaba</groupId>        <artifactId>druid</artifactId>        <version>1.1.16</version>      </dependency>        <dependency>        <groupId>org.mybatis</groupId>        <artifactId>mybatis</artifactId>        <version>3.5.6</version>      </dependency>        <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>        <version>5.1.47</version>      </dependency>        <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-jdbc</artifactId>        <version>5.2.10.RELEASE</version>      </dependency>        <dependency>        <groupId>org.mybatis</groupId>        <artifactId>mybatis-spring</artifactId>        <version>1.3.0</version>      </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 {      protected WebApplicationContext createServletApplicationContext() {          AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();          ctx.register(SpringMvcConfig.class);          return ctx;      }      protected String[] getServletMappings() {          return new String[]{"/"};      }      protected WebApplicationContext createRootApplicationContext() {        return null;      }  }    @Configuration  @ComponentScan("com.itheima.controller")  public class SpringMvcConfig {  }    @Configuration  @ComponentScan("com.itheima")  public class SpringConfig {  }
复制代码


  • 编写 Controller,Service,Dao,Domain 类


  @Controller  public class UserController {        @RequestMapping("/save")      @ResponseBody      public String save(){          System.out.println("user save ...");          return "{'info':'springmvc'}";      }  }    public interface UserService {      public void save(User user);  }    @Service  public class UserServiceImpl implements UserService {      public void save(User user) {          System.out.println("user service ...");      }  }    public interface UserDao {      @Insert("insert into tbl_user(name,age)values(#{name},#{age})")      public void save(User user);  }  public class User {      private Integer id;      private String name;      private Integer age;      //setter..getter..toString略  }
复制代码


最终创建好的项目结构如下:


🍸设置 bean 加载控制

💊方法一

方式一:修改 Spring 配置类,设定扫描范围为精准范围。


@Configuration@ComponentScan({"com.nefu.service","com.nefu.dao"})public class SpringConfig {}
复制代码


注意:


上述只是通过例子说明可以精确指定让 Spring 扫描对应的包结构,真正在做开发的时候,因为 Dao 最终是交给MapperScannerConfigurer对象来进行扫描处理的,我们只需要将其扫描到 service 包即可。

💊方法二

修改 Spring 配置类,设定扫描范围为 com.nefu,排除掉 controller 包中的 bean


@Configuration@ComponentScan(value="com.nefu",    excludeFilters=@ComponentScan.Filter(      type = FilterType.ANNOTATION,        classes = Controller.class    ))public class SpringConfig {}
复制代码


这个地方要注意一点,你在写这个 Controller.class 的时候可能有多个类符合要求:

这里导的是第一个,如果导错了程序会报错!


  • excludeFilters 属性:设置扫描加载 bean 时,排除的过滤规则

  • 我们可以看见两个过滤方法:excludeFilters 代表去除 includeFilters 代表包含

  • type 属性:设置排除规则,当前使用按照 bean 定义时的注解类型进行排除

  • ANNOTATION:按照注解排除

  • ASSIGNABLE_TYPE:按照指定的类型过滤

  • ASPECTJ:按照 Aspectj 表达式排除,基本上不会用

  • REGEX:按照正则表达式排除

  • CUSTOM:按照自定义规则排除

  • 大家只需要知道第一种 ANNOTATION 即可

  • classes 属性:设置排除的具体注解类,当前设置排除 @Controller 定义的 bean


如何测试 controller 类已经被排除掉了?


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.nefu,SpringMVC 的配置类,SpringMvcConfig上有一个 @Configuration 注解,也会被 Spring 扫描到,SpringMvcConfig 上又有一个 @ComponentScan,把 controller 类又给扫描进来了。所以如果不把 @ComponentScan 注释掉,Spring 配置类将 Controller 排除,但是因为扫描到 SpringMVC 的配置类,又将其加载回来,演示的效果就出不来。


解决方案,也简单,把 SpringMVC 的配置类移出 Spring 配置类的扫描范围即可。


最后一个问题,有了 Spring 的配置类,要想在 tomcat 服务器启动将其加载,我们需要修改 ServletContainersInitConfig


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对应的配置类。


我们打开AnnotationConfigWebApplicationContext发现,在他的下面还有一个子类AbstractAnnotationConfigDispatcherServletInitializer



借助它我们就可以简化配置类:


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[]{"/"}; }}
复制代码



发布于: 刚刚阅读数: 4
用户头像

还未添加个人签名 2022.07.31 加入

还未添加个人简介

评论

发布
暂无评论
[SpringMVC]bean加载控制_spring_十八岁讨厌编程_InfoQ写作社区