当前位置: 首页 > news >正文

Spring 04: 注解开发

内容导引

  • 1. 自动注册Bean @Component
      • 更清晰的子注解 @Service、@Controller、@Repositry
    • 1.1 Bean作用域控制 @Scope
    • 1.2 Bean生命周期回调 @PostConsruct、@PreDestroy
  • 2. 容器配置
    • 2.1 定义配置类 @Configuration
      • 配置Bean扫描 @ComponentScan
      • 读取外部配置文件 @PropertySource
    • 2.2 向容器手动注册Bean @Bean
      • 关于@Bean在@Configuration配置类中特性的讨论
  • 3. 自动注入
    • 3.1 @Autowired
      • 满足注入点类型要求的候选Bean不止一个怎么办?
        • 1. `@Primary`指定优先注入
        • 2. `@Qualifier`指定按名称注入
    • 3.2 @Resource
    • 3.3 @Autowired与@Resource区别总结
  • 4. 注解弃用说明

1. 自动注册Bean @Component

@Component 是类注解,用于自动检测和装配组件到 Spring 容器中。当 Spring 应用程序启动时,它通过类路径扫描,自动识别带有 @Component 注解的类,并将这些类实例化为 Spring 容器中的 beans。

使用该注解注册的Bean的名称,默认是类名的首字母小写形式。当然,也可以以注解实参的方式手动指定名称。

请注意,若要让Spring自动检测扫描组件,需要在XML中启用<context:component-scan>标签,不同的包既可以选择以公共父包的形式,全部扫描;也可以逐一列举,以逗号分隔。

<context:component-scan base-package="path/to/your/package1,path/to/your/package2">

或者在下面的容器配置注解介绍中,使用@ComponentScan注解等效代替。

更清晰的子注解 @Service、@Controller、@Repositry

@Component的子注解,作用效果与前者相同,只是拥有更清晰的说明性。

  • @Service:标记服务层的组件,表明该类主要用于执行业务逻辑。
  • @Controller:标记控制层组件,主要用于处理 HTTP 请求。
  • @Repository:标记数据访问组件,主要用于封装数据库操作。

1.1 Bean作用域控制 @Scope

对于@Component标记的Bean,可以附加@Scope注解说明作用域。例如:

@Component
@Scope("singleton")
class Service {...}

1.2 Bean生命周期回调 @PostConsruct、@PreDestroy

Java 9已移出Java标准库,需额外导入jakarta,详见注解弃用说明。

用于标记Bean的初始化回调与销毁回调函数。请注意,这个使用方法与XML配置一样,@PreDestroy标记的注册方法仍然需要关闭容器或对容器注册销毁钩子。

2. 容器配置

2.1 定义配置类 @Configuration

为了进一步简化Spring配置,我们可以使用Configuration注解标记的配置类完全替代applicationContext.xml配置文件。首先来看下面这一例子:

@Configuration
@ComponentScan({"com.example.dao", "com.example.service"})
@PropertySource({"config.properties", "config.properties"})
class AppConfig {...}

配置Bean扫描 @ComponentScan

等效替代XML配置中的<context:component-scan>

读取外部配置文件 @PropertySource

请注意,使用注解方式加载的外部配置文件,无法在字符串中使用通配符*。只能一个一个手动加载。或者采用@Bean手动创建导入一个配置解析Bean``,以此实现对配置文件的子集,但是方法这里不做展开。对于配置项,可以在@Value注解中通过${propname}来使用。@Value用于数据注入,其具体用途将在下面的章节中具体介绍。

但是这里要说明一个问题,当你使用@Value("${propname}")注解来引用配置内容时,如果没有该配置项,那么该占位符就会单纯的被视作字符串"${propname}",而不会报错。这显然在一些情况下与我们的预期不符,解决方法:

@Configuration
public class AppConfig {@Beanpublic static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {return new PropertySourcesPlaceholderConfigurer();}
}

如果你想对不存在的值保持严格的控制,你应该声明一个 PropertySourcesPlaceholderConfigurer Bean,使用上述配置可以确保在任何 ${} 占位符无法解析的情况下Spring初始化失败。请注意:当使用 JavaConfig 配置 PropertySourcesPlaceholderConfigurer 时,@Bean 方法必须是 static 的。

2.2 向容器手动注册Bean @Bean

@Bean是一个方法级注解,其作用相当于Spring XML配置中的<bean>标签。@Bean标记的方法将返回的对象自动注册为Spring Bean。这个调用过程正常情况下是注解处理器完成的,一般情况下我们不需要手动调用。

