ssm速通1(2/2)

本篇是对ssm速通1的补充

2、注入组件

依赖注入(Dependency Injection, DI)是Spring框架的核心特性之一,它通过将对象依赖关系的创建和管理外部化,实现了松耦合和更易测试的代码结构。

2.1、依赖注入的基本方式

字段最省事,构造最靠谱,Setter 最灵活

①、构造器注入 (Constructor Injection)

推荐方式,特别是对于必需依赖:

  @Service
  public class UserService {
      private final UserRepository userRepository; // 可 final
      
      // Spring 4.3+ 对于单构造器可以省略@Autowired
      public UserService(UserRepository userRepository) {
          this.userRepository = userRepository;
      }
  }

特点

  • 官方推荐(Spring 团队、Effective Spring)

  • 支持 finalimmutable 对象,测试直接 new

  • 启动期即可发现循环依赖,失败早暴露

  • 多依赖时参数列表过长 → 可转用 Setter / 工厂方法配置聚合

②、Setter注入 (Setter Injection)

适用于可选依赖:

  @Service
  public class OrderService {
        private UserService userService;
  ​
        @Autowired
        public void setUserService(UserService userService) {
            this.userService = userService;
        }
  }

特点

  • 可选依赖:方法可带 @Autowired(required = false)

  • 适合“有默认值、运行期可能重新设置”场景

  • 可被多次调用(re-configuration、测试 mock)

  • 无法使用 final

③、字段注入 (Field Injection)

不推荐在生产代码中使用,主要用于测试或快速原型:

  
  @Service
  public class ProductService {
      @Autowired        // 或 @Inject / @Resource
      private ProductRepository productRepository;
  }

特点

  • 代码最少,可读性高

  • 测试需反射或容器,不易写纯单元测试

  • 无法加 final,线程安全语义弱

  • 循环依赖时容器只能降级用代理,问题被推迟到运行时

2.2、依赖注入的注解
①、@Autowired

@Autowired 是 Spring 框架的依赖注入(DI)核心注解,用来自动把容器里的 Bean 装配到字段、构造器、方法参数里,省去手动 getBean()

Ⅰ、基本语法
  
  @Service
  public class OrderService {
      @Autowired                  // 1. 字段注入
      private UserService userService;
  }
  ​
  // -------------------------------------------------------------
  @Service
  public class OrderService {
      private final UserService userService;
      @Autowired                  // 2. 构造器注入(Spring 4.3+ 可省略)
      public OrderService(UserService userService) {
          this.userService = userService;
      }
  }
  ​
  // -------------------------------------------------------------
  @Autowired                      // 3. 方法注入(也可用在 setter)
  public void prepare(UserService userService, ProductService productService) {
      ...
  }
Ⅱ、特性
  • 默认要求依赖必须存在,可通过@Autowired(required=false)设为可选

  • 可与@Qualifier配合使用解决歧义

Ⅲ、自动装配流程

先按照类型找

  • 有且仅有一个找到,则直接注入,不在乎名字

  • 若按照类型找到多个,则再按照名称找,变量名就是要找的名称

    • 如果找到则直接注入

    • 否则没找到直接报错

②、@Resource

JSR-250标准注解,优先按名称匹配:

  
  @Resource(name="mySpecialBean")
  private SpecialBean bean;
③、@Inject

JSR-330标准注解,功能类似@Autowired

  
  @Inject
  private PaymentService paymentService;
④、@Resource@Autowired 注解深度对比

在Spring依赖注入中,@Resource@Autowired是两个最常用的注解,它们有相似之处但也有重要区别

Ⅰ、包来源即兼容性
特性@Autowired@Resource
所属标准Spring框架自有注解JSR-250 (Java标准注解)
包路径org.springframework.beans.factory.annotationjavax.annotation
引入版本Spring 2.5+Java EE 5+
环境@Autowired@Resource
Spring原生应用完全支持支持
JavaEE/JakartaEE需要Spring原生支持
Quarkus/Micronaut有限支持更好支持
单元测试需要Spring更通用

即理论上 @Resource 更灵活,可使用多个其他框架(包含 Spring 框架),但是 Spring 一家独大,所以使用 @Autowired 也够

