Table of Contents
Spring注解
@Bean
@Bean注解是方法级别的注解(method-level)。并且是XML的<bean/>元素的直接模拟。@Bean注解支持<bean/>提供的其中一些属性,如:init-method, detroy-method,autowiring等。
我们可以在@Configuration-annotated或@Component-annotate的类中使用@Bean注解。
声明一个bean
要声明一个bean,可以使用@Bean注解对方法进行批注。 可以使用此方法在指定为方法返回值的类型的ApplicationContext中注册bean定义。 默认情况下,bean名称与方法名称相同。例如:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
上述配置与以下Spring XML完全等效:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
这两个声明都在ApplicationContext中创建了一个名为transferService的bean,绑定到TransferServiceImpl类型的对象实例,如下面的文本所示:
transferService -> com.acme.TransferServiceImpl
还可以使用接口(或基类)作为返回类型声明@Bean方法,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
Bean依赖 - Bean Dependencies
@ Bean-annotated的方法可以有任意数量的参数来描述构建该bean所需的依赖关系。 例如,如果我们的TransferService需要AccountRepository,我们可以使用方法参数来实现该依赖项,如下:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
其解析机制与基于构造函数的依赖注入非常相似。
获取bean的生命周期回调:
使用@Bean批注定义的任何类都支持常规生命周期回调,并且可以使用JSR-250规范中的@PostConstruct和@PreDestroy注释。常规的Spring生命周期回调也是完全支持。 如果bean实现InitializingBean,DisposableBean或Lifecycle,则它们各自的方法由容器调用。
@Bean注释支持指定任意初始化和销毁回调方法,就像Spring XML上的init-method和destroy-method属性一样,如
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
指定Bean的scope
Spring包含@Scope注释,以便您可以指定bean的scope。你可以指定使用@Bean注解定义的bean应具有的特定scope。
默认的bean的scope是单例,但你可以使用@Scope注释覆盖它,如以下示例所示
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Configuration
@ConfiConfiguration这个注解由四个注解组成,分别是:@Target(ElementType.TYPE) , @Retention(RetentionPolicy.RUNTIME), @Documented, @Component。
@Configuration是一个类级注释,指示对象是bean定义的源。 @Configuration类通过用@Bean注解来注释public方法声明bean。 在@Configuration类上调用@Bean方法也可用于定义bean间依赖项。
其定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
/**
* Explicitly specify the name of the Spring bean definition associated
* with this Configuration class. If left unspecified (the common case),
* a bean name will be automatically generated.
* <p>The custom name applies only if the Configuration class is picked up via
* component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
* If the Configuration class is registered as a traditional XML bean definition,
* the name/id of the bean element will take precedence.
* @return the suggested component name, if any (or empty String otherwise)
* @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
其作用是,表示一个类声明了一个或多个@Bean方法,并且可以由Spring容器处理,以便在运行时为这些bean生成bean定义和服务请求。例如
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
// instantiate, configure and return bean ...
}
}
注入bean间依赖关系
当bean彼此依赖时,表达该依赖关系就像让一个bean方法调用另一个bean一样简单,如下例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
在前面的示例中,beanOne通过构造函数注入接收对beanTwo的引用。
有关基于Java的配置如何在内部工作的更多信息
请考虑以下示例,该示例显示了被调用两次的@Bean注释方法:
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
clientDao()在clientService1()中调用一次,在clientService2()中调用一次。 由于此方法创建了ClientDaoImpl的新实例并将其返回,因此可能我们会认为应该会有两个实例(每个服务一个)。 这肯定会有问题:在Spring中,实例化的bean默认具有单例的scope。 在spring当中,所有@Configuration类在启动时都使用CGLIB进行子类化, 在子类中,子方法在调用父方法并创建新实例之前,首先检查容器是否有任何缓存(作用域)bean。这就是魔术发生的地方。
当然,根据bean的scope不同,行为可能会有所不同。 我们在这里讨论的是单例模式
@Autowired
您可以将@Autowired注解应用于构造函数,如以下示例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
您还可以将@Autowired注释应用于“传统”setter方法,如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
您还可以将注释应用于具有任意名称和多个参数的方法,如以下示例所示:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
您也可以将@Autowired应用于字段,甚至将其与构造函数混合,如以下示例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
您还可以通过将注释添加到需要该类型数组的字段或方法,从ApplicationContext提供特定类型的所有bean,如以下示例所示:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
这同样适用于类型化集合,如以下示例所示
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
只要预期的键类型是String,即使是类型化的Map实例也可以自动装配。 Map值包含所有期望类型的bean,并且键包含相应的bean名称,如以下示例所示
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默认情况下,只要当只有0个候选的bean可用时,自动装配就会失败。 spring的默认行为是将带注释的方法,构造函数和字段视为指示所需的依赖项。 您可以在以下示例中更改此行为:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
@Autowired,@ Inject,@ Resource和@Value注释由Spring BeanPostProcessor实现处理。 这意味着您无法在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有)中应用这些注释。 必须使用XML或Spring @Bean方法显式地“连接”这些类型。
使用@Autowired的一个缺点是,不够直观,从代码当中我们并不能看出我们注入的Bean的声明在何处。
@Primary
由于按类型自动装配可能会导致多个候选人candiate,因此通常需要对选择过程有更多控制权。@Primary是其中一个方法。@Primary表示当多个bean可以自动装配到单值依赖项时,应该优先选择特定的bean。如果候选者中只存在一个主bean,则它将成为自动装配的值。
请考虑以下配置代码,将firstMovieCatalog定义为主要primary的MovieCatalog:
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
使用上述配置,以下MovieRecommender将与firstMovieCatalog一起自动装配:
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
@Qualifer
在使用具有多个实例的类型进行自动装配,确定一个主要候选者bean时,使用@Primary是一种有效的方法。当我们需要更多的控制选择候选者bean的过程时,可以使用Spring的@Qualifier注释。
我们可以将限定符值与特定参数相关联,缩小类型匹配集,以便为每个参数选择特定的bean。 在最简单的情况下,这可以是一个简单的描述性值,如以下示例所示:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
以下示例显示了相应的bean定义
<?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 class="example.SimpleMovieCatalog">
<qualifier value="main"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
对于fallback的默认匹配,bean名称被视为默认限定符值。
@Value
要指定默认值,可以在字段,方法和方法或构造函数参数上放置@Value注释。以下示例设置字段变量的默认值:
public static class FieldValueTestBean
@Value("#{ systemProperties['user.region'] }")
private String defaultLocale;
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
以下示例显示了相同效果但在属性的setter方法上的用法:
public static class PropertyValueTestBean
private String defaultLocale;
@Value("#{ systemProperties['user.region'] }")
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
@Autowired方法和构造函数也可以使用@Value注释,如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
private String defaultLocale;
@Autowired
public void configure(MovieFinder movieFinder,
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
this.movieFinder = movieFinder;
this.defaultLocale = defaultLocale;
}
// ...
}
public class MovieRecommender {
private String defaultLocale;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
@Value("#{systemProperties['user.country']}") String defaultLocale) {
this.customerPreferenceDao = customerPreferenceDao;
this.defaultLocale = defaultLocale;
}
// ...
}
@Profile以及@ActiveProfile
@profile注解是spring提供的一个用来标明当前运行环境的注解。基于某些任意系统状态,有条件地启用或禁用完整的@Configuration类甚至单独的@Bean方法通常很有用。 一个常见的例子是只有在Spring环境中启用了特定的配置文件时才使用@Profile批注来激活bean。
@Profile注释实际上是通过使用更灵活的注释@Conditional实现的。 @Conditional批注指示在注册@Bean之前应该参考的特定org.springframework.context.annotation.Condition实现。
Condition接口的实现提供了一个返回true或false的matches(...)方法。 例如,以下清单显示了用于@Profile的实际Condition实现:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}
Bean定义配置文件在核心容器中提供了一种机制,允许在不同环境中注册不同的bean。 “环境”这个词对不同的用户来说意味着不同的东西,这个功能可以帮助解决许多的问题,包括:
- 在QA或生产环境中,针对开发中的内存数据源而不是从JNDI查找相同的数据源。
- 仅在将应用程序部署到性能环境时注册监视基础结构。
- 为客户A和客户B部署注册bean的自定义实施。
考虑需要DataSource的实际应用程序中的第一个用例。 在测试环境中,Datasource配置可能类似于以下内容:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
现在考虑如何将此应用程序部署到QA或生产环境中,假设应用程序的数据源已注册到生产应用程序服务器的JNDI目录。 我们的dataSource bean现在看起来如下:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
现在我们面临的问题是如何根据当前环境在使用这两种数据源之间切换。 随着时间的推移,Spring用户已经设计了许多方法来完成这项工作,通常依赖于系统环境变量和包含$ {placeholder}标记的XML <import />语句的组合,这些标记根据值解析为正确的配置文件路径 一个环境变量。 Bean定义配置文件是核心容器功能,可为此问题提供解决方案。
使用@Profile
@Profile注释允许我们指明当一个或多个指定的配置文件处于活动状态时,组件符合的注册条件。 使用前面的示例,我们可以重写dataSource配置,如下所示:
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
配置文件字符串可以包含简单的配置文件名称(例如,生产)或配置文件表达式。 概要表达式允许表达更复杂的概要逻辑(例如,production & us-east)。 配置文件表达式支持以下运算符:
!
: A logical “not” of the profile
&
: A logical “and” of the profiles`|`_ A logical “or” of the profiles
Default Profile
默认配置文件表示默认启用的配置文件。 请考虑以下示例:
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
如果没有激活配置文件,则创建dataSource。 您可以将此视为一种为一个或多个bean提供默认定义的方法。 如果启用了任何配置文件,则默认配置文件不适用。
您可以使用环境上的setDefaultProfiles()或声明性地使用spring.profiles.default属性更改默认配置文件的名称。
基于Java的配置
Spring允许我们使用基于Java的注释功能,这可以降低配置的复杂性。
@Import
就像在Spring XML文件中使用<import />元素来帮助模块化配置一样,@ Immort注释允许从另一个配置类加载@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();
}
}
现在,在实例化上下文时,不需要同时指定ConfigA.class和ConfigB.class,只需要显式提供ConfigB即可,如下例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
这种方法简化了容器实例化,因为只需要处理一个类,而不需要在构造期间记住可能出现的大量的@Configuration类
AOP注解
@Before
你可以使用@Before注释在aspect的声明前置advice:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
如果我们使用就地切入点表达式,我们可以重写前面的示例,如下例所示:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
@AfterReturning
返回建议后,匹配的方法执行正常返回。 您可以使用@AfterReturning注释声明它:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
@AfterThrowing
抛出建议运行时,匹配的方法执行通过抛出异常退出。 您可以使用@AfterThrowing注释声明它,如以下示例所示
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
}
@After
在匹配的方法执行退出之后(finally)建议运行之后。 它是使用@After注释声明的。 在建议之后必须准备好处理正常和异常返回条件。 它通常用于释放资源和类似目的。 以下示例显示了在finally建议之后如何使用:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
}
@Around
增强处理是功能比较强大的增强处理,它近似等于Before 和 AfterReturning的总和。@Around既可在执行目标方法之前织入增强动作,也可在执行目标方法之后织入增强动作。@Around甚至可以决定目标方法在什么时候执行,如何执行,更甚者可以完全阻止目标方法的执行。
使用@Around注释声明around建议。 advice方法的第一个参数必须是ProceedingJoinPoint类型。 在通知的主体内,在ProceedingJoinPoint上调用proceed()会导致执行基础方法。 proceed方法也可以传入Object []。 数组中的值在进行时用作方法执行的参数。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}