spring core之Ioc介绍

1、ApplicationContext是BeanFactory的子接口。

2、BeanFactory提供配置框架和基本功能,ApplicationContext添加更多特定于企业的功能。

3、org.springframework.context。ApplicationContext接口表示Spring IoC容器,并负责实例化、配置和组装bean

4、Spring提供了ApplicationContext接口的几个实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例

5、使用<import/>元素的一个或多个实例从另一个或多个文件加载bean定义


<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>




前导斜杠将被忽略。然而,考虑到这些路径是相对的,最好不要使用斜杠

6、ApplicationContext是高级工厂的接口,该工厂能够维护不同bean及其依赖项的注册表。通过使用方法T getBean(String name, Class<T> requiredType),您可以检索bean的实例。

7、最灵活的变体是GenericApplicationContext与阅读器委托结合使用——例如,XML文件使用XmlBeanDefinitionReader

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

Groovy文件使用GroovyBeanDefinitionReader

GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();

7、bean id的唯一性仍然由容器强制执行,但不再由XML解析器强制执行。
8、对于内部函数,bean的class配置为:com.example.SomeThing$OtherThing,名称中使用$字符将嵌套类名与外部类名分隔开来

9、使用静态工厂方法实例化bean,下面的例子展示了如何指定工厂方法:

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>

public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

createInstance()方法必须是静态方法,能够调用此方法(使用可选参数,如后面所述)并返回一个活动对象,该对象随后将被视为通过构造函数创建的

10、使用实例工厂方法实例化

请将class属性保留为空,并在factory-bean属性中,包含要调用以创建对象的实例方法的当前(或父或祖先)容器中指定bean的名称。使用factory-method属性设置工厂方法本身的名称

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

一个工厂类还可以容纳多个工厂方法,如下面的例子所示:

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

11、依赖注入(Dependency injection, DI)是这样一个过程:对象仅通过构造函数参数、工厂方法的参数或从工厂方法构造或返回的对象实例上设置的属性来定义它们的依赖关系(即与它们一起工作的其他对象)。然后容器在创建bean时注入这些依赖项,这个过程基本上是bean本身的逆过程(因此得名“控制反转”),通过使用直接构造的类或服务定位器模式来

12、可以使用setter方法上的@Required注释使属性成为必需的依赖项。

13、Spring团队通常提倡构造函数注入,因为它允许将应用程序组件实现为不可变对象,并确保所需的依赖关系不是null,此外,构造器注入的组件总是以完全初始化的状态返回给客户机(调用)代码。附带说明的是,大量构造函数参数是一种糟糕的代码味道,这意味着类可能有太多的责任,应该进行重构,以更好地处理关注点的适当分离

14、如果bean A对bean B有依赖关系,那么Spring IoC容器在调用bean A上的setter方法之前会完全配置bean B

15、通过<ref/>标记的bean属性指定目标bean是最通用的形式,它允许创建对同一容器或父容器中的任何bean的引用,而不管它是否位于同一XML文件中,bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的值相同。

16、集合的配置

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

17、通常,预实例化是可取的,因为配置或周围环境中的错误是立即发现的,而不是在数小时甚至数天后发现的,当不需要这种行为时,可以通过将bean定义标记为延迟初始化来防止单例bean的预实例化,延迟初始化的bean告诉IoC容器在首次请求时创建bean实例,而不是在启动时,在XML中,这种行为由<bean/>元素上的lazy-init属性控制

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

当前面的配置被ApplicationContext使用时,延迟bean在ApplicationContext启动时不会急切地预先实例化

然而,当延迟初始化的bean是没有延迟初始化的单例bean的依赖项时,ApplicationContext将在启动时创建延迟初始化的bean,因为它必须满足单例bean的依赖项

您还可以通过使用<beans/>元素上的default-lazy-init属性在容器级别上控制延迟初始化,下面的示例显示:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

18、作为规则,您应该为所有有状态bean使用prototype作用域,为无状态bean使用singleton作用域。bean部署的非单例原型作用域导致每次对特定bean发出请求时都创建一个新的bean实例。也就是说,将bean注入到另一个bean中,或者通过容器上的getBean()方法调用请求它。

19、数据访问对象(DAO)通常不配置为原型,因为典型的DAO不包含任何会话状态。我们更容易重用单例图的核心。

20、容器实例化、配置和组装原型对象并将其交给客户机,而不需要该原型实例的进一步记录,因此,尽管初始化生命周期回调方法会在所有对象上调用,而不考虑范围,但在原型的情况下,配置的销毁生命周期回调不会被调用。客户端代码必须清理原型范围内的对象,并释放原型bean所持有的昂贵资源,要让Spring容器释放原型作用域bean所持有的资源,请尝试使用自定义bean后处理程序,该后处理程序包含对需要清理的bean的引用。

21、如果依赖项将原型作用域bean注入到单例作用域bean中,则实例化一个新的原型bean,然后将依赖项注入到单例bean中,但是,假设您希望单例作用域bean在运行时重复获取原型作用域bean的新实例,不能将依赖项作用域为原型的bean注入到单例bean中,因为这种注入只在Spring容器实例化单例bean并解析和注入其依赖项时发生一次。如果在运行时不止一次需要原型bean的新实例,请参见方法注入

22、如果您想将(例如)HTTP请求作用域bean注入(例如)到另一个生存时间较长的bean中,您可以选择将AOP代理注入到作用域bean中,也就是说,您需要注入一个代理对象,该代理对象公开与作用域对象相同的公共接口,但也可以从相关的作用域(如HTTP请求)检索实际目标对象,并将方法调用委托给实际对象

<?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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/> 
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.something.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

为什么在请求、会话和自定义范围级别作用域的bean定义需要<aop:作用域代理/>元素,考虑下面的单例bean定义,并将其与您需要为上述范围定义的内容进行对比

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

这里的重点是userManager bean是单例的:它对每个容器只实例化一次,它的依赖项(在本例中只有一个,userPreferences bean)也只注入一次,这意味着userManager bean只对完全相同的userPreferences对象(即它最初被注入的对象)进行操作。这不是将较短生存期的作用域bean注入较长生存期的作用域bean时所希望的行为,您需要一个userManager对象,并且在HTTP会话的生命周期中,您需要一个特定于HTTP会话的userPreferences对象,因此,容器创建的对象公开了与UserPreferences类(理想情况下是UserPreferences实例的对象)完全相同的公共接口,后者可以从范围机制(HTTP请求、会话等)获取真正的UserPreferences对象。容器将此代理对象注入userManager bean,该bean不知道此UserPreferences引用是代理。在本例中,当UserManager实例调用依赖注入的UserPreferences对象上的方法时,它实际上是在调用代理上的方法,然后代理从(在本例中)HTTP会话中获取真正的UserPreferences对象,并将方法调用委托给检索到的真正UserPreferences对象。

23、要与容器的bean生命周期管理交互,可以实现Spring InitializingBean和DisposableBean接口。容器为前者调用afterPropertiesSet(),为后者调用destroy(),以便让bean在初始化和销毁bean时执行某些操作

