2015年9月10日星期四

3. Spring in Action -- Advanced wiring

1.  An environment-specific decision is made as to which beans will and won’t be created. But rather than make that decision at build time, Spring waits to make the decision at runtime. Consequently, the same deployment unit (perhaps a WAR file) will work in all environments without being rebuilt. In version 3.1, Spring introduced bean profiles. To use profiles, you must gather all the varying bean definitions into one or more profiles and then make sure the proper profile is active when your application is deployed in each environment.

2.  In Java configuration, you can use the @Profile annotation to specify which profile a bean belongs to:

@Configuration

@Profile("dev")

public class DevelopmentProfileConfig {

  @Bean(destroyMethod="shutdown")

  public DataSource dataSource() {
      …
  }
}

The @Profile annotation applied at the class level tells Spring that the beans in this configuration class should be created only if the dev profile is active.

3.  In Spring 3.1, you could only use the @Profile annotation at the class level. Starting with Spring 3.2, however, you can use @Profile at the method level, alongside the @Bean annotation.

4.  Any bean that isn’t given a profile will always be created, regardless of what profile is active.

5.  You can also configure profiled beans in XML by setting the profile attribute of the <beans> element. Rather than creating a proliferation of XML files for each environment, you also have the option of defining <beans> elements embedded in the root <beans> element.

6.  If spring.profiles.active is set, then its value determines which profiles are active. But if spring.profiles.active isn’t set, then Spring looks to spring.profiles.default. If neither spring.profiles.active nor spring.profiles.default is set, then there are no active profiles, and only those beans that aren’t defined as being in a profile are created. You can activate multiple profiles at the same time by listing the profile names, separated by commas.

7.  There are several ways to set these properties:
  • As initialization parameters on DispatcherServlet
  • As context parameters of a web application
  • As JNDI entries
  • As environment variables
  • As JVM system properties
  • Using the @ActiveProfiles annotation on an integration test class

8.  Spring 4 offers a more general-purpose mechanism for conditional bean definitions where the condition is up to you. Spring 4 introduced a new @Conditional annotation that can be applied to @Bean methods. If the prescribed condition evaluates to true, then the bean is created. Otherwise the bean is ignored. @Conditional is given a Class that specifies the condition. The class given to @Conditional can be any type that implements the Condition interface:

   public interface Condition {

    boolean matches(ConditionContext ctx, AnnotatedTypeMetadata metadata);

}

ConditionContext is an interface that looks something like this

public interface ConditionContext {

  BeanDefinitionRegistry getRegistry();

  ConfigurableListableBeanFactory getBeanFactory();

  Environment getEnvironment();

  ResourceLoader getResourceLoader();

  ClassLoader getClassLoader();

}

From the ConditionContext, you can do the following:
  • Check for bean definitions via the BeanDefinitionRegistry returned from getRegistry().
  • Check for the presence of beans, and even dig into bean properties via the ConfigurableListableBeanFactory returned from getBeanFactory().
  • Check for the presence and values of environment variables via the Environment retrieved from getEnvironment().
  • Read and inspect the contents of resources loaded via the ResourceLoader returned from getResourceLoader().
  • Load and check for the presence of classes via the ClassLoader returned from getClassLoader().
AnnotatedTypeMetadata offers you a chance to inspect annotations that may also be placed on the @Bean method:

public interface AnnotatedTypeMetadata {

  boolean isAnnotated(String annotationType);

  Map<String, Object> getAnnotationAttributes(String annotationType);

  Map<String, Object> getAnnotationAttributes(

          String annotationType, boolean classValuesAsString);

  MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);

  MultiValueMap<String, Object> getAllAnnotationAttributes(

          String annotationType, boolean classValuesAsString);

}

9.  Starting with Spring 4, the @Profile annotation has been refactored to be based on @Conditional and the Condition interface:

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.TYPE, ElementType.METHOD})

@Documented

@Conditional(ProfileCondition.class)

public @interface Profile {

  String[] value();

}

ProfileCondition implements Condition and considers several factors from both ConditionContext and AnnotatedTypeMetadata in making its decision:

class ProfileCondition implements Condition {

  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

    if (context.getEnvironment() != null) {

      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;

  }

}

10.  When there’s more than one matching bean, the ambiguity prevents Spring from autowiring the property, constructor argument, or method parameter and Spring throws a NoUniqueBeanDefinitionException.

11.  You can avoid autowiring ambiguity by designating one of the candidate beans as a primary bean. In the event of any ambiguity, Spring will choose the primary bean over any other candidate beans. You can express that favorite choice in Spring using the @Primary annotation. @Primary can be used either alongside @Component for beans that are component-scanned or alongside @Bean for beans declared in Java configuration. If you’re configuring your beans in XML, The <bean> element has a primary attribute to specify a primary bean.

