SpringFramework核心技术一(IOC:基于Java的容器配置)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: @Bean和@ConfigurationSpring新的Java配置支持中的中心构件是 - @Configuration注释类和@Bean注释方法。

@Bean和@Configuration

Spring新的Java配置支持中的中心构件是 - @Configuration注释类和@Bean注释方法。


一、基本概念:@Bean和@Configuration

Spring新的Java配置支持中的中心构件是 - @Configuration注释类和@Bean注释方法。

该@Bean注释被用于指示一个方法实例,配置和初始化为通过Spring IoC容器进行管理的新对象。对于那些熟悉Spring的<beans/>XML配置的人来说,@Bean注释和<bean/>元素具有相同的作用。您可以在任何Spring中带@Component方法中使用@Bean,但是,它们通常与@Configurationbean一起使用。
使用注释类@Configuration表示它的主要目的是作为bean定义的来源。此外,@Configuration类允许通过简单地调用@Bean同一类中的其他方法来定义bean间依赖关系。最简单的@Configuration类可以读作如下:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

AppConfig上面的类将等同于下面的Spring <beans/>XML:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

二、Full @Configuration vs ‘lite’ @Bean mode?

@Bean方法在没有@Configuration注释的类中被声明时,它们被称为在’精简’模式下处理。
在一个@Component或者一个普通的旧类中声明的Bean方法将被认为是’精简’的,其中包含类的一个不同的主要目的和一个@Bean方法仅仅是那里的一种奖励。
例如,服务组件可能会通过@Bean每个适用组件类的附加方法向集装箱公开管理视图。在这种情况下,@Bean方法是一个简单的通用工厂方法机制。

与完整不同@Configuration,lite @Bean方法不能声明bean间依赖关系。
相反,他们对包含组件的内部状态和可选的参数进行操作,它们可能会声明。
@Bean因此这种方法不应该引用其他 @Bean方法; 每个这样的方法实际上只是一个特定的bean引用的工厂方法,没有任何特殊的运行时语义。
这里的积极副作用是,在运行时不需要应用CGLIB子类,所以在类设计方面没有限制(即,包含的类可能是final等等)。

在常见的场景中,@Bean方法将在@Configuration类中声明,确保始终使用“完整”模式,并且跨方法引用将重定向到容器的生命周期管理。这样可以防止@Bean通过常规的Java调用意外调用相同的 方法,这有助于减少在“精简”模式下操作时难以追踪的细微错误。

三、使用AnnotationConfigApplicationContext实例化Spring容器

下面的部分介绍了Spring的AnnotationConfigApplicationContextSpring 3.0中的新功能。这种多功能的ApplicationContext实现不仅能够接受@Configuration类作为输入,还能够接受 @Component用JSR-330元数据注释的普通类和类。

当@Configuration提供类作为输入时,@Configuration类本身被注册为一个bean定义,并且在该类中所有@Bean声明的方法也被注册为bean定义。

当@Component被提供和JSR-330类,它们被登记为bean定义,并且假定DI元数据,例如@Autowired或者@Inject是这些类中使用的必要。

1.结构简单

与实例化Spring XML文件时用作输入的方式大致相同,在实例化一个类时 ClassPathXmlApplicationContext@Configuration类可以用作输入AnnotationConfigApplicationContext。这允许完全无XML地使用Spring容器。

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

如上所述,AnnotationConfigApplicationContext不仅仅只适用于@Configuration注释的类。任何@Component or JSR-330注解类都可以作为输入提供给构造器,例如:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

上面的MyServiceImpl、Dependency1和Dependency2类可以使用Spring依赖注入注释如@Autowired

2.以编程的方式使用注册方法构造容器

一个AnnotationConfigApplicationContext可以使用无参数构造函数实例化,然后使用该register()方法进行配置。这种方法在编程构建一个AnnotationConfigApplicationContext

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.使用@ComponentScan启用组件扫描

要启用组件扫描,只需@Configuration按照以下步骤注释您的班级:

@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig  {
    ...
}

有经验的Spring用户将熟悉Spring的context:命名空间中的XML声明

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

在上面的例子中,com.acme软件包将被扫描,寻找任何 @Component-annotated的类,并且这些类将被注册为容器中的Spring bean定义。AnnotationConfigApplicationContext公开该 scan(String…​)方法以允许相同的组件扫描功能:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

