1.13. Environment

The Environment interface is an abstract presence integrated into the container that represents two key aspects of the application Environment: Profiles and Properties.

1.13.1. Bean Definition Profiles

Bean Definition Profiles provides a mechanism in the core container that allows different beans to be registered in different environments.

The value of spring.profiles.active can have multiple intermediate uses, separated by oneCopy the code

The term “environment” can mean different things to different users, and this feature can help in many use cases, including:

  • Work on in-memory data sources during development rather than looking up the same data sources from JNDI during QA or production.
  • Register the monitoring infrastructure only when the application is deployed into the performance environment.
  • Deploy custom implementations of the registration beans for customers A and B.

Consider the first use case we need to use in the real world: a DataSource. In a test environment, the configuration might look like the following:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}
Copy the code

Now, assuming that the data source for the application is registered in the JNDI directory of the production application server, consider how to deploy the application into a QA or production environment. Now, our dataSource bean looks like this listing:

@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
Copy the code

The question is how to switch between using these two variants depending on your current environment. Over time, Spring users have devised a number of ways to do this, often relying on a combination of system environment variables and XML statements containing placeholder, XML statements containing {placeholder}, XML statements containing placeholder, These {placeholder} are resolved to the correct configuration file path based on the value of the environment variable.

Bean Definition Profiles is a core container feature that provides a solution to this problem.

Using the @ Profile

The @Profile annotation enables the component to be registered only when one or more of the specified profiles that you specify are active. Using the previous example, we could rewrite the data source configuration as follows:

@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");
    }
}
Copy the code
As mentioned earlier, for @ Bean method, you usually choose to use programmatic JNDI lookup, method is to use the Spring JNDIMplate/JNDilocatorDeleteGate helper, or use the display directly JNDIInitialContext usage, Instead of the JndiObjectFactoryBean variable, because the factoryBean method returns the factoryBean type, not the DataSource type.Copy the code
Principle explanation: The 1.@Profile annotation specifies profilecondition.class @target ({elementtype.type, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(ProfileCondition.class) public @interface Profile { /** * The set of profiles for which the annotated component should be registered. */ String[] value(); } 2. In the first place at the time of loading beans found a method to judge whether it should be changed the current bean if (this. ConditionEvaluator. ShouldSkip (metadata, ConfigurationPhase.REGISTER_BEAN)) { configClass.skippedBeanMethods.add(methodName); return; } In shouldSkip, all the conditions of the current bean are queried and a matches loop is executed for each condition, while the @profile condition matches class ProfileCondition is shown below implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs ! = null) { for (Object value : Attrs.get ("value")) {// Here environment propertySources is called // Check whether all propertySources in the current configuration have the spring.profiles. Active property // If there is a value, set it to the activeProfiles property of the environment. // Then check whether the @profile value of the current class is included in the activeProfiles property. // If so, return true if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) { return true; } } return false; } return true; }}Copy the code

The profile string can contain either a simple profile name (such as production) or a profile expression. Configuration file expressions allow you to express more complex configuration file logic (such as Production & US-East). The following operators are supported in profile expressions:

  • ! : Logical “No” of the configuration file
  • & : logical “and” of the configuration file
  • | : the logic of the configuration file “or”
You can't in the case of not use parentheses mix & and | operator. For example, production & us - east | eu - central is not effective expression. It must be said for production & (us - east | eu - central).Copy the code

You can use its @Profile as a meta-annotation to create custom composite annotations.

The following example defines a custom @Production annotation that you can use as an alternative to @profile (” Production “)

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
Copy the code
If an @Configuration class is marked with an @profile, all @Bean methods and @import annotations associated with the class are ignored unless one or more of the specified profiles are active. If an @Component or @Configuration class is marked @profile ({" P1 ", "p2"}), the class will not be registered or processed unless Configuration files "P1" or "p2" are activated. If a given configuration file starts with the NOT operator (!) Is prefix, then annotated elements are registered only if the profile is not active. For example, given @profile ({"p1", "! P2 "}), if profile 'p1' is active or profile 'p2' is not active, the registration will take place.Copy the code

@profile can also be declared at the method level, scoping only to a particular Bean of the configuration class, as shown in the following example:

@Configuration public class AppConfig { @Bean("dataSource") @Profile("development") public DataSource standaloneDataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .addScript("classpath:com/bank/config/sql/test-data.sql") .build(); } @Bean("dataSource") @Profile("production") public DataSource jndiDataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); }}Copy the code
  • The standaloneDataSource method is only available in the Development configuration file.
  • The jndiDataSource method is only available in the Production configuration file.