org.springframework.beans.factory.InitializingBean接口允许bean在容器设置了bean上所有必需的属性之后执行初始化工作,InitializingBean接口指定一个方法:

void afterPropertiesSet() throws Exception;

我们建议您不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。或者,我们建议使用@PostConstruct注释或指定POJO初始化方法,对于基于xml的配置元数据,可以使用init-method属性指定具有空无参数签名的方法的名称。通过Java配置,您可以使用@Bean的initMethod属性。

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {

    public void init() {
        // do some initialization work
    }
}

上面的例子与下面的例子(由两个清单组成)的效果几乎完全相同:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {

    public void afterPropertiesSet() {
        // do some initialization work
    }
}

对于Destruction Callbacks:

实现org.springframework.beans.factory.DisposableBean接口允许bean在包含它的容器被销毁时获得回调

void destroy() throws Exception;

和InitializingBean接口一样,不建议使用,建议使用@PreDestroy注释或指定bean定义支持的泛型方法。对于基于xml的配置元数据,可以在<bean/>上使用销毁方法属性,通过Java配置,可以使用@Bean的destroyMethod属性

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}

24、BeanPostProcessor接口定义了回调方法,您可以实现这些方法来提供自己的(或覆盖容器的默认)实例化逻辑、依赖项解析逻辑,等等。如果您想在Spring容器完成bean的实例化、配置和初始化之后实现一些定制逻辑,您可以插入一个或多个定制BeanPostProcessor实现。

BeanPostProcessor实例的作用域是每个容器。只有在使用容器层次结构时,这才是相关的,如果在一个容器中定义BeanPostProcessor,那么它只对该容器中的bean进行后处理

org.springframework.beans.factory.config.BeanPostProcessor接口由两个回调方法组成

虽然BeanPostProcessor注册的推荐方法是通过ApplicationContext自动检测(如前所述),但是您可以使用addBeanPostProcessor方法以编程方式对ConfigurableBeanFactory进行注册。请注意,以编程方式添加的BeanPostProcessor实例不尊重有序接口。在这里,注册的顺序决定了执行的顺序。

示例:下面的示例展示如何在ApplicationContext中编写、注册和使用BeanPostProcessor实例

示例演示了基本用法。该示例显示了一个自定义BeanPostProcessor实现,该实现在容器创建每个bean时调用该bean的toString()方法,并将结果字符串打印到系统控制台。

下面的清单显示了自定义BeanPostProcessor实现类定义:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

下面的bean元素使用了InstantiationTracingBeanPostProcessor:

<?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:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        http://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

注意,InstantiationTracingBeanPostProcessor是如何定义的。它甚至没有名称,而且因为它是bean,所以可以像其他bean一样依赖注入

下面的Java应用程序运行前面的代码和配置:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }

}

前一个应用程序的输出类似如下:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

25、org.springframework.beans.factory.config.BeanFactoryPostProcessor,此接口的语义与BeanPostProcessor的语义类似,但有一个主要区别:BeanFactoryPostProcessor操作bean配置元数据,例如可以把bean的scope从singleton改为prototype,也可以把property的值给修改掉

当在ApplicationContext中声明bean工厂后处理器时,它将自动执行,以便对定义容器的配置元数据应用更改。Spring包含许多预定义的bean工厂后处理器,如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer,您还可以使用自定义BeanFactoryPostProcessor—例如,注册自定义属性编辑器。

与beanpostprocessor一样,您通常不希望为延迟初始化配置beanfactorypostprocessor。如果没有其他bean引用bean(工厂)后处理器,则该后处理器将根本不会实例化,因此,将它标记为惰性初始化将被忽略,Bean(工厂)后处理器将被急切地实例化,即使您在<beans />元素的声明中将default-lazy-init属性设置为true。

示例:类名替换PropertyPlaceholderConfigurer

考虑以下基于xml的配置元数据片段,其中定义了具有占位符值的数据源:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

在运行时,PropertyPlaceholderConfigurer应用于替换数据源的某些属性的元数据,要替换的值被指定为表单${property-name}的占位符,该表单遵循Ant和log4j以及JSP EL样式。

实际值来自标准Java属性格式的另一个文件:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

PropertyPlaceholderConfigurer检查bean定义的大多数属性和属性中的占位符。此外,您可以自定义占位符前缀和后缀。

使用Spring 2.5中引入的上下文命名空间,您可以使用专用的配置元素配置属性占位符,您可以在location属性中以逗号分隔的列表形式提供一个或多个位置,如下面的示例所示:

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>

PropertyPlaceholderConfigurer不仅在指定的属性文件中查找属性。默认情况下,如果在指定的属性文件中找不到属性,它还会根据Java系统属性进行检查,您可以使用以下三个受支持的整数值之一来设置配置程序的systemPropertiesMode属性,从而定制此行为:

1、never (0): Never check system properties.

2、fallback (1): Check system properties if not resolvable in the specified properties files. This is the default.

3、override (2): Check system properties first, before trying the specified properties files. This lets system properties override any other property source.

您可以使用PropertyPlaceholderConfigurer来替换类名,当您必须在运行时选择特定的实现类时,这有时非常有用

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/something/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.something.DefaultStrategy</value>
    </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

总的来说:BeanFactoryPostProcessor可以在spring的bean创建之前,修改bean定义的属性,BeanPostProcessor可以在Spring容器实例化bean之后,在执行bean的初始化方法前后,添加一些自己的处理逻辑,BeanPostProcessor是在spring容器加载了bean的定义文件并且实例化bean之后执行的。BeanPostProcessor的执行顺序是在BeanFactoryPostProcessor之后。(https://blog.csdn.net/liubo2012/article/details/43629153

26、FactoryBean接口是Spring IoC容器实例化逻辑的可插入点,

FactoryBean接口提供了三种方法:

1、Object getObject():返回此工厂创建的对象的实例。可以共享实例,这取决于该工厂返回的是单例还是原型。

2、boolean isSingleton():如果FactoryBean返回单例,则返回true,否则返回false。

3、Class getObjectType():返回getObject()方法返回的对象类型,如果类型事先不知道,则返回null。

当您需要向容器请求实际的FactoryBean实例本身而不是它生成的bean时,在调用ApplicationContext的getBean()方法时,在bean的id前面加上&符号,因此,对于id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的产品,而调用getBean(“&myBean”)将返回FactoryBean实例本身。

FactoryBean是一个工厂Bean,可以生成某一个类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。BeanFactory是Spring容器中的一个基本类也是很重要的一个类,在BeanFactory中可以创建和管理Spring容器中的Bean,它对于Bean的创建有一个统一的流程。

参考网址:https://blog.csdn.net/zknxx/article/details/79572387

https://www.jianshu.com/p/d8b158fe595d (详细)

27、注释注入在XML注入之前执行。因此,XML配置覆盖了通过这两种方法连接的属性的注释。

28、@Required注释适用于bean属性设置器方法,如下面的示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

此注释指示必须在配置时填充受影响的bean属性,方法是通过bean定义中的显式属性值或通过自动连接,如果没有填充受影响的bean属性,容器将抛出异常

29、Using @Autowired

每个类只能标记一个带注释的构造函数,但是可以标记多个非必需的构造函数。@Autowired的required属性比@Required注释更受推荐,required属性表明,出于自动连接的目的,属性不是必需的。如果无法自动连接,则忽略该属性

或者,您可以通过Java 8的Java .util来表达特定依赖项的非必需性质。可选,如下例所示:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

在Spring Framework 5.0中,您还可以使用@Nullable注释(任何包中的任何类型的注释—例如javax.annotation)。从jsr - 305)可以为空:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

