通过注解的形式配置容器,可以摆脱XML配置的约束。其@Bean和@Configuration这两个注解为核心注解。
@Configuration注解在类上表明为一个配置类(其本身也是一个组件Bean),相当于一个XML配置文件。@Bean注解在方法上表明此方法用于Bean的配置和初始化,相当于< bean />和@Component。基于Java的容器配置:
@Configuration public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } }XML配置:
<beans> <bean id="myService" class="com.acme.services.MyServiceImpl"/> </beans>通过使用适用于基于Java的容器配置的容器实现类AnnotationConfigApplicationContext,此实现类不仅能接受@Configuration注解形式的配置,还可以使用@Component和JSR-330标准的注解。
public static void main(String[] args) { //AppConfig为配置类 ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }你也可以通过编程方式配置容器。
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); //register方法用于注册配置类 ctx.register(AppConfig.class, OtherConfig.class); ctx.register(AdditionalConfig.class); //refresh方法用于刷新 ctx.refresh(); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }前面讲到AnnotationConfigApplicationContext是支持@Component注解标识组件的,下面有两种方式配置组件包扫描。
注解形式:
@Configuration @ComponentScan(basePackages = "org.bean") public class AppConfig { ... }编程形式:
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan("org.bean"); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); }在编程形式的例子里,若@Configuration配置类在com.acme包中,配置类中的所有@Bean组件都将被注册在容器中进行处理。
@Bean是一个方法级的注解,相当于标签<bean/ >。
其属性有:
name:指定Bean名。autowire:指定自动装配模式(不推荐使用)autowireCandidate:指定该Bean能否被其他Bean自动装配(默认true)initMethod:指定初始化回调方法destoryMethod:指定销毁回调方法要注册一个Bean,通过@Bean注解在方法上,方法的返回类型定义Bean的类型(也可以使用接口或父类),通常Bean名为方法名。
@Configuration public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } }若该Bean有多个接口,或者这个接口有多个实现类,建议用具体类型。
Bean的依赖通过方法参数进行注入(默认注入方式为自动装配,所以无需添加@Autowired)。
@Configuration public class AppConfig { @Bean public MyService myService(MyDao myDao) { return new MyServiceImpl(myDao); } }你也可以调用@Bean方法来注入:
@Configuration public class AppConfig { @Bean public MyDao myDao() { return new MyDaoImpl(); } @Bean public MyService myService() { return new MyServiceImpl(myDao()); } }@Bean支持任何形式的生命周期回调方式,如JSR-250的@PostConstruct和@PreDestroy注解,或者是实现接口的方式实现回调方法。
当然,@Bean的属性同样可以指定生命周期回调方法。
@Configuration public class AppConfig { @Bean(initMethod = "init", destroyMethod = "destroy") public MyService myService(MyDao myDao) { return new MyServiceImpl(myDao); } }你可以给Bean声明指定名称,或者多个别名。
@Configuration public class AppConfig { @Bean(name = "myService") //@Bean({"a", "b", "c"}) 多个别名 public MyService myService(MyDao myDao) { return new MyServiceImpl(myDao); } }@Bean通过和@Scope组合实现指定作用域,默认为单例。
其属性:
scopeName:指定作用域,可通过ConfigurableBeanFactory或WebApplicationContext获取常量。
proxyMode:指定作用域代理模式,可通过ScopedProxyMode获取常量。
@Configuration public class AppConfig { @Bean @Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON, proxyMode = ScopedProxyMode.NO) public MyService myService(MyDao myDao) { return new MyServiceImpl(myDao); } }通过@Description对Bean进行描述。
@Configuration public class AppConfig { @Bean @Description("Provides a bean") public MyService myService(MyDao myDao) { return new MyServiceImpl(myDao); } }此外,你还可以组合@Lazy和@DependsOn注解来实现延迟加载和控制Bean加载顺序。
你可以使用@Import注解加载另一个配置类,实现模块化配置。
当然,在XML配置中你可以使用<import/ >标签实现,但只能导入XML配置文件,导入配置类只需常规的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类即可。
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); A a = ctx.getBean(A.class); B b = ctx.getBean(B.class); }Spring 4.2以后,@Import注解也支持加载常规组件(如@Component),如果你不想用包扫描,你可以使用此注解加载所有组件。
由于@Configuration配置类本身也是一个组件Bean,那么它也具有常规组件的所有特性,如:使用@Autowired和@Value进行注入。
@Configuration @Import({BConfig.class, CConfig.class}) public class AConfig { @Autowired private AccountRepository accountRepository; @Bean public TransferService transferService() { return new TransferServiceImpl(accountRepository); } } @Configuration public class BConfig { private final DataSource dataSource; //在单一构造方法场景下,即使不使用@Autowired注解,也会被用来进行自动装配。 public RepositoryConfig(DataSource dataSource) { this.dataSource = dataSource; } @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } } @Configuration public class CConfig { @Bean public DataSource dataSource() { return new DataSource } }在@Configuration配置类中使用@Autowired和@Value进行注入要确保其依赖项是最简单的一种,由于@Configuration配置类在容器初始化过程中很早就被处理了,强制使用@Autowired和@Value注入可能会导致依赖项过早初始化。
所以,如果可以,尽量使用参数自动注入的方式。
在上一个例子中,通过@Autowired进行了自动装配,但是却无法知道Bean的定义具体的声明位置,在IDEA中你可以通过代码左侧的引导工具来找到。
当然,你无法接受这种模糊性,你可以直接通过自动装配配置类本身来消除这种模糊性。
@Configuration public class AConfig { @Autowired private BConfig bConfig; @Bean public TransferService transferService() { return new TransferServiceImpl(bConfig.accountRepository()); } }这种方式,成功的消除了模糊性,但也带来了一定问题,它使AConfig类和BConfig类之间具有了较强的耦合性。但是,我们或许可以通过接口或者抽象BConfig类来降低这种耦合度。
@Configuration public class AConfig { @Autowired private BConfig bConfig; @Bean public TransferService transferService() { return new TransferServiceImpl(bConfig.accountRepository()); } } @Configuration public interface BConfig { @Bean AccountRepository accountRepository(); } @Configuration public class BConfigImpl implements BConfig { @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(...); } }通过@Profiles注解实现在不同环境下注册不同的Bean。
@Configuration @Profile("development") public class AConfig { @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 BConfig { @Bean public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } }而@Profiles实际上是使用@Conditional(ProfileCondition.class)实现的,ProfileCondition类实现Condition接口重写matches方法,所以所有使用@Condition注解(包括@Profiles)的Bean都将在注册前回调对应的matches方法,返回true则注册,返回false则不注册。
class ProfileCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) { return true; } } return false; } return true; } }更多细节请看环境抽象章节。
基于Java的容器配置实现查找方法注入。
public abstract class UserService{ public void test(){ System.out.println(createUserDao()); } protected abstract UserDao createUserDao(); } @Bean @Scope("prototype") public UserDao userDao() { return new UserDao(); } @Bean public UserService userService() { //通过匿名重写方法 return new UserService() { protected UserDao createUserDao() { return userDao(); } } }在web开发中,默认是使用XmlWebApplicationContext(XML配置形式)进行配置的,我们可以通过修改web.xml文件使用AnnotationConfigWebApplicationContext(Java配置形式)进行配置。
<web-app> <!-- 使用ContextLoaderListener监听引导程序上下文 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 配置ContextLoaderListener使用AnnotationConfigWebApplicationContext配置形式 --> <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </context-param> <!-- 选定配置类位置,可以是一个或多个(逗号或空格分隔)的全限定类名,也可以是全限定包名指定包扫描 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.AppConfig</param-value> </context-param> <!-- 配置DispatcherServlet --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置DispatcherServlet使用AnnotationConfigWebApplicationContext配置形式 --> <init-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </init-param> <!-- 配置DispatcherServlet选定配置类位置 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.web.MvcConfig</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app>使用基于Java的配置方案并不能完全的代替XML配置,有时候得结合使用XML配置实现最佳的配置策略。
以XML容器配置为中心的配置方案:
配置类本身也是一个Bean,你可以通过通常的注册导入配置类,当然也可以使用包扫描。
<beans> <context:annotation-config/> <!--<context:component-scan base-package="com.acme"/>--> <bean class="com.acme.AppConfig"/> </beans>以基于Java的容器配置为中心的配置方案:
通过使用@ImportResource注解导入XML文件即可。(跟@Import相似,但一个是导入类,一个是导入XML文件)
@Configuration @ImportResource("classpath:/com/acme/properties-config.xml") public class AppConfig { }