默认情况下,Bean的名字和方法的名字是一样的。我们也可以通过注解形参进行修改。

一个 @Bean 注解的方法可以有任意数量的参数,描述构建该Bean所需的依赖关系。对于其所需要的其他依赖,我们可以通过以下形式提供:

  • 引用类型:如果引用类型是其他Bean的话,可以为该方法添加对应类型的形参,Spring容器在调用该方法时,会根据类型自动为该形参注入值。
  • 字面值:例如创建第三方数据源实例时需要的一些配置,除了可以在该方法中以硬编码的形式写死(不推荐),还可以先在配置类中以字段的形式用@Value注入值,再在该方法中直接使用已注入值的配置类字段。
public class DataSourceConfig {@Value("${jdbc.driver}")String driverClassName;@Value("${jdbc.url}")String url;@Value("${jdbc.username}")String username;@Value("${jdbc.password}")String password;@Bean // @Bean返回的对象将被自动配置为Bean,且还会对参数中的依赖进行自动装配public DataSource dataSource(UserDao userDao) {System.out.println(userDao);DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driverClassName);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}
}

关于@Bean在@Configuration配置类中特性的讨论

虽然我们可以在任何Spring @Component 中使用 @Bean 注解的方法,但是, @Bean 最常被用于 @Configuration Bean。

那么在@Configuration@Component中定义@Bean有什么区别呢?这篇博客讲的很好,可以参考。Spring @Configuration 和 @Component 区别-CSDN博客

首先,@Configuration@Component的子注解,它也会作为Bean被扫描注册。但是,针对@Configuration注解的处理器与一般组件不同。

在处理@Configuration时,Spring使用了CGLIB动态代理增强了被注解的配置类,具体逻辑是拦截所有该类中@Bean方法的调用,使得@Bean方法中对该类其余@Bean方法的调用,全部被转化为getBean方法。也就是说,原本通过直接调用@Bean方法创建Bean实例,而现在变成了从容器中获取已经存在的Bean,只有在不存在时才会真正去调用,这样可以确保一个@Bean方法在同类中被多次调用时返回的是同一个实例。例如:

@Configuration
class AppConfig {@Beanpublic Foo foo() {return new Foo();}@Beanpublic Zoo zoo() {return new Zoo(foo());}@Beanpublic Dummy dummy() {return new Dummy(foo()); // 与zoo中调用foo时,拿到的是同一个Foo Bean}
}

而对于Component注解,除了注解处理器自动注册@Bean方法时注册的那个实例,此外在其他方法中对@Bean方法的一切调用都视为普通的Java方法调用,返回的是不同实例,且不会注册为Bean。

如果希望在@Component注解中,在@Bean方法中描述对同一个类中其他@Bean方法创建的Bean的依赖关系,且希望前后获取同一个实例的话,可以利用自动注入将@Bean方法注册的Bean转化为该类内的一个成员变量,然后在其余需要该实例作为依赖的@Bean方法中,对该变量进行引用。例:

@Configuration
class AppConfig {@Autowiredprivate Foo foo; // 自动注入下面foo()方法注册的Bean@Beanpublic Foo foo() {return new Foo();}@Beanpublic Zoo zoo() {return new Zoo(foo); // 通过同类}@Beanpublic Dummy dummy() {return new Dummy(foo; // 与zoo拿到的是同一个Foo Bean}
}

3. 自动注入

使用@Autowired@Resource注解可以实现自动注入。

3.1 @Autowired

@Autowired注解根据注入点类型进行注入。也就是说,该注解的处理器将在容器中寻找满足注入点类型的Bean实例。下面是@Autowired注解的定义。

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
public @interface Autowired {  boolean required() default true;  
}

可以看到该注解主要可以应用在如下三个点:

  • 作用在构造器上
    等效于XML配置的基于构造器的byType自动注入。
public class Service {private final UserDao userDao;@Autowiredpublic MovieRecommender(UserDao userDao) {this.userDao = userDao;}
}

Note:从Spring Framework 4.3开始,如果目标Bean一开始就只定义了一个构造函数,那么在这样的构造函数上就不再需要 @Autowired 注解。然而,如果有几个构造函数,而且没有主要/默认构造函数,那么至少有一个构造函数必须用 @Autowired 注解,以便指示容器使用哪一个。