@Autowired、@Inject、@Resource和@Value注释由Spring BeanPostProcessor实现处理,这意味着您不能在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有的话)中应用这些注释。必须使用XML或Spring @Bean方法显式地“连接”这些类型。

30、@Primary表示当多个bean是要自动连接到单值依赖项的候选bean时,应该优先选择某个bean。如果候选bean中恰好存在一个主bean,它将成为自动连接的值。

考虑以下配置,它将firstMovieCatalog定义为主MovieCatalog:

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

在前面的配置中,使用firstMovieCatalog自动连接以下MovieRecommender:

public class MovieRecommender {

    @Autowired
    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" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

31、当您需要对选择过程进行更多控制时,您可以使用Spring的@Qualifier注释。您可以将限定符值与特定的参数相关联,缩小类型匹配集,以便为每个参数选择特定的bean。

具体介绍:https://blog.csdn.net/lovin_fang/article/details/78537547

当依赖类路径扫描自动检测组件时,可以在候选类上为限定符元数据提供类型级别的注释。下面三个例子演示了这种技术

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}

 与大多数基于注释的替代方案一样,请记住注释元数据绑定到类定义本身,而XML的使用允许相同类型的多个bean在限定符元数据中提供变体,因为元数据是按实例而不是按类提供的。

 

32、使用JSR-250 @Resource注释,它在语义上定义为通过其惟一的名称标识特定的目标组件,声明的类型与匹配过程无关。

关于@Resource注解,可参考此篇博客https://www.cnblogs.com/mr-wuxiansheng/p/6392190.html

33、除了@Qualifier注释之外,还可以使用Java泛型类型作为一种隐式的限定形式,例如,假设您有以下配置:

@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}

假设前面的bean实现了一个泛型接口(即Store<String>和Store<Integer>),您可以@Autowire存储接口,泛型用作限定符,如下面的示例所示: 

@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean

通用限定符也适用于自动连接列表、映射实例和数组。下面的示例自动连接一个通用列表:

// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;

 34、CustomAutowireConfigurer是一个BeanFactoryPostProcessor,它允许您注册自己的自定义限定符注释类型,即使它们没有使用Spring的@Qualifier注释,下面的例子展示了如何使用CustomAutowireConfigurer:

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

AutowireCandidateResolver通过以下方式确定autowire候选者:

1、The autowire-candidate value of each bean definition

2、Any default-autowire-candidates patterns available on the <beans/> element

3、The presence of @Qualifier annotations and any custom annotations registered with the CustomAutowireConfigurer

35、@Resource接受name属性。默认情况下,Spring将该值解释为要注入的bean名。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果没有显式指定名称,则默认名称来自字段名或setter方法。下面的示例将把名为movieFinder的bean注入其setter方法中:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

 在没有指定显式名称的@Resource使用的独占情况下(类似于@Autowired), @Resource会找到一个主类型匹配,而不是特定的命名bean,并解析已知的可解析依赖项:BeanFactory、ApplicationContext、ResourceLoader、ApplicationEventPublisher和MessageSource接口。

因此,在下面的示例中,customerPreferenceDao字段首先查找名为customerPreferenceDao的bean,然后返回到与customerPreferenceDao类型匹配的主类型:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; 

    public MovieRecommender() {
    }

    // ...
}

context字段是基于已知的可解析依赖项类型:ApplicationContext注入的。

36、使用@PostConstruct和@PreDestroy

在下面的示例中,缓存在初始化时预先填充,在销毁时清除:

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}

从java EE5规范开始,Servlet增加了两个影响Servlet生命周期的注解(Annotation):@PostConstruct和@PreConstruct。这两个注解被用来修饰一个非静态的void()方法.而且这个方法不能有抛出异常声明。

 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的inti()方法。被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。

   被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前

参考网址:https://www.cnblogs.com/landiljy/p/5764515.html

37、@Repository注释是任何实现存储库角色或原型(也称为数据访问对象或DAO)的类的标记。Spring提供了进一步的构造型注释:@Component、@Service和@Controller。@Component是任何spring管理组件的通用原型,@Repository、@Service和@Controller是@Component的专门化,用于更具体的用例(分别在持久性层、服务层和表示层中)。

38、元注释是可以应用于另一个注释的注释,例如,前面提到的@Service注释是用@Component元注释的,如下面的例子所示:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component 
public @interface Service {

    // ....
}

您还可以组合元注释来创建“复合注释”。例如,Spring MVC中的@RestController注释由@Controller和@ResponseBody组成。此外,复合注释可以选择性地从元注释中重新声明属性,以允许自定义。当您只想公开元注释属性的子集时,这一点尤其有用。例如,Spring的@SessionScope注释将作用域名称硬编码为session,但仍然允许定制proxyMode。下面的清单显示了SessionScope注释的定义:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

您还可以覆盖proxyMode的值,如下面的示例所示:

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}

对于spring注解编程模型可查看:https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model

39、要自动检测这些类并注册相应的bean,您需要将@ComponentScan添加到@Configuration类中,其中basePackages属性是两个类的公共父包。(或者,您可以指定一个逗号或分号或空格分隔的列表,其中包含每个类的父包)。

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    ...
}

下面的替代方案使用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="org.example"/>

</beans>

使用<context:component-scan>隐式地支持<context:annotation-config>的功能。在使用<context:component-scan>时,通常不需要包含<context:annotation-config>元素。此外,在使用组件扫描元素时,AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor都是隐式包含的。

40、默认情况下,使用@Component、@Repository、@Service、@Controller注释的类,或者本身使用@Component注释的自定义注释,是唯一检测到的候选组件。但是,您可以通过应用自定义筛选器来修改和扩展此行为。添加它们作为@ComponentScan注释的includeFilters或exclude defilters参数(或者作为component-scan元素的include-filter或exclude-filter子元素)每个筛选器元素都需要类型和表达式属性。下表描述了过滤选项:

Filter TypeExample ExpressionDescription

annotation (default)

org.example.SomeAnnotation

An annotation to be present at the type level in target components.

assignable

org.example.SomeClass

A class (or interface) that the target components are assignable to (extend or implement).

aspectj

org.example..*Service+

An AspectJ type expression to be matched by the target components.

regex

org\.example\.Default.*

A regex expression to be matched by the target components class names.

custom

org.example.MyTypeFilter

A custom implementation of the org.springframework.core.type .TypeFilter interface.

下面的示例展示了忽略所有@Repository注释而使用“Stub”存储库的配置:

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}

下面的清单显示了等价的XML:

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