For @profile on @bean methods, a special scenario may apply (within the same configuration class) : —– For overloaded @bean methods with the same Java method name (similar to constructor overloading), you need to declare the same @profile condition on all overloaded methods. The same condition is not declared because overloaded methods can be automatically selected. Because this batch of overloaded methods will all fail because the first method fails to pass the check, if the first method passes, it will continue to determine whether other overloaded methods can be used to register the bean. —– If the condition is inconsistent, only the condition declared first in the overloaded method takes effect. Therefore, @profile cannot be used to select overloaded methods with a specific parameter signature. Resolution between all factory methods of the same bean is created following Spring’s constructor resolution algorithm.

If you want to define alternative beans with different profile conditions, use a different @bean method name that points to the same bean name by using the name attribute of the @bean, as shown in the previous example.

If the parameter signatures are the same (for example, all variables have no ARG factory method), then this is the only way to represent this arrangement in the first place in a valid Java class (because there can only be a method with a specific name and parameter signature).

Analysis: / / whether the current @ Bean to skip / / here is the judgment of the Order in the Config class @ the sequence of Bean code has nothing to do with @ Order if (this. ConditionEvaluator. ShouldSkip (metadata, ConfigurationPhase.REGISTER_BEAN)) { configClass.skippedBeanMethods.add(methodName); return; } // When a method of the same name appears and has been skipped before, the skippedBeanMethods attribute is checked to see if it is included and skipped (configClass.skippedBeanMethods.contains(methodName)) { return; }Copy the code

The XML Bean defines the configuration file. The corresponding XML entry is the profile attribute of the element. Our previous example configuration can be overridden with two XML files, as follows:

<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>Copy the code

It is also possible to avoid splitting and nesting elements in the same file, as shown in the following example:

<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>Copy the code

Spring-bean.xsd has already restricted such an element to being the last element in a file. This will help provide flexibility without causing clutter in XML files.

The XML configuration file expression of corresponding item does not support described above — — — — — for example: (production & (us – east | eu – central)). However, it can be done by using! Operator to cancel the configuration file. Logical and can also be applied by nesting configuration files, as shown in the following example:

<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="..." > <! -- If both production and US-East profiles are active, <beans profile="production"> <beans profile=" US-east "> < JEE :jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> </beans> </beans>Copy the code
Default Profile

Default profile Indicates the profile that is enabled by default. Consider the following example:

@Configuration @Profile("default") public class DefaultDataConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .build(); }}Copy the code

If no configuration files are active, the dataSource is created. You can see that this is one way to provide a default definition for one or more beans.

If any profile is enabled, the default profile does not apply.

You can change the name of the default profile by setDefaultProfiles() or declaratively using the spring.profiles.default property.

1.13.2. PropertySource abstraction

Spring’s Environment abstraction provides search operations for configurable hierarchies of attribute sources.

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);
Copy the code

In the previous code snippet, we saw a way to ask Spring if the my-property property is defined for the current environment.

To answer this question, the Environment object performs a search on a set of PropertySource objects.

PropertySource is a simple abstraction of any key-value pair source, and Spring’s StandardEnvironment configuration has two PropertySource objects

  • A representation of the JVM System property set (system.getProperties ())
  • One represents the set of System environment variables (system.getenv ()).
These default property sources are provided for StandardEnvironment for stand-alone applications. StandardServletEnvironment use additional source of default attributes, including the servlet configuration and parameters of the servlet context. It can optionally enable JndiPropertySource.Copy the code
The search performed is hierarchical. By default, system attributes take precedence over environment variables. Therefore, if the my-property property is set at exactly both locations during the call to enp.getProperty (" my-property "), the system property value "wins" and is returned. Notice that the attribute values are not merged, but are completely overwritten by the previous entry. For common StandardServletEnvironment, complete hierarchy as shown below, the highest priority items at the top: The ServletConfig parameter (if applicable -- for example, In the DispatcherServlet context) ServletContext parameter (Web.xml context parameter) JNDI environment variables (Java :comp/env/ entries) JVM system properties (-d command line parameter) JVM system environment (operating system environment variables)Copy the code