  • 作用在一般方法上
    @Autowired既可以使用XML配置中的setter注入,也可以对其它的一般方法使用该注解,效果都是基于被注解的方法对Bean中依赖进行注入。
public class Service {private UserDao userDao;private BookDao bookDao;@Autowiredpublic void prepare(UserDao userDao,BookDao bookDao) {this.userDao = userDao;this.bookDao = bookDao;}// ...
}
  • 作用在字段上
    与XML配置中的自动注入不同,当对一个成员变量使用@Autowired时,不会要求其有可用的构造函数或setter方法。因为@Autowired注解处理器对于字段注解的处理,是通过反射直接赋值的。
public class Service {@Autowiredprivate UserDao userDao;// ...
}

提示:为什么不推荐使用@Autowired直接作用在字段上?
Spring官方不推荐 @Autowire使用在基于字段注入方式,推荐基于构造器注入,主要原因是:字段依赖注入容易引发 NPE 空指针异常,而构造器注入时会进行校验,若果依赖的 bean找不到就会抛出NoSuchBeanDefinitionException。

  • required属性的作用
    默认情况下,当一个给定的注入点没有匹配的候选Bean可用时,自动注入就会失败。默认行为是将注解的方法和字段视为表示必须的依赖关系。你可以改变这种行为,就像下面的例子所展示的那样,通过将其标记为非必需(即通过将 @Autowired 中的 required 属性设置为 false),使框架能够跳过一个不可满足的注入点。
public class Service {@Autowired(required=false)private UserDao userDao; // Null// ...
}

注意:任何给定的Bean类中只有一个构造函数可以在声明 @Autowired的同时将 required 属性设置为 true,表示该构造函数将用作Spring Bean自动注入的首选项。如果有多个构造函数声明该注解,它们都必须声明 required=false,才能被视为自动注入的候选者(类似于XML中的 autowire=constructor)。

满足注入点类型要求的候选Bean不止一个怎么办?

因为按类型自动注入可能会导致多个候选者,所以经常需要对选择过程进行更多的控制。

1. @Primary指定优先注入

实现这一目标的方法之一是使用Spring的 @Primary 注解。@Primary 表示,当多个Bean是自动注入到一个单值(single value)依赖的候选者时,应该优先考虑一个特定的Bean。如果在候选者中正好有一个主要(primary)Bean存在,它就会成为自动注入的值。

在下面的例子中,我们在配置类中声明了两个类型均为BookDao的Bean,那么在其余组件使用@Autowired进行自动注入时,将会优先选择被@Primary标记的Bean作为注入源。

class AppConfig{@Bean@PrimaryBookDao bookDao1() { /* ...*/ }@BeanBookDao bookDao2() { /* ...*/ }
}
2. @Qualifier指定按名称注入

另一种对多个候选者进行控制的方法是使用@Qualifier,从而找到候选者中名字符合要求的Bean。请注意,是先根据类型选择候选者,然后再在其中选择名字正确的Bean。

对于作用在字段上的@Autowired,可以直接跟在其下方。

class Service {@Autowired@Qualifier("bookDao2")BookDao bookDao; // 注入名为bookDao2的Bean
}

对于作用在方法上的@Autowired,也可以直接修饰其函数参数。

class Service {private BookDao bookDao; // 注入名为bookDao2的Beanpublic void setBookDao(@Qualifier("bookDao2") BookDao bookDao){this.bookDao = bookDao;}
}

3.2 @Resource

Spring还支持通过在字段或setter方法上使用JSR-250 @Resource 注解进行自动注入。@Resource 需要一个 name 属性。默认情况下,Spring将该值解释为要注入的Bean名称。

class Service {private BookDao bookDao; // 注入名为bookDao2的Bean@Resource(name = "bookDao2")public Service(BookDao bookDao){this.bookDao = bookDao;}
}

3.3 @Autowired与@Resource区别总结