请记住,@Configuration类是用元注释 的@Component,所以它们是组件扫描的候选对象!在上面的例子中,假设AppConfig在com.acme包(或下面的任何包)中声明了它,它将在调用过程中被拾取scan(),并且在refresh()其所有@Bean方法中将被处理并在容器中注册为bean定义。

4.在Web项目中使用AnnotationConfigWebApplicationContext

一个WebApplicationContext变体AnnotationConfigApplicationContext可用AnnotationConfigWebApplicationContext。当配置Spring ContextLoaderListenerservlet侦听器,Spring MVC DispatcherServlet等时,可以使用此实现。接下来是web.xml配置典型Spring MVC Web应用程序的片段。请注意使用contextClasscontext-paraminit-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>

四、使用@Bean注释

@Bean是一种方法级别的注释和XML 元素的直接模拟。注释支持一些由其提供的属性<bean/>,例如: init-method, destroy-method, autowiring和name。
您可以在@Configuration@Component注释类中使用@Bean注解。

1.声明一个bean

要声明一个bean,只需使用注释对一个方法进行@Bean注释。您可以使用此方法在ApplicationContext指定为方法返回值的类型中注册一个bean定义。默认情况下,bean名称将与方法名称相同。以下是@Bean方法声明的一个简单示例:

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

上述配置完全等同于以下Spring XML:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

这两个声明都将一个名为transferService可用 的bean ApplicationContext,绑定到一个类型为object的对象实例TransferServiceImpl:

transferService  - > com.acme.TransferServiceImpl

你也可以@Bean用接口(或基类)返回类型声明你的方法:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

但是,这会将提前类型预测的可见性限制为指定的接口类型(TransferService),然后,TransferServiceImpl一旦受影响的单例bean被实例化,容器就会知道完整类型()。非懒惰的singleton bean根据它们的声明顺序得到实例化,所以你可能会看到不同的类型匹配结果,这取决于另一个组件试图通过非声明类型进行匹配的时间(比如@Autowired TransferServiceImpl 只有在“transferService”bean已经被解析实例化)。

如果您始终通过声明的服务接口来引用您的类型,那么您的 @Bean返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或可能由其实现类型引用的组件,声明最具体的返回类型是可能的(至少按照注入点对bean引用的要求)是比较安全的。

2.Bean依赖关系

@Bean注解的方法可以具有描述构建豆所需要的依赖关系的参数的任意数量。例如,如果我们TransferService 需要一个,AccountRepository我们可以通过一个方法参数来实现这个依赖关系:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

解析机制与基于构造函数的依赖注入非常相似。

3.接收生命周期回调

任何使用@Bean注释定义的类都支持常规生命周期回调,并且可以使用JSR-250中的注释@PostConstruct和@PreDestroy注释,请参阅 JSR-250注释以获取更多详细信息。
常规的Spring 生命周期回调也被完全支持。如果一个bean实现了InitializingBean,DisposableBean或者Lifecycle,它们各自的方法被容器调用。
*Aware诸如BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware等的标准接口也完全受支持。

该@Bean注释支持指定任意初始化和销毁​​回调方法,就像Spring XML init-method和元素destroy-method上的属性一样bean:

public class Foo {

    public void init() {
        // initialization logic
    }
}

public class Bar {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    //初始化
    @Bean(initMethod = "init")
    public Foo foo() {
        return new Foo();
    }
    //销毁
    @Bean(destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }
}

默认情况下,使用具有公共close或shutdown 方法的Java配置定义的bean 将自动列入销毁回调。如果你有一个public close或shutdownmethod,并且你不希望在容器关闭时调用它,只需添加@Bean(destroyMethod=”“)到你的bean定义来禁用默认(inferred)模式。
您可能希望为通过JNDI获取的资源默认执行此操作,因为其生命周期在应用程序外部进行管理。特别是,确保始终DataSource以Java EE应用程序服务器上的问题着称。

@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}

此外,通过@Bean方法,您通常会选择使用编程式JNDI查找:使用Spring的JndiTemplate/ JndiLocatorDelegatehelper或直接InitialContext使用JNDI ,但不会JndiObjectFactoryBean强制您将返回类型声明为FactoryBean类型而不是实际目标类型,很难在其他@Bean方法中用于参照所提供的资源的交叉引用调用。

