Spring容器-基于Java的容器配置
读Spring框架官方文档记录。
主要讲如何在Java代码中使用注解来配置Spring容器。
1. 基本概念:@Bean和@Configuration
@Bean用于方法上,表示该方法实例化、配置并初始化一个被Spring容器管理新的对象,与XML配置中<bean/>
标签的功能相同。@Bean可用在@Component注解的类方法中,但是最常用的还是@Configuration注解的类的方法中。
@Configuration用于类上,表示该类是可用于bean定义的类。而且,@Configuration类允许通过调用同一类中的其他@Bean方法来定义bean间的依赖关系。@Configuration使用举例如下:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
等价的xml配置如下:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
@Bean用在@Configuration类中和用在@Component类/其它类中区别是什么
用在@Component/其他类中不能定义内部bean依赖,相反,它们对其包含组件的内部状态进行操作,还可以选择对它们可能声明的参数进行操作。这样的@Bean方法不能调用其它的@Bean方法。每个这样的方法字面上只是特定bean引用的一个工厂方法,没有任何特殊的运行时语义。这样的方法在运行时不需要应用CGLIB的子类,所以在类设计方面没有限制(也就是说,包含的类可以是final类等等)。
@Configuration类中使用@Bean是比较常用的方式,跨方法的引用也会因此被重定向到容器的生命周期管理中,CGLIB代理会减少难以追踪的bug。
2. 使用AnnotationConfigApplicationContext实例化Spring容器
AnnotationConfigApplicationContext是ApplicationContext的一个实现,能够处理@Configuration注解的类、@Component注解的类以及JSR-330元数据注解的类。
- 当@Configuration类作为输入的时候,@Configuration类本身被注册为bean定义,同时,所有@Bean注解的方法也都被注册为bean定义。
- 当@Component或JSR-330类作为输入时,类本身被注册为bean定义,并假设DI元数据如@Autowired/@Inject会在必要的时候在这些类中使用。
(1) 简单的构建
与实例化ClassPathXmlApplicationContext使用Sping XML文件的方式非常相似,当实例化AnnotationConfigApplicationContext时,可以使用@Configuration类作为输入,可以完全不使用XML。举例如下:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
//@Component及JSR-330注解的类也可以,假设MyServiceImpl,Dependency1,Dependency2由@Autowired注入。
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
(2) 通过使用register(Class<?>…)以编程方式构建容器
可以通过一个无参构造实例化AnnotationConfigApplicationContext,并使用register()方法进行配置。
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
(3) 使用scan(String…)开启组件扫描
可以使用@ComponentScan开启组件扫描,举例如下:
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
...
}
等价的xml配置
<beans>
<context:component-scan base-package="com.acme"/>
</beans>
或者也可以使用AnnotationConfigApplicationContext的scan方法开启组件扫面功能
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();//com.acme包及其子包中所有的@Bean方法都被处理并注册为容器内的bean定义。
MyService myService = ctx.getBean(MyService.class);
}
(4) 使用AnnotationConfigWebApplicationContext以支持Web应用
当配置Spring ContextLoaderListener servlet 监听者、Spring MVC DispatcherServlet等可用AnnotationConfigWebApplicationContext。下面的web.xml片段配置了一个典型的Spring MVC web应用程序(注意使用了contextClass context-param和init-param)
<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma- or space-delimited
fully-qualified @Configuration classes. Fully-qualified packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-delimited
and fully-qualified @Configuration classes -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
3. 使用@Bean注解
@Bean是一个方法级注解,类似于XML中的<bean/>
标签,它支持一些属性,如init-method,destroy-method,autowiring等。
@Bean可以被用在@Component注解的类或@Configuration注解的类中。
(1) 声明Bean
可以在一个方法上使用@Bean注解,来声明一个bean。可以使用此方法在容器中注册一个bean定义,该定义的类型指定为方法的返回值。默认情况下bean名和方法名相同。
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
//也可以使用接口或者基类作为方法的返回类型,但是使用接口类型会限制类型预测。且由于非延迟的单例bean依据声明的顺序被实例化,所以其它组件想匹配子类的时候可能匹配不到。
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
等价的xml配置
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
(2) Bean依赖
使用@Bean注解的方法可以由任意个参数来提供给bean的构造,解析机智与基于构造器的依赖注入十分相似。如下所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
(3) 生命周期调用
有@Bean注解的任何类都支持常规的生命周期调用,且可以使用@PostConstruct和@PreDestory注解。如果一个bean实现了InitializingBean、DisposableBean或LifeCyle,对应的方法都会被容器调用。标准的*ware接口(如: BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware等)也都支持。
@Bean注解支持定制任意吃实话和销毁回调方法,如XML中的init-method以及destory-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();
}
}
//直接调用init方法也是可以的
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
默认情况下,有公共的close/shutdown方法的Java配置的bean会自动使用销毁回调。如果希望禁止自动使用销毁回调,可以使用@Bean(destoryMethod=""),这只能针对DataSource来使用,因为Java引用服务器使用其它的会有点问题。
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
同样,对于@Bean方法,通常使用编程JNDI查找,通过使用Spring的JndiTemplate或JndiLocatorDelegate帮助程序或直接使用JNDI InitialContext,但不使用JndiObjectFactoryBean变体(这将迫使将返回类型声明为FactoryBean类型,而不是实际的目标类型,使它更难用于在其他@Bean方法中引用这里提供的资源的交叉引用调用) (作为一个小辣鸡,并不懂这里的JNDI是个啥,等我啥时候用到再学┑( ̄Д  ̄)┍)。
(4) 指定bean的作用域
可以使用@Scope注解,默认的作用域是singleton,使用举例如下:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
对于Web类型的作用域涉及的作用域依赖问题依赖Web容器作用域bean,使用Java方式也可也解决。@Scope中的proxyMode属性可达到和<aop:scoped-proxy/>
一样的作用。默认是无代理(ScopedProxyMode.NO),也可以使用ScopedProxyMode.TARGET_CLASS 或ScopedProxyMode.INTERFACES。以下是使用举例:
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope //SessionScope中定义了ProxyMode属性
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
(5) 自定义Bean的名称
默认情况下,配置类会使用@Bean方法的方法名作为生成的bean的名称。通过@Bean的name属性也可对其进行更改,使用举例如下:
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
(6) 设置Bean的别名
有时候可能要给一个bean多个名字,@Bean的name属性可以接收一个字符串数字为bean提供不同的名字。使用举例如下:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
(7) 设置Bean的描述
可以使用@Description注解,来描述bean,使用举例如下:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
4. 使用@Configuration注解
@Configuration是个类级别的注解,表名对象是bean定义的一个源。@Configuration类通过公共的@Bean注解方法声明bean。
(1) 注入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的引用。注意这种声明bean之间的依赖关系只在@Configuration类中管用,在@Component类中不能这么使用。
(2) 查找(Lookup)方法注入
单例作用域bean依赖原型作用域bean,就可以使用查找方法注入。基于Java的配置如下所示:
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
// 使用Java配置,可以创建CommandManager的子类,createCommand()方法能够被重写,就可以找到一个新的command对象了,如下所示
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}
@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return new CommandManager() {//这儿实现了匿名类,new了一个匿名类的对象,匿名类里面重写了createCommand方法。
protected Command createCommand() {
return asyncCommand();
}
}
}
(3) 关于基于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默认是单例作用域。注意:所有@Configuration类都是在启动时用CGLIB子类化的。在子类中,子类的方法在调用父类方法前会首先检查是否有任何缓存的beans,然后创建新的实例。跟平常的单例bean的会不太一样。
注意:由于CGLIB在启动时动态增加的特征,配置类不能是final类型。如果不想使用CGLIB,就可以不在@Configuration类中定义@Bean,而是在其它类如@Component注解的类中使用@Bean。
5. 组合基于Java的配置
可以在基于Java的配置中组合注解,可以降低配置的复杂性。
(1) 使用@Import注解
类似于XML文件中的<import/>
,基于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显式的声明即可
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);//这里只需要添加ConfigB.class
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
这种方法简化了容器实例化,因为只需要处理一个类,而不需要在构造期间记住大量的@Configuration类。
从Spring框架4.2开始,@Import也支持对常规组件类的引用,类似于AnnotationConfigApplicationContext.register方法。如果希望通过使用几个配置类作为入口点来显式定义所有组件,从而避免组件扫描,那么这一点特别有用。
1) 对import的@Bean定义注入依赖
@Import导入的bean定义可能会依赖其它bean,可以在@Bean注解的方法参数中完成依赖注入,举例如下:
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
由于@Configuration注解的类在容器中最终会被处理成一个bean,因此可以用@Autowired,@Value等注解完成依赖注入。但是要注意由于@Configuration在容器中会被很早的初始化,所以这种强制的依赖注入会导致有些bean过早的初始化,这是不希望看到的。所以应该尽量使用第一种方式。举例如下:
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
注意:
- 对于通过@Bean进行的BeanPostProcessor和BeanFactoryPostProcessor定义需要声明为static @Bean方法。否则,@Autowired和@Value可能对配置类本身不起作用,因为有可能在AutowiredAnnotationBeanPostProcessor之前将其创建为bean实例。
- @Configuration类中的构造器注入仅仅在Spring框架4.3中支持,如果目标bean只定义了一个构造器,就没有必须要使用@Autowired,如上面例子中的RepositoryConfig类中。
2) 对import的bean进行完全限定以便更容易的查找
前面的例子中使用@Autowired能够很好的完成工作,但是bean定义还是有一定的模糊性,比如开发者在看ServiceConfig时,并不知道@Autowired AccountRepository bean在哪里声明。当然这可以通过IDE的一些功能来看到bean之间的相互依赖图。我们也可以@Autowired配置类本身,从而有从一个@Configuration类到另一个@Configuration类的直接导航。
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;//注意:这里配置的是RepositoryConfig本身
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
在上面的例子中,AccoutRepository在哪里配置是很明确的。但是ServiceConfig与RepositoryConfig紧密耦合在一块。通过使用基于接口或基于抽象类的@Configuration类,可以在一定程度上缓解这种紧密耦合。如下面例子所示:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
(2) 选择性的包含@Configuration类或@Bean方法
根据系统状态,选择性的地启用或禁用一个完整的@Configuration类,甚至单个的@Bean方法是非常有用的。一个很常见的例子就是当在Spring Environment中启用了特定的profile时去使用@Profile注解来激活Bean。@Conditional是@Profile的一个灵活的实现类。@Conditional表示特定的org.springframework.context.annotation.Condition部署应该在@Bean注册之前实现。Condition接口的实现提供了一个matches()方法,返回true/false。下面就是mathces方法的一种实现方式:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 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;
}
(3) 结合Java和XML配置
Spring中对@Configuration类的支持并不是希望完全代替XML。在XML方便或者必要的情况下,我们可以选择以XML为中心来通过ClassPathXmlApplicationContext等实例化容器或者通过Java配置为核心来通过AnnotationConfigApplicationContext实例化容器并通过@ImportResource注解导入必要的XML。
1) 以XML为中心使用@Configuration类
从XML引导Spring容器启动并以一种特别的方式包含@Configuration类可能更可取。例如,在使用Spring XML的大型现有代码库中,更容易根据需要创建@Configuration类,并从现有XML文件中包含它们。
a. 将@Configuration类声明为普通的Spring <bean/>
元素
@Configuration最终会是容器中的bean定义。在下面的例子中,我们创建名为AppConfig类的@Configuration类并将配置类包含在system-test-config.xml作为bean定义。因为<context:annotation-config/>
被打开了,容器识别@Configuration注解并将AppConfig中的@Bean方法进行合适的处理。举例如下:
//Java中普通的配置类
@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举例-->
<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>
jdbc.properties文件中的内容如下:
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
使用 <context:component-scan/>
获取@Configuration类
因为@Configuration是用@Component进行元注解的,所以@Configuration类自动成为组件扫描的候选者。使用与前面示例中描述的场景相同的场景,我们可以重新定义system-test-config.xml来利用组件扫描的优势。注意,在本例中,我们不需要显式声明<context:annotation-config/>
,因为<context:component-scan/>
启用相同的功能。
<!--system-test-config.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>
2) 以@Configuration类为中心并通过@ImportResource使用XML
在@Configuration类是配置容器的主要机制的应用程序中,可能仍然需要使用至少一些XML。在这些场景中,您可以使用@ImportResource并只定义所需的XML。这样做可以实现以Java为中心的方法来配置容器,并尽可能少的使用XML。下面的示例(包括一个配置类、一个定义bean的XML文件、一个属性文件和主类)展示了如何使用@ImportResource注释实现以Java为中心的配置,并根据需要使用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>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}