12.  The primary attribute defaults to true. That means that all autowire candidates will be primary (and thus none will be preferred). So, to use primary, you’ll need to set it to false for all of the beans that are not the primary choice. If you’d rather eliminate some beans from consideration when autowiring, then you can set their autowire-candidate attribute to false.

13.  The @Qualifier annotation is the main way to work with qualifiers. It can be applied alongside @Autowired or @Inject at the point of injection to specify which bean you want to be injected. The parameter given to @Qualifier is the ID of the bean that you want to inject. To be more precise, @Qualifier ("iceCream") refers to the bean that has the String “iceCream” as a qualifier. For lack of having specified any other qualifiers, all beans are given a default qualifier that’s the same as their bean ID.

14.  Instead of relying on the bean ID as the qualifier, you can assign your own qualifier to a bean. All you need to do is place the @Qualifier annotation on the bean declaration. It can be applied alongside @Component and the @Bean annotation.

15.  Java doesn’t allow multiple annotations of the same type to be repeated on the same item. Java 8 allows repeated annotations, as long as the annotation is annotated with @Repeatable. Even so, Spring’s @Qualifier annotation isn’t annotated with @Repeatable.

16.  You can create a custom qualifier annotation by annotating the annotation class with @Qualifier:

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold { }

At the injection point, you can use any combination of custom qualifier annotations necessary to narrow the selection to the one bean that meets all your specifications.

17. You can also attach a qualifier to a bean in XML configuration through qualifier element :

<bean class="com.springinaction.springidol.Guitar">

  <qualifier value="stringed" />

</bean>

18.  Known in the Java Community Process as JSR-330 or more commonly as at inject, this specification brings a common dependency injection model to Java. As of Spring 3, Spring supports the at inject model. Google Guice and Picocontainer also support the JSR-330 model.

19.  The centerpiece of JSR-330 is the @Inject annotation. This annotation is an almost complete drop-in replacement for Spring’s @Autowired annotation.

20.  Just like @Autowired, @Inject can be used to autowire properties, methods, and constructors. Unlike @Autowired, @Inject doesn’t have a required attribute. Therefore, @Inject-annotated dependencies are expected to be fulfilled, failing with an exception if they’re not.

21.  @Inject’s answer to the @Qualifier annotation is the @Named annotation. The key difference between Spring’s @Qualifier and JSR-330’s @Named is one of semantics. Whereas @Qualifier helps narrow the selection of matching beans (using the bean’s ID by default), @Named specifically identifies a selected bean by its ID.

22.  Rather than inject a reference directly, you could ask @Inject to inject a Provider. The Provider interface enables, among other things, lazy injection of bean references and injection of multiple instances of a bean:

private Set<Knife> knives;

@Inject

public KnifeJuggler(Provider<Knife> knifeProvider) {

    knives = new HashSet<Knife>();

    for (int i = 0; i < 5; i++) {

        knives.add(knifeProvider.get());

   }

}

At this point, only the provider is injected. No actual Knife object will be injected until the get() method is called on the provider. Suppose the Knife bean is a prototype, we know that the Set of knives will be given five distinct Knife objects to work it.

23.  JSR-330 has its own @Qualifier annotation in the javax.inject package. Unlike Spring’s @Qualifier, the JSR-330 version isn’t intended to be used on its own. Instead, you’re expected to use it to create custom qualifier annotations, much as we did with Spring’s @Qualifier. In fact, the @Named annotation is just an annotation that’s itself annotated with @Qualifier.

24.  Spring 3.0 introduced @Value, a new wiring annotation that lets you wire primitive values such as int, boolean, and String using annotations. To use it, annotate a property, method, or method parameter with @Value and pass in a String expression to be wired into the property. The String parameter passed into @Value is just an expression—it can evaluate down to any type and thus @Value can be applied to just about any kind of property. SpEL lets you dynamically evaluate complex expressions, at runtime, into values to be wired into bean properties. That makes @Value a powerful wiring option:

@Value("#{systemProperties.myFavoriteSong}")

private String song;

25.  Annotation injection is performed before XML injection, thus the latter configuration will override the former for properties wired through both approaches.

26.  The <context:component-scan> element does everything that <context:annotation-config> does, plus it configures Spring to automatically discover beans and declare them for you. By default, <context:component-scan> looks for classes that are annotated with one of a handful of special stereotype annotations:
  • @Component—A general-purpose stereotype annotation indicating that the class is a Spring component
  • @Controller—Indicates that the class defines a Spring MVC controller
  • @Repository—Indicates that the class defines a data repository
  • @Service—Indicates that the class defines a service
  • Any custom annotation that is itself annotated with @Component