Ⅱ、注入规则

@Autowired 注入规则

  • 默认按类型匹配 (byType)

  • 配合 @Qualifier 可实现按名称匹配

@Resource 注入规则

  • 默认按名称匹配 (byName)

  • 名称未指定时回退到按类型匹配

建议的话,二者均可用,在纯Spring环境中,@Autowired提供了更丰富的功能集成,而@Resource则在跨环境兼容性上更有优势。使用 @Autowired 就够了

2.3、处理依赖歧义

当同一类型有多个实现时,即上方提到的自动装配过程中按照类型找到多个,我们需要进行消除歧义:

①、使用@Qualifier精确选取

在 Spring 容器里,“先按类型、再按名称” 的注入策略遇到多个同类型 Bean 时,会抛NoUniqueBeanDefinitionException。@Qualifier 的核心价值就是“给每个候选 Bean 打标签”,然后在注入点按标签精确点名,从而消除歧义

前提:我们在Bean定义时已经给了名字

  
  @Configuration
  public class RepoConfig {
      @Bean("mysqlUserRepo")        // ① 定义时给名字
      public UserRepository mysqlRepo() { return new MySqlUserRepo(); }
  ​
      @Bean("mongoUserRepo")
      public UserRepository mongoRepo() { return new MongoUserRepo(); }
  }
  
  @Service
  public class UserService {
      @Autowired
      @Qualifier("mysqlUserRepo")   // 注入时点名
      private UserRepository userRepository;
  }
  // 相当于我们先通过查找 UserRepository 该类型的组件,发现多个实现,使用 @Qualifier 将其精确锁定到了一个名为 mysqlUserRepo 的 UserRepository 类型的实现
②、使用自定义限定符注解

前提:我们在Bean定义时已经给了名字

 
  @Bean
  @DatabaseType("mysql")
  public DataSource mysqlUserRepo() { return new MySqlUserRepo(); }
  ​
  @Bean
  @DatabaseType("mongo")
  public DataSource mongoUserRepo() { return new MongoUserRepo(); }
  
  @Target({ElementType.FIELD, ElementType.PARAMETER})
  @Retention(RetentionPolicy.RUNTIME)
  @Qualifier       // 核心元注解
  public @interface DatabaseType {
      String value();
  }
  ​
  // 使用
  @Autowired
  @DatabaseType("mysql")
  private DataSource dataSource;
③、使用@Primary默认组件

前提:我们在Bean定义时已经给了名字

 
  @Bean
  @DatabaseType("mysql")
  public DataSource mysqlUserRepo() { return new MySqlUserRepo(); }
  ​
  @Primary       // 使用该注解,即当按照类型找到多个,默认选择这个
  @Bean
  @DatabaseType("mongo")
  public DataSource mongoUserRepo() { return new MongoUserRepo(); }
  
  @Qualifier("mysql")  // 当然即使默认是选择名为 mongo 的组件,可以使用 @Qualifier 切换为其他组件
  @Autowired
  private DataSource dataSource;

注意有 @Primary 存在,修改属性名就不生效

2.4、特殊类型的依赖注入
①、集合注入
  
  @Autowired
  private List<Validator> validators; // 注入所有Validator实现
  ​
  @Autowired
  private Map<String, Processor> processors; // key为bean名称
②、可选依赖
  // Java 8+
  @Autowired
  private Optional<CacheManager> cacheManager;
  ​
  // 或
  @Autowired(required = false)
  private CacheManager cacheManager;
③、延迟注入
  
  @Autowired
  private ObjectProvider<ExpensiveService> expensiveServiceProvider;
  ​
  public void someMethod() {
      ExpensiveService service = expensiveServiceProvider.getIfAvailable();
      // 或
      ExpensiveService service = expensiveServiceProvider.getObject();
  }
2.5、依赖注入的最佳实践
  1. 优先使用构造器注入,特别是对于必需依赖

  2. 对于可选依赖,考虑使用Setter注入或ObjectProvider

  3. 避免字段注入在生产代码中使用

  4. 使用@Qualifier或自定义限定符处理多实现情况

  5. 保持依赖不可变(final字段)

  6. 避免循环依赖,这是设计问题的信号

  7. 对于复杂对象的创建,考虑使用FactoryBean