您还可以通过在注释上设置useDefaultFilters=false或提供use-default-filters="false"作为<component-scan/>元素的属性来禁用默认过滤器。这实际上禁用了使用@Component、@Repository、@Service、@Controller或@Configuration注释的类的自动检测。

41、Spring组件还可以向容器提供bean定义元数据。您可以使用与在@Configuration注释类中定义bean元数据相同的@Bean注释来实现这一点。下面的例子展示了如何做到这一点:

@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }
}

前面的类是一个Spring组件,它的doWork()方法中有特定于应用程序的代码。但是,它还提供了一个bean定义,其中的工厂方法引用了方法publicInstance()。@Bean注释标识工厂方法和其他bean定义属性,例如通过@Qualifier注释标识一个限定符值。可以指定的其他方法级注释有@Scope、@Lazy和自定义限定符注释。

除了用于组件初始化的角色外,还可以将@Lazy注释放在标记为@Autowired或@Inject的注入点上。在这种情况下,它会导致延迟解析代理的注入。

如前所述,支持自动连接字段和方法,还支持@Bean方法的自动连接。下面的例子展示了如何做到这一点:

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}

该示例将字符串方法参数country自动连接到另一个名为privateInstance的bean上的age属性的值,Spring表达式语言元素通过符号#{< Expression >}定义属性的值,对于@Value注释,表达式解析器被预先配置为在解析表达式文本时查找bean名称。

42、当一个组件作为扫描过程的一部分被自动检测时,它的bean名由扫描程序所知道的BeanNameGenerator策略生成。默认情况下,任何包含名称值的Spring构造型注释(@Component、@Repository、@Service和@Controller)都将该名称提供给相应的bean定义。如果这样的注释不包含任何名称值或任何其他检测到的组件(例如自定义筛选器发现的组件),默认bean名称生成器将返回未大写的非限定类名。例如,如果检测到以下组件类,则名称为myMovieLister和movieFinderImpl:

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

如果不想依赖默认的bean命名策略,可以提供自定义bean命名策略,首先,实现BeanNameGenerator接口,并确保包含默认的无参数构造函数。然后,在配置扫描器时提供完全限定的类名,如下面的示例注释和bean定义所示:

@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

43、要为范围解析提供自定义策略,而不是依赖于基于注释的方法,您可以实现ScopeMetadataResolver接口。确保包含默认的无参数构造函数,然后,您可以在配置扫描程序时提供完全限定的类名,如下面的注释和bean定义示例所示:

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

在使用某些非单例作用域时,可能需要为作用域对象生成代理。推理在作用域bean中描述为依赖项。为此,组件扫描元素上有一个作用域代理属性。这三个可能的值是:no、interface和targetClass。例如,以下配置产生标准JDK动态代理:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

44、从Spring 3.0开始,Spring提供了对JSR-330标准注释(依赖注入)的支持。这些注释的扫描方式与Spring注释相同。要使用它们,您需要在类路径中包含相关的jar。

如果使用Maven,则使用javax.inject工件在标准Maven存储库中可用(http://repo1.maven.org/maven2/javax/inject/javax.inject/1/)。您可以将以下依赖项添加到文件pom.xml中:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

44.1、您可以使用@javax.inject.Inject而不是@Autowired。注入如下:

import javax.inject.Inject;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.findMovies(...);
        ...
    }
}

与@Autowired一样,可以在字段级、方法级和构造器参数级使用@Inject。

如果您想为应该注入的依赖项使用限定名,您应该使用@Named注释,如下面的示例所示:

import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

与@Autowired一样,@Inject也可以与java.util.Optional一起使用。可选或@Nullable。这在这里更加适用,因为@Inject没有必需的属性。下面的两个例子展示了如何使用@Inject和@Nullable:

public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}
public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

41.2、你可以使用@javax.inject.Named or javax.annotation.ManagedBean而不是@Component,如下例所示:

import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

当您使用@Named或@ManagedBean时,您可以使用与使用Spring注释时完全相同的方式使用组件扫描,如下面的示例所示:

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    ...
}

与@Component相反,JSR-330 @Named和JSR-250 ManagedBean注释是不可组合的。您应该使用Spring的原型模型来构建定制组件注释。

41.3、Spring组件模型元素与JSR-330变体的比较

Springjavax.inject.*javax.inject restrictions / comments

@Autowired

@Inject

@Inject has no 'required' attribute. Can be used with Java 8’s Optional instead.

@Component

@Named / @ManagedBean

JSR-330 does not provide a composable model, only a way to identify named components.

@Scope("singleton")

@Singleton

The JSR-330 default scope is like Spring’s prototype. However, in order to keep it consistent with Spring’s general defaults, a JSR-330 bean declared in the Spring container is a singleton by default. In order to use a scope other than singleton, you should use Spring’s @Scope annotation. javax.inject also provides a @Scope annotation. Nevertheless, this one is only intended to be used for creating your own annotations.

@Qualifier

@Qualifier / @Named

javax.inject.Qualifier is just a meta-annotation for building custom qualifiers. Concrete String qualifiers (like Spring’s @Qualifier with a value) can be associated through javax.inject.Named.

@Value

-

no equivalent

@Required

-

no equivalent

@Lazy

-

no equivalent

ObjectFactory

Provider

javax.inject.Provider is a direct alternative to Spring’s ObjectFactory, only with a shorter get() method name. It can also be used in combination with Spring’s @Autowired or with non-annotated constructors and setter methods.

42、基于java的容器配置

42.1、@Bean注释用于指示方法实例化、配置和初始化由Spring IoC容器管理的新对象,对于熟悉Spring的<beans/> XML配置的人来说,@Bean注释扮演的角色与<bean/>元素相同。您可以对任何Spring @Component使用@ bean注释的方法。但是,它们最常与@Configuration bean一起使用。

用@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>

当@Bean方法在没有使用@Configuration注释的类中声明时,它们被称为在“lite”模式下处理。在@Component中声明的Bean方法,甚至在普通的旧类中声明的Bean方法,都被认为是“lite”,包含类的主要目的是不同的,而@Bean方法是一种额外的好处。例如,服务组件可以通过每个应用组件类上的附加@Bean方法向容器公开管理视图,在这种情况下,@Bean方法是一种通用的工厂方法机制。与完整的@Configuration不同,lite @Bean方法不能声明bean之间的依赖关系。相反,它们对其包含组件的内部状态进行操作,并可选地对它们可能声明的参数进行操作。因此,这样的@Bean方法不应该调用其他@Bean方法。每个这样的方法实际上只是特定bean引用的工厂方法,没有任何特殊的运行时语义。这里的积极副作用是在运行时不需要应用CGLIB子类,所以在类设计方面没有限制(也就是说,包含的类可能是final,等等)。

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

42.2、使用AnnotationConfigApplicationContext实例化Spring容器

简单构造器

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

就像在实例化ClassPathXmlApplicationContext时使用Spring XML文件作为输入一样,在实例化AnnotationConfigApplicationContext时可以使用@Configuration类作为输入。这允许Spring容器完全不使用xml,如下面的示例所示:

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

