Spring 04: 注解开发

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 {

    @Bean
    public 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 {
	@Bean
	public Foo foo() {
		return new Foo();
	}
	@Bean
	public Zoo zoo() {
		return new Zoo(foo());
	}
	@Bean
	public 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 {
	@Autowired
	private Foo foo; // 自动注入下面foo()方法注册的Bean
	@Bean
	public Foo foo() {
		return new Foo();
	}
	@Bean
	public Zoo zoo() {
		return new Zoo(foo); // 通过同类
	}
	@Bean
	public 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;

    @Autowired
    public 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;

    @Autowired
    public void prepare(UserDao userDao,
            BookDao bookDao) {
        this.userDao = userDao;
        this.bookDao = bookDao;
    }
    // ...
}
  • 作用在字段上
    与XML配置中的自动注入不同,当对一个成员变量使用@Autowired时,不会要求其有可用的构造函数或setter方法。因为@Autowired注解处理器对于字段注解的处理,是通过反射直接赋值的。
public class Service {
	@Autowired
    private 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
	@Primary
	BookDao bookDao1() { /* ...*/ }

	@Bean
	BookDao bookDao2() { /* ...*/ }
}
2. @Qualifier指定按名称注入

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

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

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

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

class Service {
	private BookDao bookDao; // 注入名为bookDao2的Bean
	public 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 的遗产,同时标志着控制权和治理结构的改变。

  • 23
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值