当然,就上述情况而言, 在施工期间直接Foo调用该init()方法同样有效:

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        Foo foo = new Foo();
        foo.init();
        return foo;
    }

    // ...
}

当您直接使用Java进行工作时,您可以对您的对象执行任何您喜欢的操作,并且不总是需要依赖容器生命周期!

4.指定bean作用域

使用@Scope注释
您可以指定使用@Bean注释定义的bean 应该具有特定的作用域。您可以使用Bean Scopes部分中指定的任何标准范围 。

默认范围是singleton,但您可以使用@Scope注释覆盖它:

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}

@Scope和scoped-proxy
Spring提供了一种通过作用域代理来处理作用域依赖关系的便捷方式 。在使用XML配置时创建此类代理的最简单方法就是元素。使用@Scope注释在Java中配置bean提供了与proxyMode属性等效的支持。默认值是no proxy(ScopedProxyMode.NO),但可以指定ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。

如果您将范围代理示例从XML参考文档(请参阅前面的链接)移植到我们@Bean使用的Java中,它将如下所示:

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
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的名称。但是,可以使用该name属性覆盖此功能。

@Configuration
public class AppConfig {

    @Bean(name = "myFoo")
    public Foo foo() {
        return new Foo();
    }
}

5.Bean别名

正如在命名bean中所讨论的,有时需要为单个bean提供多个名称,否则称为bean别名。 注解的name属性@Bean为此接受一个String数组。

@Configuration
public class AppConfig {

    @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

6.Bean的描述

有时候提供一个更详细的bean的文本描述是有帮助的。当bean暴露(可能通过JMX)用于监视目的时,这可能特别有用。
为了说明添加到@Bean的 @Description 注释,可以使用:

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Foo foo() {
        return new Foo();
    }
}

五、使用@Configuration注释

@Configuration是一个类级注释,指示对象是bean定义的来源。@Configuration类通过公共@Bean注释方法声明bean 。调用@Bean的方法@Configuration类也可以用于定义bean间的依赖关系。有关一般介绍,请参阅基本概念:@Bean@Configuration

1.注入bean间依赖关系

@Beans彼此依赖时,表达该依赖性就像一个bean方法调用另一个一样简单:

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        return new Foo(bar());
    }

    @Bean
    public Bar bar() {
        return new Bar();
    }
}

在上面的例子中,foobean接收到bar通过构造函数注入的引用。

这种声明bean间依赖关系的@Bean方法只有在方法在@Configuration类中声明时才有效。您不能使用普通@Component类声明bean间依赖关系。

2.查找方法注入

如前所述,查找方法注入是一种您很少使用的高级功能。在单例范围的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()方法被重写的子类,以便查找新的(原型)命令对象:

@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 command() overridden
    // to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}

六、有关基于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并返回它的新实例,因此通常期望拥有2个实例(每个服务一个实例)。这肯定会有问题:在Spring中,实例化的bean singleton默认具有一个范围。这就是魔法来临的地方:所有@Configuration类都在启动时被分类CGLIB。在子类中,child方法在调用父方法并创建新实例之前首先检查容器是否有缓存的(范围)Bean。请注意,从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已被重新打包org.springframework.cglib并直接包含在Spring-core JAR中。

根据您的bean的范围,行为可能会有所不同。我们在这里讨论单身人士。
由于CGLIB在启动时动态添加功能,因此存在一些限制,特别是配置类不能是最终的。但是,从4.3开始,任何构造函数都可以在配置类上使用,包括@Autowired对默认注入使用 或使用单个非默认构造函数声明。
如果您更喜欢避免任何CGLIB强加的限制,请考虑@Bean 在非@Configuration类上声明您的方法,例如在普通@Component类上。方法之间的跨方法调用@Bean不会被拦截,因此您必须在构造方法或方法级别专门依赖依赖注入。

七、撰写基于Java的配置

1.使用@Import注释

就像Spring XML文件中使用该元素来帮助模块化配置一样,该@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();
    }
}

现在,不需要指定两者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在构建过程中记住潜在的大量类。

从Spring Framework 4.2开始,它@Import也支持对常规组件类的引用,类似于该AnnotationConfigApplicationContext.register方法。如果您想要避免组件扫描,使用几个配置类作为明确定义所有组件的入口点,这特别有用。

2.注入对导入的@Bean定义的依赖关系