Most importantly, the entire mechanism is configurable. Perhaps you have a custom property source that you want to integrate into this search. To do this, implement and instantiate your own instance PropertySource and add it to the Environment in the collection of PropertySourcescurrent. The following example shows how to do this:

ConfigurableApplicationContext ctx = new GenericApplicationContext(); MutablePropertySources sources = ctx.getEnvironment().getPropertySources(); //MyPropertySource has been added with the highest priority. sources.addFirst(new MyPropertySource());Copy the code

The MutablePropertySources API exposes a number of methods that allow precise manipulation of the property source set.

1.13.3. Using the @ PropertySource

The @propertysource annotation provides a convenient declaration mechanism for adding PropertySource to Spring’s environment.

Given a file named app.properties that contains the key-value pair testBean.name =myTestBean, the following @Configuration class uses @propertysource, Calling env.getProperty(” testBean.name “) returns 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; }}Copy the code

Any ${… that appears at the @propertysource resource location. } placeholders are resolved based on property sources already registered in the environment, as shown in the following example:

// Assuming my.placeholder exists in one of the registered property sources (for example, system property or environment variable), the placeholder will resolve to the corresponding value. // If not, default/path is used as the default value. // If a default value is not specified and the attribute cannot be resolved, IllegalArgumentException is thrown. @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; }}Copy the code
By Java 8 convention, the @propertysource annotation is repeatable. However, all such @propertysource annotations need to be declared at the same level, either directly on the configuration class or as meta-annotations within the same custom annotations. Mixing direct and meta-annotations is not recommended because the direct annotations effectively override the meta-annotations.Copy the code

1.13.4. In the past, placeholder values in elements could only be resolved based on JVM system properties or environment variables. That is no longer the case. Because the environment abstraction is integrated into the entire container, it is easy to parse placeholders. This means that you can configure the parsing process in any way you like. You can change the priority of search system properties and environment variables, or remove them entirely. You can also add your own property sources as appropriate.

Specifically, wherever a customer attribute is defined, as long as it is available in the environment, the following statements apply:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>
Copy the code
1.15. Additional functionality of ApplicationContext

As discussed in the introduction, org. Springframework. Beans. The factory package provides the basic function of management and operating bean, including programmatically. Org. Springframework. Context package added ApplicationContext interface, this interface extends the BeanFactory interface, and also expanded the other interfaces, with more application framework oriented style provide extra functionality.

Many people use ApplicationContext in a completely declarative way, not even creating it programmatically, but relying on supporting classes (such as ContextLoader) to automatically instantiate an ApplicationContext, As part of the normal startup process for Java EE Web applications.

To enhance the functionality of the BeanFactory in a more frame-oriented style, the context pack also provides the following capabilities:

  • Access i18N-style messages through the MessageSource interface.
  • Access resources, such as urls and files, through the ResourceLoader interface.
  • Event publishing, that is, through the use of ApplicationEventPublisher interface to realize interface ApplicationListener bean.
  • HierarchicalBeanFactory interface loads multiple (hierarchical) contexts, each focused on a specific layer, such as the Web layer of the application.
1.15.1. Internationalization uses MessageSource

The ApplicationContext interface extends an interface called MessageSource and therefore provides internationalization (” i18N “) functionality. Spring also provides HierarchicalMessageSource interface, this interface can be layered parse the message.

Together, these interfaces provide the basis for Spring’s implementation of message parsing.

Methods defined on these interfaces include:

  • String getMessage(String code, Object[] args, String default, Locale loc):

A basic method for retrieving messages from a MessageSource. If no message is found for the specified locale, the default message is used. Using the MessageFormat functionality provided by the standard library, any parameters passed in will become replacement values.

  • String getMessage(String code, Object[] args, Locale loc):