By default, the bean’s ID will be generated by camel-casing the class name. You can also specify a bean ID as a parameter to @Component.

27.  By adding <context:include-filter> and/or <context:exclude-filter> subelements to <context:component-scan>, you can tweak component-scanning behavior to your heart’s content:

<context:component-scan

        base-package="com.springinaction.springidol">

      <context:include-filter type="assignable"

          expression="com.springinaction.springidol.Instrument"/>

</context:component-scan>

In this case, we’re asking for all classes that are assignable to Instrument to be automatically registered as Spring beans.

28.  Component scanning can be customized using any of five kinds of filter types:
  • annotation (default): Filters scan classes looking for those annotated with a given annotation at the type level. The annotation to scan for is specified in the expression attribute.
  • assignable : Filters scan classes looking for those that are assignable to the type specified in the expression attribute.
  • aspectj : Filters scan classes looking for those that match the AspectJ type expression specified in the expression attribute.
  • custom : Uses a custom implementation of org.springframework.core.type.TypeFilter, as specified in the expression attribute.
  • regex : Filters scan classes looking for those whose class names match the regular expression specified in the expression attribute.

29.  By default, all Spring beans are singletons. When the container dispenses a bean (either through wiring or as the result of a call to the container’s getBean() method) it’ll always hand out the exact same instance of the bean. You have the option of declaring a scope for a bean. To force Spring to produce a new bean instance each time one is needed, you should declare the bean’s scope attribute to be prototype.

30.  Spring’s bean scopes let you declare the scope under which beans are created without hard-coding the scoping rules in the bean class itself:

Scope
What it does
singleton
Scopes the bean definition to a single instance per Spring container (default).
prototype
Allows a bean to be instantiated any number of times (once per use).
request
Scopes a bean definition to an HTTP request. Only valid when used with a web-capable Spring context (such as with Spring MVC).
session
Scopes a bean definition to an HTTP session. Only valid when used with a web-capable Spring context (such as with Spring MVC).
global-session
Scopes a bean definition to a global HTTP session. Only valid when used in a portlet context.


31.  Spring’s notion of singletons is limited to the scope of the Spring context. Unlike true singletons, which guarantee only a single instance of a class per classloader, Spring’s singleton beans only guarantee a single instance of the bean definition per the application context.

32.  To select an alternative type, you can use the @Scope annotation, either in conjunction with the @Component annotation or with the @Bean annotation. You can specify prototype scope by using the ConfigurableBeanFactory.SCOPE_PROTOTYPE. You could also use @Scope("prototype"), but using the SCOPE_PROTOTYPE constant is safer and less prone to mistakes. In the event that you’re configuring the bean in XML, you can set the scope using the scope attribute of the <bean> element.

33.  To apply session scope:

@Component

@Scope(

    value=WebApplicationContext.SCOPE_SESSION,

    proxyMode=ScopedProxyMode.INTERFACES)

public ShoppingCart cart() { ... }


The proxyMode attribute addresses a problem encountered when injecting a session- or request-scoped bean into a singleton-scoped bean:

@Component

public class StoreService {

  @Autowired

  public void setShoppingCart(ShoppingCart shoppingCart) {

    this.shoppingCart = shoppingCart;

  }

  ...

}

Because StoreService is a singleton bean, it will be created as the Spring application context is loaded. As it’s created, Spring will attempt to inject ShoppingCart into the setShoppingCart() method. But the ShoppingCart bean, being session scoped, doesn’t exist yet. There won’t be an instance of ShoppingCart until a user comes along and a session is created. Moreover, You don’t want Spring to inject just any single instance of ShoppingCart into StoreService. You want StoreService to work with the ShoppingCart instance for whichever session happens to be in play when StoreService needs to work with the shopping cart. Instead of injecting the actual ShoppingCart bean into StoreService, Spring should inject a proxy of the ShoppingCart bean. This proxy will expose the same methods as ShoppingCart so that for all StoreService knows, it is the shopping cart. But when StoreService calls methods on ShoppingCart, the proxy will lazily resolve it and delegate the call to the actual session-scoped ShoppingCart bean:


proxyMode is set to ScopedProxyMode.INTERFACES, indicating that the proxy should implement the ShoppingCart interface and delegate to the implementation bean. This is fine (and the most ideal proxy mode) as long as ShoppingCart is an interface and not a class. But if ShoppingCart is a concrete class, there’s no way Spring can create an interface-based proxy. Instead, it must use CGLib to generate a class-based proxy. So, if the bean type is a concrete class, you must set proxyMode to ScopedProxyMode.TARGET_CLASS to indicate that the proxy should be generated as an extension of the target class.