上面的例子工作,但是很简单。在大多数实际场景中,bean将跨配置类彼此依赖。当使用XML时,这本身并不是问题,因为不涉及编译器,并且可以简单地声明 ref=”someBean”并相信Spring将在容器初始化期间解决它。当然,在使用@Configuration类时,Java编译器会对配置模型施加约束,因为对其他bean的引用必须是有效的Java语法。
幸运的是,解决这个问题很简单。正如我们已经讨论过的, @Bean方法可以有任意数量的描述bean依赖关系的参数。让我们考虑一个更实际的场景,其中有几个@Configuration 类,每个类都依赖于其他类中声明的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:这意味着它们可以像其他任何bean一样利用 @Autowired和@Value注入等!

确保以这种方式注入的依赖关系只有最简单的类型。@Configuration 类在上下文初始化期间处理得相当早,并且强制依赖性以这种方式注入可能会导致意外的早期初始化。在可能的情况下,采用基于参数的注入,如上例所示。
另外,要特别小心,BeanPostProcessor并BeanFactoryPostProcessor通过定义@Bean。这些通常应该声明为static @Bean方法,而不是触发其包含的配置类的实例化。否则,@Autowired它@Value不会在配置类本身上工作,因为它太早创建为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;

    @Autowired
    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");
}

@Configuration类的构造器注入仅在Spring Framework 4.3中受支持。还要注意,不需要指定@Autowired目标bean是否只定义了一个构造函数; 在上面的例子中,@Autowired在RepositoryConfig构造函数中是没有必要的。

1.完全限定导入的豆类以便导航

在上面的场景中,使用得@Autowired很好,并提供了所需的模块化,但是确切地确定自动布线bean定义的声明位置仍然有些模糊。例如,作为一名开发人员ServiceConfig,您如何知道@Autowired AccountRepositorybean的确切位置?它在代码中并不明确,这可能会很好。请记住, Spring Tool Suite提供的工具可以呈现图表,显示如何连接所有东西 - 这可能就是您所需要的一切。此外,您的Java IDE可以轻松找到该AccountRepository类型的所有声明和用法,并会快速向您显示@Bean返回该类型的方法的位置。

如果这种不明确性不可接受,并且希望从IDE内部直接导航@Configuration到另一个类,请考虑自动配置自己的配置类:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

在上面的情况中,它是完全明确的,在哪里AccountRepository定义。但是,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");
}

现在ServiceConfig与具体方面松散耦合 DefaultRepositoryConfig,并且内置的IDE工具仍然有用:开发人员可以轻松获得类型层次结构的RepositoryConfig实现。通过这种方式,导航@Configuration类及其依赖关系与导航基于接口的代码的常用过程无异。

如果你想影响某些豆类的启动创建顺序,考虑宣布一些为@Lazy(对于第一次访问,而不是在启动时创建)或@DependsOn某些其它豆类(确保特定的其他豆将在当前之前创建bean,超越了后者的直接依赖意味)。

二、有条件地包含@Configuration类或@Bean方法

根据某些任意系统状态,有条件地启用或禁用完整的@Configuration类,甚至个别的@Bean方法通常很有用。一个常见的例子是,@Profile只有在Spring中启用了特定的配置文件时才使用注释来激活bean Environment(请参阅Bean定义配置文件 以了解详细信息)。
该@Profile注释是使用所谓的更灵活的注释实际执行@Conditional。该@Conditional注释指示特定 org.springframework.context.annotation.Condition前应谘询的实施@Bean是注册。
Condition接口的实现只是提供了一个matches(…​) 返回true或返回的方法false。例如,以下是Condition用于以下内容的实际 实现@Profile:

@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;
}

八、结合Java和XML配置

Spring的@Configuration类支持并不旨在成为Spring XML的100%完全替代品。Spring XML命名空间等一些工具仍然是配置容器的理想方式。在XML方便或必要的情况下,您可以选择:或者使用“以XML为中心”的方式实例化容器ClassPathXmlApplicationContext,或者使用“以Java为中心”的方式 实例化容器, AnnotationConfigApplicationContext并使用@ImportResource注释根据需要导入XML 。

1.以XML为中心使用@Configuration类

最好从XML引导Spring容器,并@Configuration以临时方式包含 类。例如,在使用Spring XML的大型现有代码库中,根据需要创建@Configuration类并从现有XML文件中包含它们会更容易。下面你会发现@Configuration在这种“以XML为中心”的情况下使用类的选项。