  1. 来源
  • @Autowired是Spring提供的注解。
  • @Resource是JavaEE提供的注解,只不过Spring对该注解提供了相应的注解处理器。JDK 6到8内置在javax中,而JDK 9之后,必须导入jakarta.annoration才能正常使用。
  1. 作用方式
  • @AutowiredbyType的,可以通过@Qualifier限定名称。
  • @ResourcebyName的。
  1. 作用范围
  • @Autowired对于构造器和一般方法的处理模式不同。对于单构造器,可以不写@Autowired。
  • @Resource的元注解中关于方法的作用范围只有ElementType.METHOD,而没有单独的ElementType.CONSTRUCTOR。也就是说@Resource无法应用于构造函数。
  1. 语义
  • @Autowired是先按照类型获取候选,当候选项大于1个时,会抛NoUniqueBeanDefinitionException。此时应当选择使用@Qualifer或@Primary等机制进行筛选。如果@Qualifier提供的名字没有符合的值的话,会抛NoSuchBeanDefinitionException
  • @Resource则直接是按照名字寻找。找到的名字如果不符合类型要求,则会抛BeanNotOfRequiredTypeException

4. 注解弃用说明

@Resource一样,@PostConstruct@PreDestroy 注解类型在JDK 6到8中是标准Java库的一部分。然而,整个 javax.annotation 包在JDK 9中从核心Java模块中分离出来,最终在JDK 11中被删除。从Jakarta EE 9开始,该包现在住在 jakarta.annotation 中。如果需要,现在需要通过Maven中心获得 jakarta.annotation-api 工件,只需像其他库一样添加到应用程序的classpath中即可。 ---- Spring文档

Jakarta[dʒəˈkɑrtə] 名称的选用:这个名字来源于 Apache Jakarta 项目,这是一个由 Apache 软件基金会创建的开源项目,用于开发多个开源的 Java 解决方案和库。Apache Jakarta 项目开始于 1999 年,由于它是 Java 开源生态中的一个重要部分,这个名称被选用来表示新的 Java EE 未来。在 2017 年,Oracle 宣布将 Java EE 的控制权转移给了 Eclipse 基金会,这是一个全球性的非盈利开源软件开发社区。随着这个转让,需要一个新的名称来表示这个平台的新时代。最终,“Jakarta EE” 被选择作为新的品牌,来继续 Java EE 的遗产,同时标志着控制权和治理结构的改变。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 代码随想录算法训练营第四十六天|回文子串、最长回文子序列
  • 算法急救LeetCode62题-python版(1)/ 数组、链表
  • 电子管的检测
  • day17:一文弄懂“无参装饰器”、“有参装饰器”和“叠加装饰器”
  • Echarts添加水印
  • 第八季完美童模全球人气冠军【夏沛然】荣耀加冕 见证星芒风采!
  • 【运维】Linux如何解压.rar文件
  • 微前端架构入门
  • uniapp left right 的左右模态框
  • webrtc ns 降噪之粉红噪声参数推导
  • 微软Edge浏览器
  • 2024数据泄露事件增涨迅猛,我们决不能坐以待毙!
  • day37-https实战
  • 【python数据分析11】——Pandas统计分析(分组聚合进行组内计算)
  • Nuxt3【路由中间件】middleware
  • CAP 一致性协议及应用解析
  • co模块的前端实现
  • Django 博客开发教程 8 - 博客文章详情页
  • Hexo+码云+git快速搭建免费的静态Blog
  • Javascripit类型转换比较那点事儿,双等号(==)
  • JavaScript的使用你知道几种?(上)
  • JS变量作用域
  • Laravel 中的一个后期静态绑定
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • mockjs让前端开发独立于后端
  • Redis 懒删除(lazy free)简史
  • spark本地环境的搭建到运行第一个spark程序
  • vue中实现单选
  • 安装python包到指定虚拟环境
  • 多线程 start 和 run 方法到底有什么区别?
  • 计算机在识别图像时“看到”了什么?
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 力扣(LeetCode)21
  • 前端
  • 前端 CSS : 5# 纯 CSS 实现24小时超市
  • 深度学习入门:10门免费线上课程推荐
  • 腾讯优测优分享 | Android碎片化问题小结——关于闪光灯的那些事儿
  • 想使用 MongoDB ,你应该了解这8个方面!
  • 在Mac OS X上安装 Ruby运行环境
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • 长三角G60科创走廊智能驾驶产业联盟揭牌成立,近80家企业助力智能驾驶行业发展 ...
  • ​​​​​​​开发面试“八股文”:助力还是阻力?
  • ​LeetCode解法汇总1410. HTML 实体解析器
  • ‌JavaScript 数据类型转换
  • # Maven错误Error executing Maven
  • #我与Java虚拟机的故事#连载18:JAVA成长之路
  • (01)ORB-SLAM2源码无死角解析-(66) BA优化(g2o)→闭环线程:Optimizer::GlobalBundleAdjustemnt→全局优化
  • (10)ATF MMU转换表
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (实战篇)如何缓存数据
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转)memcache、redis缓存
  • (转)菜鸟学数据库(三)——存储过程
  • *** 2003
  • **PHP分步表单提交思路(分页表单提交)