正如前面提到的,AnnotationConfigApplicationContext并不仅限于使用@Configuration类。任何@Component或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();
}

通过使用register(Class<?>…)以编程方式构建容器。

您可以使用无参数构造函数实例化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();
}

启用组件扫描扫描(字符串…)

要启用组件扫描,可以按如下方式注释@Configuration类:

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

有经验的Spring用户可能熟悉Spring上下文:namespace中的等价XML声明,如下面的示例所示:

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

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元注释的,因此它们是组件扫描的候选者。

使用AnnotationConfigWebApplicationContext支持Web应用程序

AnnotationConfigApplicationContext的WebApplicationContext变体可以与AnnotationConfigWebApplicationContext一起使用。您可以在配置Spring ContextLoaderListener servlet侦听器、Spring MVC DispatcherServlet等时使用此实现。下面的网站。xml片段配置了一个典型的Spring MVC web应用程序(注意contextClass context-param和int -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>

42.3、@Bean是一个方法级注释,是XML <bean/>元素的直接模拟。注释支持<bean/>提供的一些属性,例如:* init-method * destroy-method * autowiring * name。可以在带@ configuration注释的类中使用@Bean注释,也可以在带@ component注释的类中使用@Bean注释。

您还可以使用接口(或基类)返回类型声明@Bean方法,如下面的示例所示:

@Configuration
public class AppConfig {

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

其中TransferService为接口

42.4、带@ bean注释的方法可以具有任意数量的参数,这些参数描述构建该bean所需的依赖项,例如,如果TransferService需要AccountRepository,我们可以使用方法参数来实现这个依赖关系,如下面的例子所示:

@Configuration
public class AppConfig {

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

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

42.5、使用@Bean注释定义的任何类都支持常规的生命周期回调,并且可以使用JSR-250中的@PostConstruct和@PreDestroy注释。还完全支持常规Spring生命周期回调。如果bean实现InitializingBean、DisposableBean或Lifecycle,则容器将调用它们各自的方法。还完全支持*Aware标准接口集(如BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware等)

@Bean注释支持指定任意的初始化和销毁回调方法,非常类似于Spring XML在bean元素上的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定义中添加@Bean(destroyMethod="")来禁用默认(推断)模式。

下面的例子展示了如何防止数据源的自动销毁回调:

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

对于上面的例子中的BeanOne,在构造过程中直接调用init()方法同样有效,如下面的例子所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}

42.6、@Configuration是一个类级别的注释,指示对象是bean定义的源。配置类通过公共的@Bean注释方法声明bean。还可以使用对@Configuration类上的@Bean方法的调用来定义bean之间的依赖关系

42.7、当bean之间存在依赖关系时,表示这种依赖关系就像让一个bean方法调用另一个bean方法一样简单,如下面的示例所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

在前面的示例中,beanOne通过构造函数注入接收到对beanTwo的引用。只有在@Configuration类中声明@Bean方法时,这种声明bean间依赖关系的方法才有效。不能使用普通的@Component类来声明bean之间的依赖关系。

42.8、所有@Configuration类在启动时都使用CGLIB子类化。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器是否缓存了(有作用域的)bean。

从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已经在org.springframework下重新打包。cglib,并直接包含在spring-core JAR中。

42.9、正如Spring XML文件中使用<import/>元素来帮助模块化配置一样,@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();
    }
}

42.10、下面的例子展示了如何将一个bean自动连接到另一个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中受支持。还请注意,如果目标bean只定义一个构造函数,则不需要指定@Autowired。在前面的示例中,在RepositoryConfig构造函数上不需要@Autowired。

可以通过参数来实现bean之间的依赖关系,如:

@Configuration
public class RepositoryConfig {

//    private final User user;

    /**
     * 如果目标bean定义了一个构造函数,则不需要指定@Autowired,下面的注解可以去掉
     * @param user
     */
//    @Autowired
//    public RepositoryConfig(User user) {
//        this.user = user;
//    }

    @Bean
    public Student student(User user) {
        return new Student(user);
    }
}

 

您希望在IDE中从一个@Configuration类直接导航到另一个@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");
}

 42.11、@Profile注释实际上是通过使用一个更灵活的名为@ condition的注释实现的。@ condition注释指示特定的org.springframework.context.annotation.Condition。在注册@Bean之前应该咨询的条件实现。

条件接口的实现提供了返回true或false的matches(…)方法。例如,下面的清单显示了@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;
}

 42.12、组合java和xml配置

以xml为中心使用@Configuration类

最好从XML引导Spring容器,并以一种特别的方式包含@Configuration类。

请记住,@Configuration类最终是容器中的bean定义。在本系列示例中,我们创建了一个名为AppConfig的@Configuration类,并将其包含在system-test-config中。xml作为<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());
    }
}

下面的示例展示了示例系统测试配置的一部分。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>

 

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元注释的,所以@Configuration注释的类自动成为组件扫描的候选类。使用与前面示例中描述的相同的场景,我们可以重新定义system-test-config。利用xml进行组件扫描

注意,在这种情况下,我们不需要显式地声明<context:annotation-config/>,因为<context:component-scan/>支持相同的功能。

下面的示例显示修改后的系统测试配置。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>

使用@ImportResource以配置类为中心的XML

在以@Configuration类为配置容器的主要机制的应用程序中,仍然可能需要至少使用一些XML。在这些场景中,您可以使用@ImportResource并只定义所需的XML,这样做可以实现一种“以java为中心”的容器配置方法,并将XML保持在最低限度。下面的示例(包括配置类、定义bean的XML文件、属性文件和主类)展示了如何使用@ImportResource注释来实现按需使用XML的“以java为中心”配置:

@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);
    // ...
}

43、@Profile注释允许您指出,当一个或多个指定的概要文件处于活动状态时,组件有资格进行注册。

@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)或概要文件表达式,配置文件表达式允许表达更复杂的配置文件逻辑(例如,production & us-east)。配置文件表达式支持以下操作符:

  • !: A logical “not” of the profile

  • &: A logical “and” of the profiles

  • |: A logical “or” of the profiles

如果不使用括号,就不能混合&和|操作符。例如,production & us-east | eu-central不是一个有效的表达式,它必须表示为production & (us-east | eu-central)。

如果@Configuration类被标记为@Profile,那么与该类关联的所有@Bean方法和@Import注释都将被绕过,除非一个或多个指定的概要文件是活动的。如果@Component或@Configuration类被标记为@Profile({"p1", "p2"}),那么除非配置文件'p1'或'p2'被激活,否则该类不会被注册或处理。如果给定的概要文件以NOT操作符(!)为前缀,那么只有在概要文件不活动时才注册带注释的元素。例如,给定@Profile({"p1", "!p2"}),如果配置文件'p1'是活动的,或者配置文件'p2'不是活动的,则会发生注册。

44、XML对应的是<beans>元素的profile属性。我们前面的示例配置可以用两个XML文件重写,如下所示:

<beans profile="development"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>

<beans profile="production"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