将@Configuration类声明为普通的Spring <bean/>元素
请记住,@Configuration类最终只是容器中的bean定义。在这个例子中,我们创建一个@Configuration名为AppConfig并包含它的类system-test-config.xml作为<bean/>定义。因为<context:annotation-config/>已打开,容器将识别 @Configuration注释并处理 正确@Bean声明的方法AppConfig。

@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://本地主机/ 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);
    // ...
}

在system-test-config.xml上面,AppConfig <bean/>没有声明一个id 元素。虽然这样做是可以接受的,但没有必要考虑到其他bean将不会引用它,并且它不太可能通过名称明确从容器中提取。与DataSourcebean 同样- 它只能通过类型自动装配,因此id不需要显式bean 。

使用<context:component-scan />来拾取@Configuration类
因为@Configuration与间注解@Component,@Configuration-annotated类自动对于组件扫描的候选者。使用与上述相同的场景,我们可以重新定义system-test-config.xml以利用组件扫描。请注意,在这种情况下,我们不需要显式声明 <context:annotation-config/>,因为<context:component-scan/>启用了相同的功能。

<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保持最低限度。

@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://本地主机/ XDB
jdbc.username = SA
jdbc.password =
  • main
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

好啦,基于Java的容器配置讲了这么多,总算是完啦。
加油啦,越努力越幸运

目录
相关文章
|
7天前
|
运维 Kubernetes Cloud Native
云原生技术:容器化与微服务架构的完美结合
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术以其灵活性和高效性成为企业的新宠。本文将深入探讨云原生的核心概念,包括容器化技术和微服务架构,以及它们如何共同推动现代应用的发展。我们将通过实际代码示例,展示如何在Kubernetes集群上部署一个简单的微服务,揭示云原生技术的强大能力和未来潜力。
|
9天前
|
运维 持续交付 Docker
深入理解Docker容器化技术
深入理解Docker容器化技术
|
5天前
|
安全 持续交付 云计算
揭秘云计算中的容器化技术及其优势
揭秘云计算中的容器化技术及其优势
11 1
|
5天前
|
缓存 监控 开发者
掌握Docker容器化技术:提升开发效率的利器
在现代软件开发中,Docker容器化技术成为提升开发效率和应用部署灵活性的重要工具。本文介绍Docker的基本概念,并分享Dockerfile最佳实践、容器网络配置、环境变量和秘密管理、容器监控与日志管理、Docker Compose以及CI/CD集成等技巧,帮助开发者更高效地利用Docker。
|
8天前
|
Kubernetes Cloud Native Docker
云原生技术探索:容器化与微服务的实践之道
【10月更文挑战第36天】在云计算的浪潮中,云原生技术以其高效、灵活和可靠的特性成为企业数字化转型的重要推手。本文将深入探讨云原生的两大核心概念——容器化与微服务架构,并通过实际代码示例,揭示如何通过Docker和Kubernetes实现服务的快速部署和管理。我们将从基础概念入手,逐步引导读者理解并实践云原生技术,最终掌握如何构建和维护一个高效、可扩展的云原生应用。
|
9天前
|
Cloud Native 持续交付 Docker
Docker容器化技术:从入门到实践
Docker容器化技术:从入门到实践
|
9天前
|
数据中心 开发者 Docker
理解并实践Docker容器化技术
理解并实践Docker容器化技术
|
7天前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker和Kubernetes入门
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性和效率的关键。本篇文章将引导读者了解如何利用Docker进行容器化打包及部署,以及Kubernetes集群管理的基础操作,帮助初学者快速入门云原生的世界。通过实际案例分析,我们将深入探讨这些技术在现代IT架构中的应用与影响。
28 2
|
17天前
|
Kubernetes 监控 开发者
掌握容器化:Docker与Kubernetes的最佳实践
【10月更文挑战第26天】本文深入探讨了Docker和Kubernetes的最佳实践,涵盖Dockerfile优化、数据卷管理、网络配置、Pod设计、服务发现与负载均衡、声明式更新等内容。同时介绍了容器化现有应用、自动化部署、监控与日志等开发技巧,以及Docker Compose和Helm等实用工具。旨在帮助开发者提高开发效率和系统稳定性,构建现代、高效、可扩展的应用。
|
13天前
|
关系型数据库 MySQL API