2.6、Spring Boot中的依赖注入

Spring Boot简化了依赖注入的配置:

①、自动配置
  @SpringBootApplication
  public class MyApp {
      public static void main(String[] args) {
          SpringApplication.run(MyApp.class, args);
      }
  }
②、条件化Bean
  @Bean
  @ConditionalOnMissingBean
  public DataSource dataSource() {
      // 默认数据源配置
  }
③、属性驱动注入
  
  @Configuration
  @EnableConfigurationProperties(AppProperties.class)
  public class AppConfig {
  }
  ​
  @ConfigurationProperties(prefix = "app")
  public class AppProperties {
      private String name;
      // getters/setters
  }
2.7、补充注解
①、@Value

@Value是Spring提供的最常用的属性注入注解,用于从各种来源注入值到Spring管理的Bean中。

Ⅰ、基本语法
  @Component
  public class AppConfig {
      
      // 注入简单值
      @Value("defaultValue")      // 即默认 simpleValue = defaultValue
      private String simpleValue;
      
      // 注入系统属性
      @Value("${user.home}")
      private String userHome;
      
      // 注入环境变量
      @Value("${JAVA_HOME}")
      private String javaHome;
      
      // 注入配置文件属性
      @Value("${tom.age}")   
      // 即默认 myage = tom.age,tom.age是在 application.properties 中配置的
      private int myage;
      @Value("${app.timeout:30}")   // 即默认 timeout 取不到 app.timeout 时默认是 30 
      private int timeout;
    
      // 注入SpEL表达式,即使用#{}内可加 Spring 表达式语言
      @Value("#{1>2?1:2}")
      // 相当于 int max = 1>2?1:2;
      private int max;
      @Value("#{T(java.util.UUID).randomUUID().toString}")
      // 相当于 String myUUID = UUID.randomUUID().toString
      private String myUUID;
  }
Ⅱ、支持的内容类型
表达式类型示例说明
字面量@Value("hello")直接字符串值
属性占位符@Value("${app.name}")从Environment解析
SpEL表达式@Value("#{systemProperties['user.region']}")Spring表达式语言
默认值@Value("${app.threads:4}")冒号后为默认值
类型转换@Value("${app.percentage}") double percent自动类型转换
②、@PropertySource

@PropertySource 是 Spring 3.1 开始提供的 “外挂”式配置加载器让容器在启动时把任意 .properties(或 XML/YAML,自定义工厂即可)文件读进来,转成 Environment 里的 PropertySource,随后就可通过 @Value@ConfigurationPropertiesEnvironment 接口随取随用。它只负责“加载”,不做注入;一次声明,全局可用

Ⅰ、核心功能
  1. 指定文件路径(classpath / file / URL)

  2. 支持多文件、编码、忽略缺失、自定义解析工厂

  3. 加载后内容并入 Environment,优先级 高于 application.properties 低于 命令行参数

  4. @Value@ConfigurationProperties 无缝配合

Ⅱ、基本语法

首先在 application.properties 的当前目录先创建对应的 jdbc.properties

  jdbc.url=jdbc:mysql://127.0.0.1:3306/demo
  jdbc.user=root
  jdbc.pwd=root

之后加载配置类即可

  
  @Configuration
  @PropertySource("classpath:jdbc.properties")   // 加载
  public class DataSourceConfig {
  jdbc.properties
      @Value("${jdbc.url}")                      
      // 使用,相当于从你自定义创建的 jdbc.properties 中取出
      // jdbc.url=jdbc:mysql://127.0.0.1:3306/demo 并且赋值给 url
      private String url;
  ​
      @Value("${jdbc.user}")
      private String user;
  ​
      @Value("${jdbc.pwd}")
      private String pwd;
  ​
      @Bean
      public DataSource dataSource() {
          return DataSourceBuilder.create()
                  .url(url).username(user).password(pwd)
                  .build();
      }
  }