还可以避免在同一个文件中分割和嵌套<beans/>元素,如下面的示例所示:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- other bean definitions -->

    <beans profile="development">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>
</beans>

XML对应程序不支持前面描述的概要文件表达式。但是,可以使用!操作符。也可以通过嵌套配置文件来应用逻辑“and”,如下面的示例所示:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- other bean definitions -->

    <beans profile="production">
        <beans profile="us-east">
            <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
        </beans>
    </beans>
</beans>

在前面的示例中,如果生产和us-east配置文件都处于活动状态,则数据源bean将被公开。

45、激活概要文件可以通过几种方式来完成,但是最直接的方法是根据环境API(通过ApplicationContext可用)通过编程来完成。下面的例子展示了如何做到这一点:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

此外,还可以通过spring.profiles.active声明性地激活概要文件。活动属性,可以通过系统环境变量、JVM系统属性、web中的servlet上下文参数来指定。xml,甚至作为JNDI中的一个条目

通过编程,您可以为setActiveProfiles()方法提供多个配置文件名称,该方法接受String…varargs。下面的示例激活多个配置文件:

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");

声明spring.profiles.active可以接受以逗号分隔的配置文件名称列表,如下面的示例所示:

-Dspring.profiles.active="profile1,profile2"

46、Spring的环境抽象在可配置的属性源层次结构上提供搜索操作。考虑以下清单:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);

PropertySource是对任何键值对源的简单抽象,Spring的标准环境配置有两个PropertySource对象——一个表示JVM系统属性集(System.getproperties()),另一个表示系统环境变量集(System.getenv())。

具体地说,当您使用标准环境时,如果运行时存在my-property系统属性或my-propertyi环境变量,那么对env.containsProperty(“my-property”)的调用将返回true。

执行的搜索是分层的。默认情况下,系统属性优先于环境变量,因此,如果在调用env.getProperty(“my-property”)的过程中,my-property属性碰巧在两个地方都设置了,则系统属性值“获胜”并被返回。

对于一个公共的StandardServletEnvironment,完整的层次结构如下,其中最高优先级的条目位于顶部:

1、ServletConfig参数(如果适用——例如,对于DispatcherServlet上下文)

2、ServletContext参数(web.xml context-param条目)

3、JNDI环境变量(java:comp/env/ entries)

4、JVM系统属性(-D命令行参数)

5、JVM系统环境(操作系统环境变量)

最重要的是,整个机制是可配置的。也许您有一个想要集成到这个搜索中的自定义属性源。为此,实现并实例化您自己的PropertySource,并将其添加到当前环境的PropertySource集合中,下面的例子展示了如何做到这一点:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

在前面的代码中,MyPropertySource在搜索中以最高优先级添加。如果它包含my-property属性,则检测并返回该属性,以支持任何其他PropertySource中的任何my-property属性。MutablePropertySources API公开了许多方法,这些方法允许对属性源集进行精确操作

47、@PropertySource注释为向Spring的环境添加PropertySource提供了一种方便的声明性机制。

给定一个名为app.properties的文件,该文件包含键-值对testbean.name=myTestBean,下面的@Configuration类使用@PropertySource的方式是:调用testBean.getName()返回myTestBean:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

@PropertySource资源位置中出现的任何${…}占位符都根据已经在环境中注册的属性源集进行解析,如下面的示例所示:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

假设my.placeholder存在于已注册的属性源(例如,系统属性或环境变量)中,占位符解析为相应的值。如果不是,那么default/path将被用作默认值,如果没有指定默认值,且无法解析属性,则抛出IllegalArgumentException。

根据Java 8约定,@PropertySource注释是可重复的。但是,所有这些@PropertySource注释都需要在同一级别声明,要么直接在configuration类上声明,要么作为同一定制注释中的元注释声明,不建议混合使用直接注释和元注释,因为直接注释可以有效地覆盖元注释。

48、org.springframework.context包添加了ApplicationContext接口,该接口扩展了BeanFactory接口,此外还扩展了其他接口,以更面向应用程序框架的风格提供额外的功能。

许多人以一种完全声明式的方式使用ApplicationContext,甚至没有以编程方式创建它,而是依赖诸如ContextLoader这样的支持类来自动实例化ApplicationContext,这是Java EE web应用程序的正常启动过程的一部分。

为了以更面向框架的风格增强BeanFactory功能,上下文包还提供了以下功能:

  • 通过MessageSource接口以i18n风格访问消息。
  • 通过ResourceLoader接口访问资源,例如url和文件。
  • 事件发布,即通过使用ApplicationEventPublisher接口发布到实现ApplicationListener接口的bean
  • 通过HierarchicalBeanFactory接口加载多个(分层的)上下文,使每个上下文都集中于一个特定的层,例如应用程序的web层。

48.1、使用MessageSource国际化

ApplicationContext接口扩展了一个名为MessageSource的接口,因此提供了国际化(“i18n”)功能。Spring还提供了HierarchicalMessageSource接口,它可以分层地解析消息。这些接口共同提供了Spring影响消息解析的基础。在这些接口上定义的方法包括:

  • String getMessage(String code, Object[] args, String default, Locale loc):用于从MessageSource检索消息的基本方法,如果没有为指定的区域设置找到消息,则使用默认消息。通过使用标准库提供的MessageFormat功能,传入的任何参数都将成为替换值。
  • String getMessage(String code, Object[] args, Locale loc):本质上与前面的方法相同,但有一点不同:不能指定默认消息,如果找不到消息,则抛出NoSuchMessageException。
  • String getMessage(MessageSourceResolvable resolvable, Locale Locale):前面方法中使用的所有属性也被包装在名为MessageSourceResolvable的类中,您可以使用这个类使用这个方法

当装载ApplicationContext时,它会自动搜索上下文中定义的MessageSource bean。bean必须具有名称messageSource。如果找到这样的bean,则对前面方法的所有调用都委托给消息源。如果没有找到消息源,ApplicationContext将尝试查找包含同名bean的父bean ,如果是,则使用该bean作为MessageSource。如果ApplicationContext找不到任何消息源,则实例化一个空的DelegatingMessageSource,以便能够接受对上面定义的方法的调用。

Spring提供了两个MessageSource实现,ResourceBundleMessageSource和StaticMessageSource。它们都实现了HierarchicalMessageSource来执行嵌套消息传递。StaticMessageSource很少使用,但提供了向源添加消息的编程方法,下面的例子显示ResourceBundleMessageSource:

<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>

该示例假设在类路径中定义了三个名为format、exceptions和windows的资源包,任何解析消息的请求都以jdk标准的方式处理,即通过ResourceBundle对象解析消息。就本例而言,假设上述两个资源包文件的内容如下:

# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.

下一个示例显示了一个执行MessageSource功能的程序。请记住,所有ApplicationContext实现也是MessageSource实现,因此可以将其强制转换为MessageSource接口。

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", null);
    System.out.println(message);
}

以上程序的输出结果如下:

Alligators rock!

总之,MessageSource是在一个名为bean.xml的文件中定义的,它存在于类路径的根目录中。messageSource bean定义通过其basenames属性引用了许多资源束,列表中传递给basenames属性的三个文件作为类路径根目录下的文件存在,分别叫做format.properties, exceptions.propertieswindows.properties