34.  <aop:scoped-proxy> is the Spring XML configuration’s counterpart to the @Scope annotation’s proxyMode attribute. It tells Spring to create a scoped proxy for the bean. By default, it uses CGLib to create a target class proxy. But you can ask it to generate an interface-based proxy by setting the proxy-target-class attribute to false:

<bean id="cart"

      class="com.myapp.ShoppingCart"

      scope="session">

  <aop:scoped-proxy proxy-target-class="false" />

</bean>

35. Spring offers two ways of evaluating values at runtime:

  • Property placeholders
  • The Spring Expression Language (SpEL)


36.  The following codes show a basic Spring configuration class that uses external properties to wire up a BlankDisc bean:

@Configuration

@PropertySource(“classpath:/com/soundsystem/app.properties”)

public class ExpressiveConfig{

    @Autowired

    Environment env;

    @Bean

    public BlankDisc disc() {

    return new BlankDisc( env.getProperty(“disc.title”) , env.getProperty(“disc.artist”));

  }

}

@PropertySource references a file named app.properties in the classpath. This properties file is loaded into Spring’s Environment, from which it can be retrieved later.

37.  If you use getProperty() methods of Environment class without specifying a default value, you’ll receive null if the property isn’t defined. If you want to require that the property be defined, you can use getRequiredProperty() in which case, if the property is undefined, an IllegalStateException will be thrown.

38.  Environment also offers some methods for checking which profiles are active:

  • String[] getActiveProfiles() —Returns an array of active profile names
  • String[] getDefaultProfiles() —Returns an array of default profile names
  • boolean acceptsProfiles(String... profiles) —Returns true if the environment supports the given profile(s)

39.  In Spring wiring, placeholder values are property names wrapped with ${ ... }:

<bean id="sgtPeppers"

      class="soundsystem.BlankDisc"

      c:_title="${disc.title}"

      c:_artist="${disc.artist}" />

40.  When relying on component-scanning and autowiring to create and initialize your application components, you can use the @Value annotation in much the same way as you might use the @Autowired annotation:

public BlankDisc(

      @Value("${disc.title}") String title,

      @Value("${disc.artist}") String artist) {

  this.title = title;

  this.artist = artist;

}


41.  In order to use placeholder values, you must configure either a PropertyPlaceholderConfigurer bean or a PropertySourcesPlaceholderConfigurer bean. Starting with Spring 3.1, PropertySourcesPlaceholderConfigurer is preferred because it resolves placeholders against the Spring Environment and its set of property sources. If you’d rather use XML configuration, the <context:property-placeholder> element from Spring’s context namespace will give you a PropertySourcesPlaceholderConfigurer bean.

42.  Spring 3 introduced the Spring Expression Language (SpEL), a powerful yet succinct way of wiring values into a bean’s properties or constructor arguments using expressions that are evaluated at runtime. SpEL has a lot of tricks up its sleeves, including

  • The ability to reference beans by their ID
  • Invoking methods and accessing properties on objects
  • Mathematical, relational, and logical operations on values
  • Regular expression matching
  • Collection manipulation


43.  SpEL expressions are framed with #{ ... }, much as property placeholders are framed with ${ ... }. What follows could be mixed with non-SpEL values as well:

<property name="message" value="The value is #{5}"/>

44.  Numbers can even be expressed in scientific notation:

<property name="capacity" value="#{1e4}"/>

45.  Literal String values can be expressed in SpEL with either single or double quote marks:

<property name="name" value="#{'Chuck'}"/>

46.  A couple of other literal values you may use are the Boolean true and false values:

<property name="enabled" value="#{false}"/>

47.  You could use SpEL to wire one bean into another bean’s property by using the bean ID as the SpEL expression:

<property name="instrument" value="#{saxophone}"/>

48.  You can refer to another bean’s property using the Spring Expression Language:

<bean id="carl" class="com.springinaction.springidol.Instrumentalist">
    <property name="song" value="#{kenny.song}" />
</bean>

The first part (the part before the period delimiter) refers to the kenny bean by its ID. The second part refers to the song attribute of the kenny bean. It’s effectively as if you programmatically performed the following Java code:

Instrumentalist carl = new Instrumentalist();
carl.setSong(kenny.getSong());

49.  You could also invoke a method of a bean:

<property name="song" value="#{songSelector.selectSong()}"/>

50.  ?. operator makes sure that the item to its left isn’t null before accessing the thing to its right:

<property name="song" value="#{songSelector.selectSong()?.toUpperCase()}"/>

