基于注解的配置
在bean.xml
中配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample"/> <bean id="beanProvider" class="com.ajn.spring.beanfactory.bean.BeanProvider"/> </beans>
配置只能在该配置所在容器的bean中寻找注解,如果你在
DispatcherServlet
中一个WebApplicationContext
中配置,它只能寻找到Controller中的注解,不会找到Service中的注解。
3.1 @Autowired
@Autowired
注解提供的功能和前面讲的自动注入(autowire)功能一样。重新修改两个类:
// 被注入的bean public class BeanProvider { public void sayHello(String name, Integer age) { System.out.println("name = [" + name + "], age = [" + age + "]"); } } // 通过属性注入 public class BeanExample { @Autowired private BeanProvider beanProvider; public void run() { String name = "ajn"; Integer age = 24; this.beanProvider.sayHello(name, age); } } // 通过构造器方法注入 public class BeanExample { private BeanProvider beanProvider; @Autowired public BeanExample(BeanProvider beanProvider) { this.beanProvider = beanProvider; } public void run() { String name = "ajn"; Integer age = 24; this.beanProvider.sayHello(name, age);
运行主方法类输出结果可知,beanProvider
已经被注入了。@Autowired
注解不仅能在字段上注入bean,还可以用其他方式:
- 在构造器方法上使用
- 在任何方法上使用,只要该方法参数中有被注入的bean
- 在任何方法的参数前使用,该参数为被注入的bean
所以,总的来讲就是使用该注解可以把bean注入到另一个的bean的字段中,或方法的参数中,这些方法不局限于构造器方法或setter方法。
默认情况下自动注入的bean没有候选者,程序就会因为自动注入失败而报异常。当一个bean注入是否成功对程序没有什么影响,你可以在该注解上添加属性:@Autowired(required = false)
来避免报异常。
3.2 @Qualifier
前面说过通过类型自动注入时,当有多个候选者bean都符合注入的要求时,比如一个接口有多个实现类,这时Spring不知道会注入哪一个,这时可以在一个bean上的 标签上添加
primary="true"
属性来确定主要候选者(参考:2.1.3)。
这是一种解决方案,还有另一种方案就是在@Autowired
注解后使用@Qualifier
注解来指定候选者,也就是指定某个需要注入的bean。
可以指定bean的名称:
public class BeanExample { @Autowired @Qualifier("beanProvider") private BeanProvider beanProvider; // ... }
可以自定义Qualifier名称:
<bean class="example.SimpleMovieCatalog"> <qualifier value="main"/> </bean>
public class MovieRecommender { @Autowired @Qualifier("main") private MovieCatalog movieCatalog; // ... }
使用@Qualifier
可以更细粒度地选择bean的注入候选者,上面演示的是在XML中标签下配置子标签
来配置名称,当基于注解配置bean时,可以在类上面使用该注解来配置qualifier名称,如下:
@Component @Qualifier("Action") public class ActionMovieCatalog implements MovieCatalog { // ... }
3.3 @Resource
@Resource
注解作用和@Autowired
作用类似,它是JDK自带的注解,只不过它默认是通过bean名称注入,也就是我们前面讲过的byName
自动注入方式。
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource(name="myMovieFinder") public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
如果不指定name
属性,注解在字段上则会取字段名,注解在setter方法上则会取bean的属性名称:
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
@Resource
默认会通过名称注入bean,如果找不到对应名称的bean,才会通过类型注入,但一但指定name
属性,则只会通过名称注入。@Autowired
注解只能通过类型注入,要想实现通过名称注入,则要结合@Qualifier
注解使用。
新版本的Spring中的 @Autowired
也支持字段名称注入。
3.4 @Value
在1.7节我们讲了使用外部属性值,使用@Value
在基于注解的情况下注入属性值:
@Component public class BeanExample { @Value("${name}") private String name; @Value("${age}") private Integer age; @Resource private BeanProvider beanProvider; public void run() { this.beanProvider.sayHello(this.name, this.age); } }
3.5 @PostConstruct和@PreDestroy
在前面讲生命周期的时候说到了配置自定义初始化方法init-method
和销毁方法destroy-method
,在基于注解配置的情况下,我们使用@PostConstruct
和@PreDestroy
来指定自定义初始化方法和销毁方法:
public class BeanExample { // ... @PostConstruct public void init() { System.out.println("BeanExample.init"); } @PreDestroy public void destroy() { System.out.println("BeanExample.destroy"); } }
3.6 @Component
前面我们都是使用XML方式来配置元数据,定义一个bean的,即便前面讲了好多使用注解方式配置,但bean的定义依然是在XML文件中配置的,本节介绍通过注解指定并通过扫描类路径来检测候选组件。只需将bean.xml
配置改写成:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--包名指定自己代码的包全路径--> <context:component-scan base-package="com.ajn.spring"/> </beans>
包含了
的功能,所以我们只需配置一个。
修改类为:
import org.springframework.stereotype.Component; import javax.annotation.Resource; @Component public class BeanProvider { public void sayHello(String name, Integer age) { System.out.println("name = [" + name + "], age = [" + age + "]"); } } @Component public class BeanExample { @Resource private BeanProvider beanProvider; public void run() { String name = "ajn"; Integer age = 24; this.beanProvider.sayHello(name, age); } }
Spring还提供了同样功能的注解@Repository
、@Service
和 @Controller
,它们用于区分分层开发模型中bean的类型,比如:@Repository
用于数据持久层,它可以自动转换异常,@Service
用于业务层,@Controller
用于控制层。
使用这些注解创建的bean的名称默认是类名转化成小驼峰的形式,例如:BeanProvider
的bean的名称是beanProvider
,也可以自己指定bean的名称:
@Component("beanProvider") public class BeanProvider { public void sayHello(String name, Integer age) { System.out.println("name = [" + name + "], age = [" + age + "]"); } }
3.7 @Scope
基于注解配置bean时也可以使用注解@Scope
来指定作用域,例如使用原型模式:
@Component @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class BeanProvider { public void sayHello(String name, Integer age) { System.out.println("name = [" + name + "], age = [" + age + "]"); } }
基于Java的配置
4.1 @Configuration
这两个注解用于基于Java方式注册bean,下面是基于XML配置的bean:
<beans> <bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample"/> <bean id="beanProvider" class="com.ajn.spring.beanfactory.bean.BeanProvider"/> </beans>
替换成基于Java配置的形式如下:
@Configuration public class AppConfig { @Bean public BeanExample beanExample() { return new BeanExample(); } @Bean public BeanProvider beanProvider() { return new BeanProvider(); } }
修改主方法类:
- 通过
AnnotationConfigApplicationContext
简单启动
public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); BeanExample beanExample = applicationContext.getBean(BeanExample.class); beanExample.run(); applicationContext.close(); }
2、通过register(Class…)
方法构建容器
public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.register(AppConfig.class); applicationContext.refresh(); BeanExample beanExample = applicationContext.getBean(BeanExample.class); beanExample.run(); applicationContext.close(); }
4.2 @Bean
前面说过@Bean
注解可以用于基于Java定义bean,下面是该注解的详细用法:
- 参数自动注入,使用注解的方法可以有任意个参数,依赖的bean都会通过参数注入,比如:
BeanExample
依赖BeanProvider
,可以通过如下方式使用参数自动依赖
@Configuration public class AppConfig { @Bean public BeanExample beanExample(BeanProvider beanProvider) { BeanExample beanExample = new BeanExample(); beanExample.setBeanProvider(beanProvider); return beanExample; } @Bean public BeanProvider beanProvider() { return new BeanProvider(); } }
- 添加属性
initMethod
和destroyMethod
使用生命周期方法
@Configuration public class AppConfig { @Bean(initMethod = "init", = "destroy") public BeanExample beanExample() { return new BeanExample(); } } // 在类中添加方法 public class BeanExample { // ... private void init() { System.out.println("BeanExample.init"); } private void destroy() { System.out.println("BeanExample.destroy"); } }
- 默认情况下使用该注解所在的方法名作为bean的名称,添加属性
name
可以另行配置
@Configuration public class AppConfig { @Bean(name = "myThing") public Thing thing() { return new Thing(); } }
- 可以结合
@Profile
、@Scope
、@Lazy
、@DependsOn
和@Primary
等注解使用
@Configuration public class AppConfig { @Bean @DependsOn("beanProvider") @Scope(BeanDefinition.SCOPE_SINGLETON) @Primary @Profile("prod") @Lazy public BeanExample beanExample() { return new BeanExample(); } @Bean public BeanProvider beanProvider() { return new BeanProvider(); } }
@Profile
用来区分环境,我们后面会讲到,表示只有在某个环境下才实例化当前bean@Scope
可以用来配置单例模式还是原型模式(见1.5)@Lazy
表示懒加载(见1.6)@DependsOn
指定依赖关系(见1.4)@Primary
定义该bean为依赖的主要候选者(见1.3)
4.3 @ComponentScan
基于XML方式配置使用注解@Component
、@Repository
、@Service
和 @Controller
创建bean的配置如下:
<beans> <context:component-scan base-package="com.ajn.spring"/> </beans>
修改为使用@ComponentScan
注解配置:
@Configuration @ComponentScan("com.ajn.spring") public class AppConfig { }
也可以在主方法中使用scan(String…)
方法来实现这个功能:
public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.scan("com.ajn.spring"); applicationContext.refresh(); BeanExample beanExample = applicationContext.getBean(BeanExample.class); beanExample.run(); applicationContext.close(); }
4.4 @Import
在基于XML配置时,我们使用标签来导入另一个
bean.xml
文件,在程序设计的时候,可以根据模块来定义不同的bean.xml
文件。在使用基于Java配置时,可以使用@Import
注解来导入另一个bean配置类:
@Configuration public class ConfigA { @Bean public A a() { return new A(); } } @Configuration @Import(ConfigA.class) public class ConfigB { @Bean public B b() { return new B(); } }
这样处理的话,就不需要在主方法里面实例化容器的时候配置两个类了,上面的例子只需要配置ConfigB
就可以。
4.5 @Conditional
有时我们需要会有条件的启用或禁用@Configuration
注解的类和@Bean
注解的方法,基于一些系统状态而对bean是否注册的控制。此时可以使用@Conditional
来完成这个功能,新建一个类实现Spring提供的Condition接口:
public class TestCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return false; } }
在@Configuration
注解下或@Bean
注解下使用@Conditional
注解并使用前面的类:
@Configuration @Conditional(TestCondition.class) public class AppConfig { @Bean public BeanExample beanExample() { return new BeanExample(); } @Bean public BeanProvider beanProvider() { return new BeanProvider(); } }
此时,matches()
方法返回false
时AppConfig
类就不会生效,两个bean也不会被注册进容器,返回true
时才能正常注册。
4.6 @ImportResource
在Spring项目中可以组合使用基于Java和基于XML的配置。
4.6.1 基于XML配置为主
基于XML配置的Spring容器要使用@Configuration
注解,只需把声明该注解的类当作普通的bean即可,例如存在如下类:
@Configuration public class AppConfig { @Autowired private DataSource dataSource; @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } @Bean public TransferService transferService() { return new TransferService(accountRepository()); } }
要想使上面的配置类生效,只要system-test-config.xml
在配置文件中声明bean:
<beans> <!-- enable processing of annotations such as @Autowired and @Configuration --> <context:annotation-config/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="com.acme.AppConfig"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
此时主方法加载XML配置:
public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml"); TransferService transferService = ctx.getBean(TransferService.class); // ... }
查看@Configuration
源码,可以看出它使用了元注解@Component
,所以我们可以使用,通过扫描配置类所在包来创建bean。将XML文件修改如下:
<beans> <!-- picks up and registers AppConfig as a bean definition --> <context:component-scan base-package="com.acme"/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
4.6.2 基于Java配置为主
基于Java配置的Spring容器要使用XML文件配置,很简单,只需使用@ImportResource
注解来导入XML文件,例如:
@Configuration @ImportResource("classpath:/com/acme/properties-config.xml") public class AppConfig { @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource() { return new DriverManagerDataSource(url, username, password); }
properties-config.xml <beans> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> </beans>
此时主方法加载注解启动:
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); TransferService transferService = ctx.getBean(TransferService.class); // ...