下一个示例显示传递给消息查找的参数。这些参数被转换成字符串对象并插入到查找消息中的占位符中。

<beans>

    <!-- this MessageSource is being used in a web application -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- lets inject the above MessageSource into this POJO -->
    <bean id="example" class="com.something.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>

public class Example {

    private MessageSource messages;

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", null);
        System.out.println(message);
    }
}

execute()方法调用的结果输出如下:

The userDao argument is required.

关于国际化(“i18n”),Spring的各种MessageSource实现遵循与标准JDK ResourceBundle相同的地区解析和回退规则,简而言之,继续前面定义的示例messageSource,如果您希望根据英国(en-GB)地区解析消息,您将创建名为format_en_GB.properties, exceptions_en_GB.propertieswindows_en_GB.properties

通常,区域设置解析是由应用程序的周围环境管理的。在下面的例子中,解析(英国)消息的语言环境是手动指定的:

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}

运行上述程序的输出结果如下:

Ebagum lad, the 'userDao' argument is required, I say, required.

您还可以使用MessageSourceAware接口获取对已定义的任何MessageSource的引用。当创建和配置bean时,在实现MessageSourceAware接口的ApplicationContext中定义的任何bean都被注入应用程序上下文的MessageSource。

作为ResourceBundleMessageSource的替代方案,Spring提供了ReloadableResourceBundleMessageSource类。该变体支持相同的bundle文件格式,但比基于JDK的ResourceBundleMessageSource实现更灵活,特别是,它允许从任何Spring资源位置(不仅仅是从类路径)读取文件,并支持热重加载bundle属性文件(同时有效地在两者之间缓存它们)。有关详细信息,请参见ReloadableResourceBundleMessageSource

48.2、标准和定制事件

ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口提供的。如果实现ApplicationListener接口的bean部署到上下文中,那么每次将ApplicationEvent发布到ApplicationContext时,都会通知该bean。本质上,这是标准的观察者设计模式。

下表描述了Spring提供的标准事件:

EventExplanation
ContextRefreshedEvent在初始化或刷新ApplicationContext时发布(例如,通过在ConfigurableApplicationContext接口上使用refresh()方法)。在这里,“initialized”意味着加载了所有bean,检测并激活了后处理器bean,预实例化了单例,ApplicationContext对象准备好使用了。只要上下文没有关闭,就可以多次触发刷新,前提是所选的ApplicationContext实际上支持这种“热”刷新。例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持。
ContextStartedEvent通过在ConfigurableApplicationContext接口上使用start()方法启动ApplicationContext时发布,在这里,“started”意味着所有生命周期bean都接收显式的start信号。通常,此信号用于在显式停止后重新启动bean,但也可以用于启动未为autostart配置的组件(例如,尚未在初始化时启动的组件)。
ContextStoppedEvent通过在ConfigurableApplicationContext接口上使用stop()方法停止ApplicationContext时发布。在这里,“停止”意味着所有生命周期bean都接收一个明确的停止信号。停止的上下文可以通过start()调用重新启动。
ContextClosedEvent通过在ConfigurableApplicationContext接口上使用close()方法关闭ApplicationContext时发布。在这里,“closed”意味着所有单例bean都被销毁。封闭的环境达到它的生命终点。无法刷新或重新启动。
RequestHandledEvent一个特定于web的事件,它告诉所有bean一个HTTP请求已经得到了服务。此事件在请求完成后发布。此事件仅适用于使用Spring的DispatcherServlet的web应用程序。

您还可以创建和发布自己的自定义事件。下面的例子展示了一个扩展Spring的ApplicationEvent基类的简单类:

public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlackListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}

 要发布定制的ApplicationEvent,请在ApplicationEventPublisher上调用publishEvent()方法,通常,这是通过创建一个实现ApplicationEventPublisherAware的类并将其注册为Spring bean来实现的,下面的例子展示了这样一个类:

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blackList.contains(address)) {
            publisher.publishEvent(new BlackListEvent(this, address, content));
            return;
        }
        // send email...
    }
}

在配置时,Spring容器检测到EmailService实现了ApplicationEventPublisherAware,并自动调用setApplicationEventPublisher()。实际上,传入的参数是Spring容器本身,您正在通过应用程序的ApplicationEventPublisher接口与应用程序上下文进行交互。

要接收自定义ApplicationEvent,您可以创建一个实现ApplicationListener的类,并将其注册为Spring bean,下面的例子展示了这样一个类:

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

注意,ApplicationListener是通用定制事件的类型(前面示例中的BlackListEvent)参数化的。这意味着onApplicationEvent()方法可以保持类型安全,避免任何向下强制转换的需要。您可以注册任意数量的事件侦听器,但请注意,默认情况下,事件侦听器同步接收事件。这意味着publishEvent()方法将阻塞,直到所有侦听器完成对事件的处理。这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果事务上下文可用,它将在发布者的事务上下文中操作。如果需要另一种事件发布策略,请参阅javadoc获取Spring的 ApplicationEventMulticaster接口。

下面的示例展示了用于注册和配置上述每个类的bean定义:

<bean id="emailService" class="example.EmailService">
    <property name="blackList">
        <list>
            <value>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
    <property name="notificationAddress" value="blacklist@example.org"/>
</bean>

综上所述,当emailService bean的sendEmail()方法被调用时,如果有任何电子邮件消息应该被列入黑名单,则会发布一个BlackListEvent类型的自定义事件。blackListNotifier bean注册为ApplicationListener并接收BlackListEvent,此时它可以通知适当的方。

 

Spring的事件处理机制是为相同应用程序上下文中Spring bean之间的简单通信而设计的。然而,对于更复杂的企业集成需求,单独维护的Spring integration项目为构建基于知名Spring编程模型的轻量级、面向模式的、事件驱动的体系结构提供了完整的支持。

 

基于注解的事件监听器

从Spring 4.2开始,您可以使用EventListener注释在托管bean的任何公共方法上注册事件侦听器。BlackListNotifier可以重写如下:

public class BlackListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlackListEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

方法签名再次声明它要侦听的事件类型,但这次使用灵活的名称,并且没有实现特定的侦听器接口。

如果您的方法应该侦听多个事件,或者您希望在没有任何参数的情况下定义它,那么也可以在注释本身上指定事件类型。

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    ...
}

还可以使用定义SpEL表达式的注释的condition属性添加额外的运行时筛选,该表达式应该与实际调用特定事件的方法相匹配

下面的例子展示了如何重写我们的通知器,只有当事件的内容属性等于my-event时才调用它:

@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

每个SpEL表达式根据专用上下文计算。下表列出了上下文可用的项,以便您可以使用它们进行条件事件处理:

NameLocationDescriptionExample

Event

root object

The actual ApplicationEvent.

#root.event

Arguments array

root object

The arguments (as array) used for invoking the target.

#root.args[0]

Argument name

evaluation context