Essentially the same as the previous method, with one difference: you cannot specify a default message. If no message is found, NoSuchMessageException is thrown.

  • String getMessage(MessageSourceResolvable, Locale Locale): All attributes used in the previous method are also wrapped in a single class called MessageSourceResolvable, which can be used with this method.

When the ApplicationContext is loaded, it automatically searches for MessageSource Beans defined in the context. The bean name must be messageSource.

  • If such a bean is found, all calls to the previous methods are delegated to the message source.
  • If no message source is found, ApplicationContext will try to find the parent message source that contains the bean with the same name. If so, the bean is used as the message source.
  • If ApplicationContext does not find any message sources, instantiate an empty DelegatingMessageSource to accept calls to the methods defined above.

Spring provides two sources: ResourceBundleMessageSource and StaticMessageSource.

Both realized HierarchicalMessageSource to perform a nested messaging. The StaticMessageSource is rarely used, but it provides a programmatic way to add messages to the source.

The following example shows 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>
Copy the code

Properties, exceptions. Properties, and Windows.properties are defined in the classpath. Any request to parse a message is handled by the JDK standard way of parsing the message through a ResourceBundle object. For the purposes of this example, assume the contents of the two resource bundles above:

# Message =Alligators Rock! Properties argument. Required =The {0} argument is required.Copy the code

The next example shows a program that runs the MessageSource function. Keep in mind that all ApplicationContext implementations are also MessageSource implementations, so you can cast to the MessageSource interface.

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

The output of the above program is as follows:

Alligators rock!
Copy the code

In summary, MessageSource is defined in a file named beans.xml, which exists in the CLASspath. The MessageSource Bean definition references a number of resource bundles through its Basenames property. The three files passed to the Basenames attribute in the list exist as the root files of the classpath and are called format.properties, exceptions. Properties, and Windows.properties.

The next example shows the parameters passed to the message lookup.

These parameters are converted to string objects and inserted into placeholders in the lookup message.

<beans> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="exceptions"/> </bean> <bean id="example" class="com.something.Example"> <property name="messages"  ref="messageSource"/> </bean> </beans>Copy the code
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", Locale.ENGLISH); System.out.println(message); }}Copy the code

The result of the execute() method call is as follows:

The userDao argument is required.
Copy the code

Regarding internationalization (” I18N “), Spring’s various MessageSource implementations follow the same locale parsing and fallback rules as standard JDK Resourcebundles. In short, continuing with the example messageSource defined earlier, if you want to parse the message according to the United Kingdom (EN-GB) locale, You create the names format_en_gb.properties, ExceptionS_EN_Gb.properties, and windows_EN_gB.properties.

Typically, locale parsing is managed by the application’s surroundings. In the following example, manually specify the locale to parse the (UK) message:

# 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);
}
Copy the code

Running the above program results in the following output:

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

You can also use the MessageSourceAware interface to get a reference to any message source that you have defined. When a bean is created and configured, any bean defined in the ApplicationContext that implements the MessageSourceAware interface is injected into the MessageSourceAware interface of the ApplicationContext.

As an alternative ResourceBundleMessageSource, Spring provides a ReloadableResourceBundleMessageSource class. This variant support the same bundle file format, but is more flexible than based on the standard of the JDK ResourceBundleMessageSource implementation. In particular, it allows files to be read from any Spring resource location (not just from the classpath) and supports hot reloading of bundle property files (while effectively caching them). For more information, please see ReloadableResourceBundleMessageSource javadoc.Copy the code
1.15.2. Standard and custom events

Event handling in ApplicationContext is provided through the ApplicationEvent class and the ApplicationListener interface. If a bean implementing the ApplicationListener interface is deployed into the context, the bean will be notified whenever an ApplicationEvent is published to the ApplicationContext. In essence, this is the standard observer design pattern.

The event infrastructure has been significantly improved since Spring 4.2, with an annotation-based model and the ability to publish arbitrary events (that is, objects that do not need to be extended from ApplicationEvent). When publishing such objects, we wrap them in events for you.Copy the code

Table 7. Built-in events

The event instructions
ContextRefreshedEvent Published when the ApplicationContext is initialized or refreshed

For example, ConfigurableApplicationContext. Refresh () method



In this case, initialization means that all beans are loaded

The post-processor bean is detected and activated