If selectSong() were to return a null, then SpEL wouldn’t even try to invoke toUpperCase() on it.

51.  The key to working with class-scoped methods and constants in SpEL is to use the T() operator. The result of the T(java.lang.Math) is a Class object that represents java.lang.Math.

52.  You can reference the Math class’s PI constant by:

<property name="multiplier" value="#{T(java.lang.Math).PI}"/>

Similarly, static methods can also be invoked on the result of the T() operator:

<property name="randomNumber" value="#{T(java.lang.Math).random()}"/>

53.  SpEL includes several operators that you can use to manipulate the values of an expression:

Operation type
Operators
Arithmetic
+, -, *, /, %, ^
Relational
<, >, ==, <=, >=, It, gt, eq, le, ge
Logical
and, or, not, !
Conditional
?: (ternary), ?: (Elvis)
Regular expression
matches

54.  Unlike Java, SpEL also offers a power-of operator in the form of the carat:

<property name="area" value="#{T(java.lang.Math).PI * circle.radius ^ 2}"/>

55.  It is consistent with Java in that the + operator can be used to concatenate String values.

56.  The less-than and greater-than symbols pose a problem when using these expressions in Spring’s XML configuration, as they have special meaning in XML. So, when using SpEL in XML, it’s best to use SpEL’s textual alternatives to these operators:

Operation
Symbolic
Textual
Equals
==
eq
Less than
lt
Less than or equals
<=
le
Greater than
gt
Greater than or equals
>=
ge

57.  SpEL’s ternary operator works the same as Java’s ternary operator:

<property name="instrument" 
    value="#{songSelector.selectSong()=='Jingle Bells'?piano:saxophone}"/>

58.  SpEL offers a variant of the ternary operator that simplifies the following expression:

<property name="song" value="#{kenny.song != null ? kenny.song : 'Greensleeves'}"/>

which is Elvis operator:

<property name="song" value="#{kenny.song ?: 'Greensleeves'}"/>

59.  The matches operator attempts to apply a regular expression (given as its right-side argument) against a String value (given as the left-side argument). The result of a matches evaluation is a Boolean value: true if the value matches the regular expression, false otherwise:

<property name="validEmail" 
    value="#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}"/>

60.  The <util:list> element comes from Spring’s util namespace. It effectively creates a bean of type java.util.List that contains all of the values or beans that it contains:

<util:list id="cities">
  <bean class="com.habuma.spel.cities.City"
     p:name="Chicago" p:state="IL" p:population="2853114"/>
  <bean class="com.habuma.spel.cities.City"
     p:name="Atlanta" p:state="GA" p:population="537958"/>
  ...
</util:list>

61.  The most basic thing we could do with a collection is extract a single element out of the list and wire it into a property:

<property name="chosenCity" 
    value="#{cities[T(java.lang.Math).random() * cities.size()]}"/>

62.  The [] operator is also good for retrieving a member of a java.util.Map collection:

<property name="chosenCity" value="#{cities['Dallas']}"/>

63.  To load a properties configuration file into Spring:

<util:properties id="settings" location="classpath:settings.properties"/>

Here the settings bean will be a java.util.Properties that contains all of the entries in the file named settings.properties. With SpEL, you can access a property from <util:properties> in the same way you access a member of a Map:

<property name="accessToken" value="#{settings['twitter.accessToken']}"/>

64.  Spring makes two special selections of properties available to SpEL: systemEnvironment and systemPropertiessystemEnvironment contains all of the environment variables on the machine running the application:

<property name="homePath" value="#{systemEnvironment['HOME']}"/>

Meanwhile, systemProperties contains all of the properties that were set in Java as the application started (typically using the -D argument):

<property name="homePath" value="#{systemProperties['application.home']}"/>

65.  The [] operator can also be used on String values to retrieve a single character by its index within the String.

66.  The selection operator .?[] will create a new collection whose members include only those members from the original collection that meet the criteria expressed between the square braces:

<property name="bigCities" value="#{cities.?[population gt 100000]}"/>

SpEL also offers two other selection operators, .^[] and .$[], for selecting the first and last matching items (respectively) from a collection.

67.  Collection projection involves collecting a particular property from each of the members of a collection into a new collection. SpEL’s projection operator (.![]) can do exactly that:

<property name="cityNames" value="#{cities.![name]}"/>

But projection isn’t limited to projecting a single property:

<property name="cityNames" value="#{cities.![name + ', ' + state]}"/>

Now the cityNames property will be given a list containing values such as “Chicago, IL”, “Atlanta, GA”, and “Dallas, TX”.

没有评论:

发表评论