The name of any of the method arguments. If, for some reason, the names are not available (for example, because there is no debug information), the argument names are also available under the #a<#arg> where #arg stands for the argument index (starting from 0).

#blEvent or #a0 (you can also use #p0 or #p<#arg> notation as an alias)

如果在处理另一个事件时需要发布一个事件,可以更改方法签名以返回应该发布的事件,如下面的示例所示:

@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

 这个新方法为上面方法处理的每个BlackListEvent发布一个新的ListUpdateEvent。如果需要发布多个事件,则可以返回一个事件集合。

异步监听

如果希望某个侦听器异步处理事件,可以重用常规的@Async支持。下面的例子展示了如何做到这一点:

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

在使用异步事件时,请注意以下限制:

  • 如果事件侦听器抛出异常,则不会将其传播给调用方,请参见AsyncUncaughtExceptionHandler以获得更多详细信息。
  • 此类事件侦听器无法发送响应。如果在处理过程中需要发送另一个事件,则注入ApplicationEventPublisher手动发送事件。

顺序监听

如果您需要在调用另一个侦听器之前先调用一个侦听器,那么可以将@Order注释添加到方法声明中,如下面的示例所示:

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}

通用的事件

还可以使用泛型进一步定义事件的结构。考虑使用EntityCreatedEvent<T>,其中T是实际创建的实体的类型。例如,您可以创建以下侦听器定义来仅接收EntityCreatedEvent for a Person:

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    ...
}

由于类型擦除,只有当触发的事件解析事件侦听器筛选器所基于的泛型参数时(即类似于class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ }),该方法才有效。

 

48.3、方便访问底层资源

应用程序上下文是一个ResourceLoader,可用于加载资源对象,Resource本质上是JDK java.net.URL类的更富特性的版本,实际上,资源的实现在适当的情况下包装了java.net.URL的实例,资源可以以透明的方式从几乎任何位置获得低级资源,包括类路径、文件系统位置、任何可以用标准URL描述的位置,以及其他一些变体。如果资源位置字符串是一个没有任何特殊前缀的简单路径,则这些资源的来源特定于实际应用程序上下文类型。

您可以配置部署到应用程序上下文中的bean来实现特殊的回调接口,ResourceLoaderAware将在初始化时自动回调,应用程序上下文本身作为ResourceLoader传入,您还可以公开Resource类型的属性,以用于访问静态资源。它们像其他属性一样被注入其中。您可以将这些资源属性指定为简单的字符串路径,并依赖于一个特殊的JavaBean PropertyEditor(由上下文自动注册),以便在部署bean时将这些文本字符串转换为实际的资源对象。

48.4、方便的Web应用程序的ApplicationContext实例化

例如,您可以使用ContextLoader以声明的方式创建ApplicationContext实例,当然,您也可以通过使用ApplicationContext实现之一以编程方式创建ApplicationContext实例。

您可以使用ContextLoaderListener注册ApplicationContext,如下面的示例所示:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

侦听器检查contextConfigLocation参数。如果参数不存在,侦听器将使用/WEB-INF/applicationContext.xml作为默认值,当参数存在时,侦听器使用预定义的分隔符(逗号、分号和空格)分隔字符串,并使用值作为搜索应用程序上下文的位置,还支持ant样式的路径模式。例子/ web - inf / *context.xml(用于名称以context.xml结尾的所有文件)。和/WEB-INF/**/*上下文。xml(适用于WEB-INF的任何子目录中的所有此类文件)。

48.5、将Spring ApplicationContext部署为Java EE RAR文件

可以将Spring ApplicationContext部署为RAR文件,将上下文及其所需的所有bean类和库jar封装在Java EE RAR部署单元中,这相当于引导一个独立的ApplicationContext(仅托管在Java EE环境中)来访问Java EE服务器设施

49、BeanFactory API为Spring的IoC功能提供了基础。它的特定契约主要用于与Spring的其他部分和相关第三方框架的集成,它的DefaultListableBeanFactory实现是更高级别GenericApplicationContext容器中的关键委托。

BeanFactory和相关接口(如BeanFactoryAware、InitializingBean、DisposableBean)是其他框架组件的重要集成点

请注意,核心BeanFactory API级别及其DefaultListableBeanFactory实现不假设要使用的配置格式或任何组件注释,所有这些风格都是通过扩展(如XmlBeanDefinitionReader和AutowiredAnnotationBeanPostProcessor)实现的,并作为核心元数据表示对共享BeanDefinition对象进行操作。这就是Spring容器如此灵活和可扩展的本质。

49.1、BeanFactory和ApplicationContext容器级别之间的差异以及引导的含义。

您应该使用ApplicationContext,除非您有很好的理由不这样做,使用GenericApplicationContext及其子类AnnotationConfigApplicationContext作为自定义引导的常见实现,这些是Spring核心容器的主要入口点,用于所有常见目的:加载配置文件、触发类路径扫描、以编程方式注册bean定义和带注释的类,以及(从5.0开始)注册功能bean定义。

因为ApplicationContext包含beanFactory的所有功能,所以一般建议在普通beanFactory上使用它,除非需要完全控制bean处理的场景。在ApplicationContext(如GenericApplicationContext实现)中,有几种bean是通过约定(即通过bean名称或bean类型——特别是后处理器)检测的,而普通的DefaultListableBeanFactory对任何特殊的bean都是不可知的。

对于许多扩展的容器特性,例如注释处理和AOP代理,BeanPostProcessor扩展点是必不可少的。如果您只使用普通的DefaultListableBeanFactory,那么这种后处理器在默认情况下不会被检测和激活。这种情况可能令人困惑,因为您的bean配置实际上没有任何问题,相反,在这样的场景中,容器需要通过额外的设置完全引导。

下表列出了BeanFactory和ApplicationContext接口和实现提供的特性。

FeatureBeanFactoryApplicationContext
Bean实例化/布线YesYes
集成的生命周期管理NoYes
自动BeanPostProcessor登记NoYes
自动BeanFactoryPostProcessor登记NoYes
方便的MessageSource访问(用于内部化)NoYes
内置的ApplicationEvent发布机制NoYes

 要显式地向DefaultListableBeanFactory注册bean后处理器,需要以编程方式调用addBeanPostProcessor,如下面的示例所示:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());

// now start using the factory

要将BeanFactoryPostProcessor应用于一个普通的DefaultListableBeanFactory,您需要调用它的postProcessBeanFactory方法,如下面的示例所示:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

// bring in some property values from a Properties file
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// now actually do the replacement
cfg.postProcessBeanFactory(factory);

在这两种情况下,显式注册步骤都很不方便,这就是为什么在spring支持的应用程序中,各种ApplicationContext变体比普通的DefaultListableBeanFactory更受欢迎,特别是在典型企业设置中依赖于BeanFactoryPostProcessor和BeanPostProcessor实例实现扩展容器功能时。

一个AnnotationConfigApplicationContext拥有所有注册的公共注释后处理器,并且可以通过配置注释(例如@EnableTransactionManagement)在底层引入额外的处理器,在Spring基于注释的配置模型的抽象级别上,bean后处理器的概念仅仅是一个内部容器细节。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值