The Singleton was pre-instantiated

And the ApplicationContext object is ready to use.



As long as the context is not closed

And the selected ApplicationContext actually supports this “hot” refresh

You can trigger the refresh multiple times

For example, XmlWebApplicationContext supports hot refreshing,

But GenericApplicationContext does not support.
ContextStartedEvent In ConfigurableApplicationContext. Start () method

Published when ApplicationContext is launched.



Here, start means that all lifecycle beans receive an explicit start signal.

Typically, this signal is used to restart beans after an explicit stop

, but it can also be used to start components that are not configured to start automatically

For example, components that have not been started at initialization.
ContextStoppedEvent In ConfigurableApplicationContext. Stop () method

Published when ApplicationContext is stopped.



“Stop” means that all lifecycle beans receive an explicit stop signal.

The stopped context can be restarted with the start() call.
ContextClosedEvent By using ConfigurableApplicationContext. Close () method

Or when the ApplicationContext is closed by the JVM shutdown hook.



, “Close” means that all singleton beans will be destroyed.

Once the context is closed,

It reaches the end of its life and cannot be refreshed or restarted.
RequestHandledEvent A Web-specific event,

Tell all beans that an HTTP request has been served.

This event is published after the request completes.

This event only applies to Web applications that use Spring’s DispatcherServlet.
ServletRequestHandledEvent A subclass of this class is RequestHandledEvent

Servlet-specific context information was added.

You can also create and publish your own custom events. The following example shows a simple class that extends Spring’s ApplicationEvent base class:

public class BlockedListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

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

    // accessor and other methods...
}
Copy the code

To publish a custom ApplicationEvent, please call on the ApplicationEventPublisher publishEvent () method.

Usually, this is by creating an implementation ApplicationEventPublisherAware and register for Spring bean class.

The following example shows this class:

public class EmailService implements ApplicationEventPublisherAware { private List<String> blockedList; private ApplicationEventPublisher publisher; public void setBlockedList(List<String> blockedList) { this.blockedList = blockedList; } public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } public void sendEmail(String address, String content) { if (blockedList.contains(address)) { publisher.publishEvent(new BlockedListEvent(this, address, content)); return; } // send email... }}Copy the code

When configured, the Spring container detects the ApplicationEventPublisherAware realize EmailService and automatically call setApplicationEventPublisher ().

In fact, the argument passed in is the Spring container itself. You are through its ApplicationEventPublisher interface to interact with the application context.

To receive custom ApplicationEvents, you create a class that implements ApplicationListener and registers it as a Spring Bean. The following example shows this class:

public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> { private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; } public void onApplicationEvent(BlockedListEvent event) { // notify appropriate parties via notificationAddress... }}Copy the code

Note that ApplicationListener is usually parameterized with the type of a custom event (in the previous example, BlockedListEvent). This means that the onApplicationEvent() method is type-safe and avoids downward casts. You can register any number of event listeners, but note that by default, event listeners receive events synchronously. This means that the publishEvent() method blocks until all listeners have finished processing the event. One advantage of this synchronous and single-threaded approach is that when the listener receives an event, it operates within the publisher’s transaction context, if available.

The following example shows the bean definitions used to register and configure each of the above classes:

<! Publish a custom event of type BlockedListEvent if there are any E-mail messages that need to be blocked when the sendEmail() method of the emailService bean is called. --> <bean id="emailService" class="example.EmailService"> <property name="blockedList"> <list> <value>[email protected]</value> <value>[email protected]</value> <value>[email protected]</value> </list> </property> </bean> <! --blockedListNotifier Bean registers as an ApplicationListener and receives BlockedListEvent, at which point it can notify the appropriate party. --> <bean id="blockedListNotifier" class="example.BlockedListNotifier"> <property name="notificationAddress" value="[email protected]"/> </bean>Copy the code
Spring's event mechanism is designed for simple communication between Spring beans in the same context. However, for more complex enterprise integration requirements, a separately maintained Spring Integration project provides complete support for building lightweight, pattern-oriented, event-driven architectures that build on the well-known Spring programming model.Copy the code
Annotation-based event listeners

Starting with Spring 4.2, you can register event listeners on any public method of a managed Bean using the @EventListener annotation.

This BlockedListNotifier can be rewritten as follows:

public class BlockedListNotifier { private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; } @EventListener public void processBlockedListEvent(BlockedListEvent event) { // notify appropriate parties via notificationAddress... }}Copy the code

The method signature again declares the type of event it listens for, but this time with a flexible name and without implementing a specific listener interface. Event types can be narrowed down through generics as long as the actual event type resolves generic parameters in its implementation hierarchy.

If your method should listen for multiple events, or if you want to define it without any parameters, you can also specify the event type on the annotation itself.

The following example shows how to do this:

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}
Copy the code

Additional runtime filtering can also be added by using the conditional attribute of the annotation that defines the SpEL expression, which should match the actual method call for a particular event.

The following example shows how our notifier can be overridden to be called only if the Content property is equal to my-Event:

@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blockedListEvent) {
    // notify appropriate parties via notificationAddress...
}
Copy the code

Each SpEL expression is evaluated against a specific context. The following table lists the items available for context so that you can use them for conditional event processing:

Table 8. Metadata available for Event SpEL

The name of the example
Event #root.event or event
Parameters of the array #root.args or args;

Args [0] accesses the first parameter, etc.
The parameter name # # blEvent or a0

(You can also use the # P0 or #p<#arg> argument notation as an alias.)
Note that root.event gives you access to the underlying event, even if your method signature actually refers to any published object.Copy the code

If you need to publish an event as a result of processing another event, you can change the method signature to return the event that should be published, as shown in the following example:

@EventListener public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) { // notify appropriate parties via  notificationAddress and // then publish a ListUpdateEvent... }Copy the code
Asynchronous listeners. This feature is not supported.Copy the code

This new method publishes a new ListUpdateEvent for each BlockedListEvent processed. If you need to publish multiple events, you can return a Collection event.

Asynchronous listeners

If you need a specific listener to handle events asynchronously, you can reuse the regular @async support. The following example shows how to do this:

@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}
Copy the code

When using asynchronous events, be aware of the following limitations:

  • If an asynchronous event listener throws an Exception, it is not propagated to the caller.
  • Asynchronous event listener methods cannot publish subsequent events by returning values. If you need to release another event as a result of processing, please insert a ApplicationEventPublisher to publish events manually.
Ordering Listeners

If you need to invoke a listener first, you can add the @Order annotation to the method declaration, as shown in the following example:

@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}
Copy the code
General events

You can also use generics to further define the structure of events. Consider using EntityCreatedEvent, where T is the type of the actual entity being created. For example, you could create the following listener definition to receive EntityCreatedEvent for only one person:

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}
Copy the code

Due to type erasure, only the triggered event resolves the generic parameter on which the event listener is based (that is, class PersonCreatedEvent extends EntityCreatedEvent {… }), this method will work.

In some cases, this can become very tedious if all events follow the same structure (as they should in the previous example). In this case, you can implement the ResolvableTypeProvider to guide the framework provided by the runtime environment. The following event shows how to do this:

public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider { public EntityCreatedEvent(T entity) { super(entity); } @Override public ResolvableType getResolvableType() { return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource())); }}Copy the code
This applies not only to any object that ApplicationEvent is sent as an event, but also to that object.Copy the code
1.15.3. Easy access to low-level resources

To optimize usage and understand application context, you should be familiar with Spring’s Resource classes, as described in Resources.

The application context is a ResourceLoader that can be used to load resource objects.

Resource is essentially a feature-rich version of the JDK java.net.URL class. In fact, the Resource implementation wraps an instance of java.net.URL when appropriate.

Resource can transparently fetch underlying resources from almost any location, including the classpath, file system location, any location that can be described using a standard URL, and some other variations.

If the Resource location strings are simple paths without any special prefixes, then the source of these resources is specific and appropriate to the actual application context type.

You can configure beans deployed into the application context to implement a special callback interface, ResourceLoaderAware, that is automatically called back upon initialization, and the application context itself is passed in as ResourceLoader. You can also expose properties of Resource type that can be used to access static resources. Resource can be injected just like any other property.

You can specify these resource properties as simple string paths and rely on automatic conversion from these text strings to actual resource objects when deploying beans.