即该注解可以帮助我们将原来所有要写入 application.properties 的配置属性分类存放,如商铺对应的配置属性新建一个 shop.properties 存放,用户对应的配置属性新建一个 user.properties 存放,之后使用 @PropertySource("classpath:shop.properties") 加载进我们的商铺配置类,使用 @PropertySource("classpath:user.properties") 加载进我们的用户配置类即可

注意:

  • 从自己项目中找 properties 使用 classpath:后面接 application.properties 到要寻找的 properties 的路径

  • 从第三方项目中找 properties 使用 classpath*:后面接要寻找的 properties 的路径

③、@Profile

@Profile 是 Spring 的 “环境开关”只有当前激活的 Profile 与注解值匹配时,相应的 Bean / 配置类才会被注册到容器;不匹配就整体跳过,实现“一套代码,多环境差异化部署”。

举例:

  
  @Profile("test")      // 只有激活 test 环境才可与进行以下配置的实现
  @Configuration
  public class DataSourceConfig {
  ​
      @Bean
      @Profile("dev")                    // 仅 dev 环境生效
      public DataSource devDs() {
          return DataSourceBuilder.create()
                  .url("jdbc:h2:mem:dev")
                  .build();
      }
  ​
      @Bean
      @Profile("prod")                   // 仅 prod 环境生效
      public DataSource prodDs() {
          return DataSourceBuilder.create()
                  .url("jdbc:mysql://prod/db")
                  .username("${db.user}")
                  .password("${db.pwd}")
                  .build();
      }
    
      @Bean
      @Profile("build")                    // 仅 build 环境生效
      public DataSource devDs() {
        return DataSourceBuilder.create()
          .url("jdbc:h2:mem:build")
          .build();
      }
  }

如上数据源有三个,@Profile 中配置的环境是自定义的(如 test、dev、prod等),默认是 default

  
  @Component
  public class DeliveryDao {
      @Autowired
      private MyDataSource myDataSource;
  ​
      public void saveDelivery() {
          System.out.println("数据源:保存数据" + myDataSource);
      }
  }

当我们在其他组件内使用 @Autowired 注入数据源时,由于数据源有多个,则自动注入不知道选择哪个,默认选择带有 default 标识的,没有则报错

此时我们可以在 @Profile("") 内再添加一个 default 标识,如@Profile("build","default"),则会默认使用 build 环境的数据源,当然可以在 application.properties 中激活数据源环境,使用spring.profiles.active=你要激活的环境

3、生命周期

大多数 Java 组件(Servlet、Spring Bean、EJB 等)都遵循类似的四个主要生命周期阶段:

  1. 创建(Instantiation)

  2. 初始化(Initialization)

  3. 运行(In Service/Runtime)

  4. 销毁(Destruction)

3.1、创建阶段
  • 实例化对象:通过构造函数或工厂方法创建组件实例

  • 依赖注入:为对象的属性/字段注入依赖项(在IoC容器中)

3.2、初始化阶段
  • Aware接口回调(Spring特有):

    • BeanNameAware.setBeanName()

    • BeanFactoryAware.setBeanFactory()

    • ApplicationContextAware.setApplicationContext()

  • 初始化前处理

    • Spring: BeanPostProcessor.postProcessBeforeInitialization()

  • 初始化操作

    • 调用 @PostConstruct 注解的方法

    • 实现 InitializingBean 接口的 afterPropertiesSet() 方法(Spring)

    • 调用自定义的 init-method(XML配置或@Bean(initMethod="...")

  • 初始化后处理

    • Spring: BeanPostProcessor.postProcessAfterInitialization()

3.3、运行阶段
  • 组件处于就绪状态,处理业务请求

  • 对于有状态组件可能涉及状态转换:

    • EJB 的激活/钝化(@PostActivate, @PrePassivate

    • Web 应用的会话管理

3.4、销毁阶段
  • 销毁前处理

    • 调用 @PreDestroy 注解的方法

    • 实现 DisposableBean 接口的 destroy() 方法(Spring)

    • 调用自定义的 destroy-method(XML配置或@Bean(destroyMethod="...")

  • 资源释放

    • 关闭数据库连接

    • 释放文件句柄

    • 清理缓存等

以下给出一个流程图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值