The location path, or path, provided to the ApplicationContext constructor is actually a resource string and is handled appropriately in a simple form, depending on the specific context implementation. ClassPathXmlApplicationContext will, for example, a simple path as a classpath location. You can also use a location path (resource string) with a special prefix to force the definition to be loaded from the classpath or URL, regardless of what the actual type below is.

1.15.4. Application start trace

ApplicationContext manages the life cycle of Spring applications and provides a rich programming model around components. As a result, complex applications can have equally complex component diagrams and startup phases.

Tracking an application’s startup steps using specific metrics can help you understand where startup time is spent, and it can also serve as a way to better understand the overall context lifecycle.

AbstractApplicationContext (sub) by ApplicationStartup detection, it collects StartupStep data about different startup phase:

  • Application context lifecycle (basic packet scanning, configuration class management)
  • Bean life cycle (instantiation, intelligent initialization, post-processing)
  • Application event handling

Below is the instrumentation AnnotationConfigApplicationContext example:

/ / create and start recording StartupStep scanPackages = this. GetApplicationStartup (.) start (" spring. The context. The base - packages. The scan "); ScanPackages. Tag ("packages", () -> Arrays. ToString (basePackages)); // Execute the actual phase we are measuring this.scanner.scan(basePackages); / / end scanPackages. End ();Copy the code
1.15.5. Convenient instantiation of the ApplicationContext for Web applications

For example, a ContextLoader can be used to create an ApplicationContext instance declaratively. Of course, you can also programmatically create an ApplicationContext instance using one of the ApplicationContext implementations.

You can register an ApplicationContext with ContextLoaderListener, as shown in the following example:

<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>
Copy the code

The listener checks the contextConfigLocation parameter. If the parameter does not exist, the listener will use/WEB – INF/applicationContext. XML as the default values.

When parameters do exist, the listener separates the string with predefined delimiters (commas, semicolons, and whitespace) and uses these values as a place to search for application context.

Ant style path patterns are also supported. For example: / web-INF /* context.xml (for all files in the web-INF directory whose names end in Context)

/ web-INF /**/* context.xml (for all such files in any web-INF subdirectory)

1.16.1. The BeanFactory or ApplicationContext?

This section explains the difference between the BeanFactory and ApplicationContext container levels, and what bootstrap means.

You should use ApplicationContext, unless you have a very good reason not to do so, use GenericApplicationContext and its subclasses AnnotationConfigApplicationContext guided as custom general implementation. These are the main entry points for the Spring core container for all the common purposes: loading configuration files, triggering classpath scanning, programmatically registering bean definitions and annotated classes, and (starting with 5.0) registering functional bean definitions.

Because the ApplicationContext contains all of the functionality of a BeanFactory, it is generally recommended that it be superior to a regular BeanFactory, except in scenarios where full control of bean processing is required.

For many of the extended container features, such as annotation processing and AOP proxies, BeanPostProcessor extension points are essential. If only use ordinary DefaultListableBeanFactory, by default, will not detect the post-processor and activate it.

The following table lists the features provided by the BeanFactory and ApplicationContext interfaces and implementations. Table 9. Functional matrix

Characteristics of the BeanFactory ApplicationContext
Bean instantiation/wiring Yes Yes
Integrated lifecycle management No Yes
Automatic BeanPostProcessor registration No Yes
Easy message source access (for internalization) No Yes
Built-in ApplicationEvent publishing mechanism No Yes

To use explicit processor DefaultListableBeanFactory after registered Bean, you need to programmatically invoke addBeanPostProcessor, as shown in the example below:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());

// now start using the factory
Copy the code

To apply a spring BeanFactoryPostProcessor into an ordinary DefaultListableBeanFactory, you need to call it postProcessBeanFactory method, shown in example is shown in the following:

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

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

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

As seen above, manual registration can be quite inconvenient, especially when relying on the BeanFactoryPostProcessor and BeanPostProcessor extensions.

A AnnotationConfigApplicationContext registered all public comments after the processor, and may through configuration annotations (such as @ EnableTransactionManagement) introducing additional processors in the background. At the abstraction level of Spring's annotation-based configuration model, the concept of a bean post-processor is just a detail inside the container.Copy the code