10. Spring AOP APIs

10.1 Introduction

The previous chapter described the Spring’s support for AOP using @AspectJ and schema-based aspect definitions. In this chapter we discuss the lower-level Spring AOP APIs and the AOP support used in Spring 1.2 applications. For new applications, we recommend the use of the Spring 2.0 and later AOP support described in the previous chapter, but when working with existing applications, or when reading books and articles, you may come across Spring 1.2 style examples. Spring 4.0 is backwards compatible with Spring 1.2 and everything described in this chapter is fully supported in Spring 4.0.

10.2 Pointcut API in Spring

Let’s look at how Spring handles the crucial pointcut concept.

10.2.1 Concepts

Spring’s pointcut model enables pointcut reuse independent of advice types. It’s possible to target different advice using the same pointcut.

The org.springframework.aop.Pointcut interface is the central interface, used to target advices to particular classes and methods. The complete interface is shown below:

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}

Splitting the Pointcut interface into two parts allows reuse of class and method matching parts, and fine-grained composition operations (such as performing a "union" with another method matcher).

The ClassFilter interface is used to restrict the pointcut to a given set of target classes. If the matches() method always returns true, all target classes will be matched:

public interface ClassFilter {

    boolean matches(Class clazz);
}

The MethodMatcher interface is normally more important. The complete interface is shown below:

public interface MethodMatcher {

    boolean matches(Method m, Class targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class targetClass, Object[] args);
}

The matches(Method, Class) method is used to test whether this pointcut will ever match a given method on a target class. This evaluation can be performed when an AOP proxy is created, to avoid the need for a test on every method invocation. If the 2-argument matches method returns true for a given method, and the isRuntime() method for the MethodMatcher returns true, the 3-argument matches method will be invoked on every method invocation. This enables a pointcut to look at the arguments passed to the method invocation immediately before the target advice is to execute.

Most MethodMatchers are static, meaning that their isRuntime() method returns false. In this case, the 3-argument matches method will never be invoked.

[Tip]Tip

If possible, try to make pointcuts static, allowing the AOP framework to cache the results of pointcut evaluation when an AOP proxy is created.

10.2.2 Operations on pointcuts

Spring supports operations on pointcuts: notably, union and intersection.

  • Union means the methods that either pointcut matches.
  • Intersection means the methods that both pointcuts match.
  • Union is usually more useful.
  • Pointcuts can be composed using the static methods in the org.springframework.aop.support.Pointcuts class, or using the ComposablePointcut class in the same package. However, using AspectJ pointcut expressions is usually a simpler approach.

10.2.3 AspectJ expression pointcuts

Since 2.0, the most important type of pointcut used by Spring is org.springframework.aop.aspectj.AspectJExpressionPointcut. This is a pointcut that uses an AspectJ supplied library to parse an AspectJ pointcut expression string.

See the previous chapter for a discussion of supported AspectJ pointcut primitives.

10.2.4 Convenience pointcut implementations

Spring provides several convenient pointcut implementations. Some can be used out of the box; others are intended to be subclassed in application-specific pointcuts.

Static pointcuts

Static pointcuts are based on method and target class, and cannot take into account the method’s arguments. Static pointcuts are sufficient - and best - for most usages. It’s possible for Spring to evaluate a static pointcut only once, when a method is first invoked: after that, there is no need to evaluate the pointcut again with each method invocation.

Let’s consider some static pointcut implementations included with Spring.

Regular expression pointcuts

One obvious way to specify static pointcuts is regular expressions. Several AOP frameworks besides Spring make this possible. org.springframework.aop.support.JdkRegexpMethodPointcut is a generic regular expression pointcut, using the regular expression support in JDK 1.4+.

Using the JdkRegexpMethodPointcut class, you can provide a list of pattern Strings. If any of these is a match, the pointcut will evaluate to true. (So the result is effectively the union of these pointcuts.)

The usage is shown below:

<bean id="settersAndAbsquatulatePointcut"
        class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

Spring provides a convenience class, RegexpMethodPointcutAdvisor, that allows us to also reference an Advice (remember that an Advice can be an interceptor, before advice, throws advice etc.). Behind the scenes, Spring will use a JdkRegexpMethodPointcut. Using RegexpMethodPointcutAdvisor simplifies wiring, as the one bean encapsulates both pointcut and advice, as shown below:

<bean id="settersAndAbsquatulateAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref bean="beanNameOfAopAllianceInterceptor"/>
    </property>
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

RegexpMethodPointcutAdvisor can be used with any Advice type.

Attribute-driven pointcuts

An important type of static pointcut is a metadata-driven pointcut. This uses the values of metadata attributes: typically, source-level metadata.

Dynamic pointcuts

Dynamic pointcuts are costlier to evaluate than static pointcuts. They take into account method arguments, as well as static information. This means that they must be evaluated with every method invocation; the result cannot be cached, as arguments will vary.

The main example is the control flow pointcut.

Control flow pointcuts

Spring control flow pointcuts are conceptually similar to AspectJ cflow pointcuts, although less powerful. (There is currently no way to specify that a pointcut executes below a join point matched by another pointcut.) A control flow pointcut matches the current call stack. For example, it might fire if the join point was invoked by a method in the com.mycompany.web package, or by the SomeCaller class. Control flow pointcuts are specified using the org.springframework.aop.support.ControlFlowPointcut class.

[Note]Note

Control flow pointcuts are significantly more expensive to evaluate at runtime than even other dynamic pointcuts. In Java 1.4, the cost is about 5 times that of other dynamic pointcuts.

10.2.5 Pointcut superclasses

Spring provides useful pointcut superclasses to help you to implement your own pointcuts.

Because static pointcuts are most useful, you’ll probably subclass StaticMethodMatcherPointcut, as shown below. This requires implementing just one abstract method (although it’s possible to override other methods to customize behavior):

class TestStaticPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method m, Class targetClass) {
        // return true if custom criteria match
    }
}

There are also superclasses for dynamic pointcuts.

You can use custom pointcuts with any advice type in Spring 1.0 RC2 and above.

10.2.6 Custom pointcuts

Because pointcuts in Spring AOP are Java classes, rather than language features (as in AspectJ) it’s possible to declare custom pointcuts, whether static or dynamic. Custom pointcuts in Spring can be arbitrarily complex. However, using the AspectJ pointcut expression language is recommended if possible.

[Note]Note

Later versions of Spring may offer support for "semantic pointcuts" as offered by JAC: for example, "all methods that change instance variables in the target object."

10.3 Advice API in Spring

Let’s now look at how Spring AOP handles advice. 让我们看看 Spring AOP 是怎么处理 advice的

10.3.1 Advice lifecycles Advice 生命周期

Each advice is a Spring bean. An advice instance can be shared across all advised objects, or unique to each advised object. This corresponds to per-class or per-instance advice.

每个Advice都是Spring里面的 Bean。 Advice有2种: 第一种叫 per-class Advice 此 Advice的实例在所有的目标对象的实例中共享。 第二种叫 per-instance Advice 此 Advice的实例在每个目标对象的实例中唯一不能共享。

Per-class advice is used most often. It is appropriate for generic advice such as transaction advisors. These do not depend on the state of the proxied object or add new state; they merely act on the method and arguments.

Per-class Advice 用的最多。 它适合用来做通用的Advice例如事物管理的Advice, 它不会为代理的目标对象保存状态和添加状态,他们仅仅适用于方法和参数的拦截。

Per-instance advice is appropriate for introductions, to support mixins. In this case, the advice adds state to the proxied object.

Per-instance advice 适合做介绍,支持融合(混合)。在这种情况下,他为代理对象添加状态。

It’s possible to use a mix of shared and per-instance advice in the same AOP proxy. 在相同的AOP代理中可以混用 per-class advice 和 per-instance advice

10.3.2 Advice types in Spring

Spring Advice的类型

Spring provides several advice types out of the box, and is extensible to support arbitrary advice types. Let us look at the basic concepts and standard advice types.

Spring 提供了一些开箱即用的advice类型,并且通过扩展可以支持任意的advice类型。让我们看看基本概念和标准的advice类型

Interception around advice 拦截 around Advice

The most fundamental advice type in Spring is interception around advice. 在Spring 中最基本的advice 类型是 __拦截 around Advice

Spring is compliant with the AOP Alliance interface for around advice using method interception. MethodInterceptors implementing around advice should implement the following interface:

Spring 用 around Adivice 来服从 AOP联盟的接口。方法拦截器 实现 around advice 需要实现以下接口:

7月3号开始

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}

The MethodInvocation argument to the invoke() method exposes the method being invoked; the target join point; the AOP proxy; and the arguments to the method. The invoke() method should return the invocation’s result: the return value of the join point. invoke() 方法的参数 MethodInvocation 在被调用的时候暴漏 方法;目标切入点;AOP代理;和参数给这个方法。 invoke()方法需要返回调用者的结果:返回切入点的值

A simple MethodInterceptor implementation looks as follows: 下面是一个简单的MethodInterceptor的实现:

public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }
}

Note the call to the MethodInvocation’s proceed() method. This proceeds down the interceptor chain towards the join point. Most interceptors will invoke this method, and return its return value. However, a MethodInterceptor, like any around advice, can return a different value or throw an exception rather than invoke the proceed method. However, you don’t want to do this without good reason!

注意MethodInvocation调用的proceed() 方法。这些procee方法在这个加入点上击败拦截器链( 这些procee方法在加入点上要比拦截器链要好)。 大部分拦截器会调用这个方法,和返回他的值。然而,像任何的around Advice一样能够返回不同的值和抛出异常胜于调用这个 procce方法。 然而,除非有更好的理由你应该不想这么做。

[Note]Note

MethodInterceptors offer interoperability with other AOP Alliance-compliant AOP implementations. The other advice types discussed in the remainder of this section implement common AOP concepts, but in a Spring-specific way. While there is an advantage in using the most specific advice type, stick with MethodInterceptor around advice if you are likely to want to run the aspect in another AOP framework. Note that pointcuts are not currently interoperable between frameworks, and the AOP Alliance does not currently define pointcut interfaces.

方法拦截器提供与其他AOP联盟标准的AOP的互操作性实现。本章节的其余部分讨论其余的advice类型实现共同的AOP概念,但是是spring的实现方式。

虽然用最具体的advice类型有优势,但是如果你想在另外的AOP框架中运行坚持使用 around advice 方法拦截器。

请注意这个切入点目前在不同框架间不能相互操作,AOP联盟目前也不能定义切入点接口。

7月4号开始

Before advice

 前置 Advice
A simpler advice type is a __before advice__. This does not need a `MethodInvocation`
object, since it will only be called before entering the method.
__前置 Advice 是更简单的 Advice。因为它只在进入方法之前调用,所以它不需要`MethodInvocation` 对象

The main advantage of a before advice is that there is no need to invoke the proceed() method, and therefore no possibility of inadvertently failing to proceed down the interceptor chain.

前置advice的一个主要优势是不需要调用 proceed() 方法, 因此它不可能调用失败 proceed方法,在这一点上击败了 拦截器链。

The MethodBeforeAdvice interface is shown below. (Spring’s API design would allow for field before advice, although the usual objects apply to field interception and it’s unlikely that Spring will ever implement it). 下面展示MethodBeforeAdvice 接口(对于字段上的advice拦截器比较常用,虽然spring在设计上也支持,但是spring应该不太可能实现它)

public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}

Note the return type is void. Before advice can insert custom behavior before the join point executes, but cannot change the return value. If a before advice throws an exception, this will abort further execution of the interceptor chain. The exception will propagate back up the interceptor chain. If it is unchecked, or on the signature of the invoked method, it will be passed directly to the client; otherwise it will be wrapped in an unchecked exception by the AOP proxy. 注意返回类型是void。前置 advice 可以在加入点之前执行用户的行为,但是不能改变返回值。 如果一个前置advice抛出了一个异常,拦截器链将不会继续执行。这个异常将会传播回拦截器链。 如果这个异常没有被检查,或者标记的方法被调用时,它将直接传递给客户端,否则这个异常将会被AOP代理包装成一个未检查异常

An example of a before advice in Spring, which counts all method invocations: 下面是spring里面的一个前置advice例子,它将统计所有的方法调用:

public class CountingBeforeAdvice implements MethodBeforeAdvice {

    private int count;

    public void before(Method m, Object[] args, Object target) throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}

7月5号开始

[Tip]Tip

Before advice can be used with any pointcut. 前置Advice 能够使用在任何的切入点

Throws advice 抛出Advice

Throws advice is invoked after the return of the join point if the join point threw an exception. Spring offers typed throws advice. Note that this means that the org.springframework.aop.ThrowsAdvice interface does not contain any methods: It is a tag interface identifying that the given object implements one or more typed throws advice methods. These should be in the form of:

如果一个连接点抛出一个异常,Throws advice 将会在连接点返回之后调用。Spring提供 throws advice 类型。 请注意这就意味着 org.springframework.aop.ThrowsAdvice接口不会包含任何方法:它是一个标签,给实现它的对象一个或者多个throws advice 方法。这些应该是以下的形式

afterThrowing([Method, args, target], subclassOfThrowable)

Only the last argument is required. The method signatures may have either one or four arguments, depending on whether the advice method is interested in the method and arguments. The following classes are examples of throws advice. 只有最后一个参数是必须的。这个方法签名会有1个或者4个参数,依赖于 是否 建议的方法 在方法或者参数上感兴趣。 下面的类是 throws advice的例子:

The advice below is invoked if a RemoteException is thrown (including subclasses): 如果RemoteException (包括子类)被抛出, 下面的advice将会被调用

public class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}

The following advice is invoked if a ServletException is thrown. Unlike the above advice, it declares 4 arguments, so that it has access to the invoked method, method arguments and target object: 如果ServletException被抛出 则下面的advice将会被调用。与上述 advice不同的是 ,它定义了4个参数,因此它可以访问调用 的方法,方法参数和目标对象

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}

7月6号开始

The final example illustrates how these two methods could be used in a single class, which handles both RemoteException and ServletException. Any number of throws advice methods can be combined in a single class. 最后的例子说明了2个方法可以用到一个类中,处理RemoteExceptionServletException。 任何数目的 throws advice方法能够在一个类中结合。

public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}
[Note]Note

If a throws-advice method throws an exception itself, it will override the original exception (i.e. change the exception thrown to the user). The overriding exception will typically be a RuntimeException; this is compatible with any method signature. However, if a throws-advice method throws a checked exception, it will have to match the declared exceptions of the target method and is hence to some degree coupled to specific target method signatures. Do not throw an undeclared checked exception that is incompatible with the target method’s signature!

如果throws-advice方法自己抛出异常,它将会重载原始异常(改变异常抛给用户)。这重载的异常是一个典型的运行时异常;这将兼容任何的方法签名。 然而,如果throws-advice方法抛出一个受检查异常, 它必须匹配目标方法声明的异常,他今后一定程度上耦合在特定的方法签名上。 不要抛出未检查异常,它和目标方法的签名不兼容。

[Tip]Tip

Throws advice can be used with any pointcut. Throws advice 能够被用在任何的切入点

After Returning advice 后置advice

An after returning advice in Spring must implement the org.springframework.aop.AfterReturningAdvice interface, shown below: 后置advice 必须实现 org.springframework.aop.AfterReturningAdvice 接口,请看下面示例:

public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable;
}

7月7号开始

An after returning advice has access to the return value (which it cannot modify), invoked method, methods arguments and target. 后置advice可以访问调用的方法,方法参数,和目标的返回值(不能修改)

The following after returning advice counts all successful method invocations that have not thrown exceptions: 下面的后置advice会统计所有没有抛出异常的成功方法调用:

public class CountingAfterReturningAdvice implements AfterReturningAdvice {

    private int count;

    public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}

This advice doesn’t change the execution path. If it throws an exception, this will be thrown up the interceptor chain instead of the return value. 上述advice不会改变执行路径。如果它抛出异常,它将会向上抛给拦截器链来代理返回值

[Tip]Tip

After returning advice can be used with any pointcut. 后置advice 可以被用在任何的切入点上

7月8号开始

Introduction advice 介绍的Advice

Spring treats introduction advice as a special kind of interception advice. Spring 把 introduction advice 当成一种特殊的拦截器advice

Introduction requires an IntroductionAdvisor, and an IntroductionInterceptor, implementing the following interface: 介绍需要IntroductionAdvisorIntroductionInterceptor,需要实现以下接口:

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}

The invoke() method inherited from the AOP Alliance MethodInterceptor interface must implement the introduction: that is, if the invoked method is on an introduced interface, the introduction interceptor is responsible for handling the method call - it cannot invoke proceed(). invoke()方法继承自AOP联盟的MethodInterceptor接口,它必须实现这个introduction: 如下就是, 如果调用的方法在被介绍的接口,这个介绍的拦截器负责处理方法调用,他能够调用proceed()方法。

Introduction advice cannot be used with any pointcut, as it applies only at class, rather than method, level. You can only use introduction advice with the IntroductionAdvisor, which has the following methods: Introduction advice 不能被用在任何的加入点,只能被用在类而不是方法级别。introduction advice只能和IntroductionAdvisor在一起用, 例如下面的方法:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class[] getInterfaces();
}

There is no MethodMatcher, and hence no Pointcut, associated with introduction advice. Only class filtering is logical.

这里没有MethodMatcher,因此没有Pointcut,和introduction advice连接。只有类的过滤是合乎逻辑的。

The getInterfaces() method returns the interfaces introduced by this advisor. getInterfaces() getInterfaces()方法返回

The validateInterfaces() method is used internally to see whether or not the introduced interfaces can be implemented by the configured IntroductionInterceptor.

Let’s look at a simple example from the Spring test suite. Let’s suppose we want to introduce the following interface to one or more objects:

public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}

7月9号开始

This illustrates a mixin. We want to be able to cast advised objects to Lockable, whatever their type, and call lock and unlock methods. If we call the lock() method, we want all setter methods to throw a LockedException. Thus we can add an aspect that provides the ability to make objects immutable, without them having any knowledge of it: a good example of AOP.

Firstly, we’ll need an IntroductionInterceptor that does the heavy lifting. In this case, we extend the org.springframework.aop.support.DelegatingIntroductionInterceptor convenience class. We could implement IntroductionInterceptor directly, but using DelegatingIntroductionInterceptor is best for most cases.

The DelegatingIntroductionInterceptor is designed to delegate an introduction to an actual implementation of the introduced interface(s), concealing the use of interception to do so. The delegate can be set to any object using a constructor argument; the default delegate (when the no-arg constructor is used) is this. Thus in the example below, the delegate is the LockMixin subclass of DelegatingIntroductionInterceptor. Given a delegate (by default itself), a DelegatingIntroductionInterceptor instance looks for all interfaces implemented by the delegate (other than IntroductionInterceptor), and will support introductions against any of them. It’s possible for subclasses such as LockMixin to call the suppressInterface(Class intf) method to suppress interfaces that should not be exposed. However, no matter how many interfaces an IntroductionInterceptor is prepared to support, the IntroductionAdvisor used will control which interfaces are actually exposed. An introduced interface will conceal any implementation of the same interface by the target.

Thus LockMixin extends DelegatingIntroductionInterceptor and implements Lockable itself. The superclass automatically picks up that Lockable can be supported for introduction, so we don’t need to specify that. We could introduce any number of interfaces in this way.

Note the use of the locked instance variable. This effectively adds additional state to that held in the target object.

public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {

    private boolean locked;

    public void lock() {
        this.locked = true;
    }

    public void unlock() {
        this.locked = false;
    }

    public boolean locked() {
        return this.locked;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
            throw new LockedException();
        }
        return super.invoke(invocation);
    }

}

7月10号开始

Often it isn’t necessary to override the invoke() method: the DelegatingIntroductionInterceptor implementation - which calls the delegate method if the method is introduced, otherwise proceeds towards the join point - is usually sufficient. In the present case, we need to add a check: no setter method can be invoked if in locked mode.

The introduction advisor required is simple. All it needs to do is hold a distinct LockMixin instance, and specify the introduced interfaces - in this case, just Lockable. A more complex example might take a reference to the introduction interceptor (which would be defined as a prototype): in this case, there’s no configuration relevant for a LockMixin, so we simply create it using new.

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}

We can apply this advisor very simply: it requires no configuration. (However, it is necessary: It’s impossible to use an IntroductionInterceptor without an IntroductionAdvisor.) As usual with introductions, the advisor must be per-instance, as it is stateful. We need a different instance of LockMixinAdvisor, and hence LockMixin, for each advised object. The advisor comprises part of the advised object’s state.

We can apply this advisor programmatically, using the Advised.addAdvisor() method, or (the recommended way) in XML configuration, like any other advisor. All proxy creation choices discussed below, including "auto proxy creators," correctly handle introductions and stateful mixins.

10.4 Advisor API in Spring

在Spring中, 一个通知器就是一个包含通知和切入点表达式的切面.

除了一些特殊的情况, 任意的通知可以应用于任何的通知器. org.springframework.aop.support.DefaultPointcutAdvisor 是最常用的通知器类. 例如, 它可以被用于 MethodInterceptor, BeforeAdvice or ThrowsAdvice.

在Spring的相同的AOP代理中,通知和通知器混用是可行. 例如, 在一个代理配置中你 可以使用环绕通知,异常通知和前置通知: Spring将会自动创建拦截器链.

10.5 Using the ProxyFactoryBean to create AOP proxies

如果你正在使用 Spring IoC 容器(ApplicationContext或者BeanFactory) 管理你的业务对象 - 你会想要使用Spring的AOP FactoryBeans. (请记住一个factory bean是为了引进一种分层的 机制,可以创建不同类型的对象.)

[Note]Note

Spring AOP支持使用factory beans避免被覆盖.

在Spring当中创建代理的最好的方式是使用org.springframework.aop.framework.ProxyFactoryBean. 对于切入点和通知的应用和顺序,它提供了完成的控制. 然后, 当你需要太多的控制时,更好的方式是通过使用简单的选项.

10.5.1 Basics

ProxyFactoryBean的实现就像其他的Spring的FactoryBean, 起到了分层的作用. 如果你定义了一个名为fooProxyFactoryBean, 当引用了foo看到不会是ProxyFactoryBean的实例, 而是通过ProxyFactoryBean实现的getObject()方法创建的对象.这个方法将会一个AOP的代理包装在目标类上.

使用ProxyFactoryBean或者其他IoC-aware类来创建AOP代理,其中一个很大的好处就是通知和切入点将会 被IoC容器管理. 这是一个很有用特性,当它开启时可以很好的确保不被其他的AOP框架所获得. 例如, 一个通知可能引用到应用内的对象 (除了目标类之外, 这些都是可以在任何AOP框架内获得的), 有利的是通过依赖注入,这些都是可插拔的.

10.5.2 JavaBean properties

通常情况下,Spring提供的FactoryBean实现, ProxyFactoryBean本身就是一个JavaBean. 有以下属性可以被设置:

一些关键的属性继承于org.springframework.aop.framework.ProxyConfig (在Spring中,这个所有Aop代理工厂的超类). 这些关键的属性包括:

  • proxyTargetClass: true的情况下将使用类的代理, 而不是基于接口的代理. 当属性设置为true的情况下,CGLIB代理将会被创建。(见文 <aop-pfb-proxy-types>>).
  • optimize: controls whether or not aggressive optimizations are applied to proxies created via CGLIB. One should not blithely use this setting unless one fully understands how the relevant AOP proxy handles optimization. 只能使用 CGLIB 的代理; JDK 动态代理此项配置是无效的.
  • frozen: if a proxy configuration is frozen, then changes to the configuration are no longer allowed. This is useful both as a slight optimization and for those cases when you don’t want callers to be able to manipulate the proxy (via the Advised interface) after the proxy has been created. The default value of this property is false, so changes such as adding additional advice are allowed.
  • exposeProxy: determines whether or not the current proxy should be exposed in a ThreadLocal so that it can be accessed by the target. If a target needs to obtain the proxy and the exposeProxy property is set to true, the target can use the AopContext.currentProxy() method.

其他可以指定的属性对于ProxyFactoryBean包括:

  • proxyInterfaces: String数组的接口名称. 如果没有找到这个接口, CGLIB的代理将会被创建 (可以见文 Section 10.5.3, “JDK- and CGLIB-based proxies”).
  • interceptorNames: String数组的Advisor, 拦截器或者其它的通知名称将会被应用。 数组顺序的是有重大意义的, 这些拦截器和通知将会被顺序的应用.

    这些名称是bean的名称存在于当前的工厂中, 包括它父类的工厂中。
    你将不能引用到bean,当`ProxyFactoryBean`中的通知不是使用单例.

当你需要添加一个拦截器时,你可以添加一个星号 ( *). 整个应用中的拦截器的名称匹配星号前面的内容的拦截器将会被应用. 一个样例可以在这里找到 Section 10.5.6, “Using global advisors”.

  • singleton: 默认使用的单例模式, 不管调用getObject()的方法有多频繁。默认值是true. 你想要使用普通状态的对象,可以配置成false.

10.5.3 JDK- and CGLIB-based proxies

这个章节是关于 ProxyFactoryBean 最后的文档,提供了说明对于特别类是要是要使用基于类的代理还是JDK动态代理.

[Note]Note

ProxyFactoryBean创建基于CGLIB的代理还是JDK动态代理的行为已经发生改变 对于Spring的 1.2.x 和Spring 2.0 版本. The ProxyFactoryBean now exhibits similar semantics with regard to auto-detecting interfaces as those of the TransactionProxyFactoryBean class.

If the class of a target object that is to be proxied (hereafter simply referred to as the target class) doesn’t implement any interfaces, then a CGLIB-based proxy will be created. This is the easiest scenario, because JDK proxies are interface based, and no interfaces means JDK proxying isn’t even possible. One simply plugs in the target bean, and specifies the list of interceptors via the interceptorNames property. Note that a CGLIB-based proxy will be created even if the proxyTargetClass property of the ProxyFactoryBean has been set to false. (Obviously this makes no sense, and is best removed from the bean definition because it is at best redundant, and at worst confusing.)

If the target class implements one (or more) interfaces, then the type of proxy that is created depends on the configuration of the ProxyFactoryBean.

If the proxyTargetClass property of the ProxyFactoryBean has been set to true, then a CGLIB-based proxy will be created. This makes sense, and is in keeping with the principle of least surprise. Even if the proxyInterfaces property of the ProxyFactoryBean has been set to one or more fully qualified interface names, the fact that the proxyTargetClass property is set to true will cause CGLIB-based proxying to be in effect.

If the proxyInterfaces property of the ProxyFactoryBean has been set to one or more fully qualified interface names, then a JDK-based proxy will be created. The created proxy will implement all of the interfaces that were specified in the proxyInterfaces property; if the target class happens to implement a whole lot more interfaces than those specified in the proxyInterfaces property, that is all well and good but those additional interfaces will not be implemented by the returned proxy.

If the proxyInterfaces property of the ProxyFactoryBean has not been set, but the target class does implement one (or more) interfaces, then the ProxyFactoryBean will auto-detect the fact that the target class does actually implement at least one interface, and a JDK-based proxy will be created. The interfaces that are actually proxied will be all of the interfaces that the target class implements; in effect, this is the same as simply supplying a list of each and every interface that the target class implements to the proxyInterfaces property. However, it is significantly less work, and less prone to typos.

10.5.4 Proxying interfaces

Let’s look at a simple example of ProxyFactoryBean in action. This example involves:

  • A target bean that will be proxied. This is the "personTarget" bean definition in the example below.
  • An Advisor and an Interceptor used to provide advice.
  • An AOP proxy bean definition specifying the target object (the personTarget bean) and the interfaces to proxy, along with the advices to apply.
<bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name" value="Tony"/>
    <property name="age" value="51"/>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person"
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>

    <property name="target" ref="personTarget"/>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

Note that the interceptorNames property takes a list of String: the bean names of the interceptor or advisors in the current factory. Advisors, interceptors, before, after returning and throws advice objects can be used. The ordering of advisors is significant.

[Note]Note

You might be wondering why the list doesn’t hold bean references. The reason for this is that if the ProxyFactoryBean’s singleton property is set to false, it must be able to return independent proxy instances. If any of the advisors is itself a prototype, an independent instance would need to be returned, so it’s necessary to be able to obtain an instance of the prototype from the factory; holding a reference isn’t sufficient.

The "person" bean definition above can be used in place of a Person implementation, as follows:

Person person = (Person) factory.getBean("person");

Other beans in the same IoC context can express a strongly typed dependency on it, as with an ordinary Java object:

<bean id="personUser" class="com.mycompany.PersonUser">
    <property name="person"><ref bean="person"/></property>
</bean>

The PersonUser class in this example would expose a property of type Person. As far as it’s concerned, the AOP proxy can be used transparently in place of a "real" person implementation. However, its class would be a dynamic proxy class. It would be possible to cast it to the Advised interface (discussed below).

It’s possible to conceal the distinction between target and proxy using an anonymous inner bean, as follows. Only the ProxyFactoryBean definition is different; the advice is included only for completeness:

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>
    <!-- Use inner bean, not local reference to target -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name" value="Tony"/>
            <property name="age" value="51"/>
        </bean>
    </property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

This has the advantage that there’s only one object of type Person: useful if we want to prevent users of the application context from obtaining a reference to the un-advised object, or need to avoid any ambiguity with Spring IoC autowiring. There’s also arguably an advantage in that the ProxyFactoryBean definition is self-contained. However, there are times when being able to obtain the un-advised target from the factory might actually be an advantage: for example, in certain test scenarios.

10.5.5 Proxying classes

What if you need to proxy a class, rather than one or more interfaces?

Imagine that in our example above, there was no Person interface: we needed to advise a class called Person that didn’t implement any business interface. In this case, you can configure Spring to use CGLIB proxying, rather than dynamic proxies. Simply set the proxyTargetClass property on the ProxyFactoryBean above to true. While it’s best to program to interfaces, rather than classes, the ability to advise classes that don’t implement interfaces can be useful when working with legacy code. (In general, Spring isn’t prescriptive. While it makes it easy to apply good practices, it avoids forcing a particular approach.)

If you want to, you can force the use of CGLIB in any case, even if you do have interfaces.

CGLIB proxying works by generating a subclass of the target class at runtime. Spring configures this generated subclass to delegate method calls to the original target: the subclass is used to implement the Decorator pattern, weaving in the advice.

CGLIB proxying should generally be transparent to users. However, there are some issues to consider:

  • Final methods can’t be advised, as they can’t be overridden.
  • There is no need to add CGLIB to your classpath. As of Spring 3.2, CGLIB is repackaged and included in the spring-core JAR. In other words, CGLIB-based AOP will work "out of the box" just as do JDK dynamic proxies.

There’s little performance difference between CGLIB proxying and dynamic proxies. As of Spring 1.0, dynamic proxies are slightly faster. However, this may change in the future. Performance should not be a decisive consideration in this case.

10.5.6 Using global advisors

By appending an asterisk to an interceptor name, all advisors with bean names matching the part before the asterisk, will be added to the advisor chain. This can come in handy if you need to add a standard set of global advisors:

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="service"/>
    <property name="interceptorNames">
        <list>
            <value>global*</value>
        </list>
    </property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>

10.6 简洁的代理定义

特别是在定义事务代理的时候, 你有可能最后都会有许多相似的代理定义. 使用父子bean定义, 以及内部bean定义, 可以创作出更加干净和简洁的代理定义.

首先, 为代理定义一个父模板bean定义:

<bean id="txProxyTemplate" abstract="true"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

这并不能让它被实例化, 所以其实应该是未完成的. 然后每一个需要创建的代理都是它的子bean定义, 这些bean都 包含一个target的内部bean定义, 景观target永远不会以它自己的方式使用.

<bean id="myService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MyServiceImpl">
        </bean>
    </property>
</bean>

当然可以覆盖父模板里面的属性, 就像这个例子一样, 覆盖事务的传播属性设置:

<bean id="mySpecialService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MySpecialServiceImpl">
        </bean>
    </property>
    <property name="transactionAttributes">
        <props>
            <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="store*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

记住, 在上面的例子中, 我们实际上是使用了前面介绍的abstract 属性来把bean标记为abstract, 从而使得它不能被实例化的. 应用上下文(不是简单的bean工厂)默认将会 预先实例化所有的单例. 所以这是很重要的(至少对单例bean来说), 如果你有一个(父)bean定义想用来仅仅作为 模板, 并且这个定义指明了一个类, 你必须确保设置了abstract属性为true, 否则应用上下文将会尝试 预先实例化它.

10.7 使用编程方式通过ProxyFactory创建AOP代理

在Spring中用编程的方式创建AOP代理是很简单的. 这样就使得你可以使用Spring AOP而不需要依赖于Spring IoC.

下面列举的就是为目标对象创建代理, 以及设置拦截器和通知器. 被目标对象实现的接口将会被自动代理:

ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();

第一步是构造一个类型为org.springframework.aop.framework.ProxyFactory的对象. 你可以像上面例子 那样创建的时候指定目标对象, 或者使用其他的替代的构造函数来指定需要代理的接口.

你可以添加advice(把拦截器作为一种特殊的advice)和/或者通知器, 并且操控它们来为ProxyFactory的生命周期 服务. 如果你添加了一个IntroductionInterceptionAroundAdvisor, 你就可以让代理支持实现附加的接口.

在ProxyFactory中也有许多方便的方法(继承自AdvisedSupport), 他们允许你添加其他类型的advice, 比如 前置或者抛出异常. AdvisedSupport是ProxyFactory和ProxyFactoryBean的父类.

[Tip]Tip

在IoC框架中集成AOP代理的创建在大多数应用中是最好的选择. 我们建议你在一般情况下使用AOP的Java代码来 完成具体的配置.

10.8 操作通知对象

当你创建了AOP代理, 你可以通过org.springframework.aop.framework.Advised接口来操作它们. 所有的 AOP代理都可以类型转换到这个接口, 不管它实现的其他接口. 该接口包含了如下的方法:

Advisor[] getAdvisors();

void addAdvice(Advice advice) throws AopConfigException;

void addAdvice(int pos, Advice advice) throws AopConfigException;

void addAdvisor(Advisor advisor) throws AopConfigException;

void addAdvisor(int pos, Advisor advisor) throws AopConfigException;

int indexOf(Advisor advisor);

boolean removeAdvisor(Advisor advisor) throws AopConfigException;

void removeAdvisor(int index) throws AopConfigException;

boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;

boolean isFrozen();

方法getAdvisors()将会返回一个包含了所有通知器的Advisor、拦截器或者其他的添加到工厂的通知类型. 如果 你添加了一个Advisor, 那么在返回的通知器中其索引对应的对象将会对应你所添加的. 如果你添加的是拦截器或者 其他的通知类型, Spring将会将其包装在一个拥有始终返回true的切入点的通知器中. 也就是说, 如果你添加了 一个MethodInterceptor, 那么通知器中在对应索引处将会是一个DefaultPointcutAdvisor来表示你的 MethodInterceptor以及一个匹配所有类和方法的切入点.

addAdvisor()方法可以用来添加任何的Advisor. 通常用于持有切入点和通知器的advisor是通用的 DefaultPointcutAdvisor, 它可以用于任何的通知器或者切入点(但不用于介绍).

默认情况下, 添加或者删除advisor或者拦截器在代理创建好之后是可行的. 唯一的限制是有可能当你添加或者移除 了一个介绍型advisor后, 从工厂类创建的已经存在了的代理不会表现出接口的修改来. (你可以通过从工厂重新获取 一个新的代理来解决这个问题.)

下面是一个转换面向切面代理(AOP proxy)为一个Advised接口并且检查和操作他的通知器的简单例子:

Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");

// 添加通知器例如不带切入点的拦截器
// 将会匹配所有的代理方法
// 可用于拦截器、在返回或者抛出异常之前、之后
advised.addAdvice(new DebugInterceptor());

// 使用切入点添加选择的通知器
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
[Note]Note

对业务对象修改通知器是否是明智的(没有双关语意), 即使那是没有疑问的合法使用, 这是富有争议的. 然而, 它在 开发中是很有用的: 例如, 在测试中. 我有时候发觉它是很有用的用来向拦截器的表单里面添加代码或者其他通知器, 使得进入我想测试的方法调用. (例如, 通知器可以进入一个方法创建好的事务中: 例如, 在标记事务回滚之前, 运行 一个SQL来检查数据库已经被正确的更新了.)

取决于你是怎样创建的代理, 你通常都可以设置一个frozen标志位, 这样Advised isFrozen()方法都将 返回true, 并且任何试图通过添加或者移除修改通知器都将产生AopConfigException. 一个advised对象的封冻 状态的能力在某些情况下是很有用的, 例如, 阻止调用代码来移除安全拦截器. 它也可以被用于Spring 1.1中来允许 积极的优化如果运行时的通知器修改是已知的不需要的.

10.9 Using the "auto-proxy" facility

So far we’ve considered explicit creation of AOP proxies using a ProxyFactoryBean or similar factory bean.

Spring also allows us to use "auto-proxy" bean definitions, which can automatically proxy selected bean definitions. This is built on Spring "bean post processor" infrastructure, which enables modification of any bean definition as the container loads.

In this model, you set up some special bean definitions in your XML bean definition file to configure the auto proxy infrastructure. This allows you just to declare the targets eligible for auto-proxying: you don’t need to use ProxyFactoryBean.

There are two ways to do this:

  • Using an auto-proxy creator that refers to specific beans in the current context.
  • A special case of auto-proxy creation that deserves to be considered separately; auto-proxy creation driven by source-level metadata attributes.

10.9.1 Autoproxy bean definitions

The org.springframework.aop.framework.autoproxy package provides the following standard auto-proxy creators.

BeanNameAutoProxyCreator

The BeanNameAutoProxyCreator class is a BeanPostProcessor that automatically creates AOP proxies for beans with names matching literal values or wildcards.

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="jdk*,onlyJdk"/>
    <property name="interceptorNames">
        <list>
            <value>myInterceptor</value>
        </list>
    </property>
</bean>

As with ProxyFactoryBean, there is an interceptorNames property rather than a list of interceptors, to allow correct behavior for prototype advisors. Named "interceptors" can be advisors or any advice type.

As with auto proxying in general, the main point of using BeanNameAutoProxyCreator is to apply the same configuration consistently to multiple objects, with minimal volume of configuration. It is a popular choice for applying declarative transactions to multiple objects.

Bean definitions whose names match, such as "jdkMyBean" and "onlyJdk" in the above example, are plain old bean definitions with the target class. An AOP proxy will be created automatically by the BeanNameAutoProxyCreator. The same advice will be applied to all matching beans. Note that if advisors are used (rather than the interceptor in the above example), the pointcuts may apply differently to different beans.

DefaultAdvisorAutoProxyCreator

A more general and extremely powerful auto proxy creator is DefaultAdvisorAutoProxyCreator. This will automagically apply eligible advisors in the current context, without the need to include specific bean names in the auto-proxy advisor’s bean definition. It offers the same merit of consistent configuration and avoidance of duplication as BeanNameAutoProxyCreator.

Using this mechanism involves:

  • Specifying a DefaultAdvisorAutoProxyCreator bean definition.
  • Specifying any number of Advisors in the same or related contexts. Note that these must be Advisors, not just interceptors or other advices. This is necessary because there must be a pointcut to evaluate, to check the eligibility of each advice to candidate bean definitions.

The DefaultAdvisorAutoProxyCreator will automatically evaluate the pointcut contained in each advisor, to see what (if any) advice it should apply to each business object (such as "businessObject1" and "businessObject2" in the example).

This means that any number of advisors can be applied automatically to each business object. If no pointcut in any of the advisors matches any method in a business object, the object will not be proxied. As bean definitions are added for new business objects, they will automatically be proxied if necessary.

Autoproxying in general has the advantage of making it impossible for callers or dependencies to obtain an un-advised object. Calling getBean("businessObject1") on this ApplicationContext will return an AOP proxy, not the target business object. (The "inner bean" idiom shown earlier also offers this benefit.)

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>

<bean id="businessObject1" class="com.mycompany.BusinessObject1">
    <!-- Properties omitted -->
</bean>

<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>

The DefaultAdvisorAutoProxyCreator is very useful if you want to apply the same advice consistently to many business objects. Once the infrastructure definitions are in place, you can simply add new business objects without including specific proxy configuration. You can also drop in additional aspects very easily - for example, tracing or performance monitoring aspects - with minimal change to configuration.

The DefaultAdvisorAutoProxyCreator offers support for filtering (using a naming convention so that only certain advisors are evaluated, allowing use of multiple, differently configured, AdvisorAutoProxyCreators in the same factory) and ordering. Advisors can implement the org.springframework.core.Ordered interface to ensure correct ordering if this is an issue. The TransactionAttributeSourceAdvisor used in the above example has a configurable order value; the default setting is unordered.

AbstractAdvisorAutoProxyCreator

This is the superclass of DefaultAdvisorAutoProxyCreator. You can create your own auto-proxy creators by subclassing this class, in the unlikely event that advisor definitions offer insufficient customization to the behavior of the framework DefaultAdvisorAutoProxyCreator.

10.9.2 Using metadata-driven auto-proxying

A particularly important type of auto-proxying is driven by metadata. This produces a similar programming model to .NET ServicedComponents. Instead of defining metadata in XML descriptors, configuration for transaction management and other enterprise services is held in source-level attributes.

In this case, you use the DefaultAdvisorAutoProxyCreator, in combination with Advisors that understand metadata attributes. The metadata specifics are held in the pointcut part of the candidate advisors, rather than in the auto-proxy creation class itself.

This is really a special case of the DefaultAdvisorAutoProxyCreator, but deserves consideration on its own. (The metadata-aware code is in the pointcuts contained in the advisors, not the AOP framework itself.)

The /attributes directory of the JPetStore sample application shows the use of attribute-driven auto-proxying. In this case, there’s no need to use the TransactionProxyFactoryBean. Simply defining transactional attributes on business objects is sufficient, because of the use of metadata-aware pointcuts. The bean definitions include the following code, in /WEB-INF/declarativeServices.xml. Note that this is generic, and can be used outside the JPetStore:

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="transactionInterceptor"
        class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributeSource">
        <bean class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource">
            <property name="attributes" ref="attributes"/>
        </bean>
    </property>
</bean>

<bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/>

The DefaultAdvisorAutoProxyCreator bean definition (the name is not significant, hence it can even be omitted) will pick up all eligible pointcuts in the current application context. In this case, the "transactionAdvisor" bean definition, of type TransactionAttributeSourceAdvisor, will apply to classes or methods carrying a transaction attribute. The TransactionAttributeSourceAdvisor depends on a TransactionInterceptor, via constructor dependency. The example resolves this via autowiring. The AttributesTransactionAttributeSource depends on an implementation of the org.springframework.metadata.Attributes interface. In this fragment, the "attributes" bean satisfies this, using the Jakarta Commons Attributes API to obtain attribute information. (The application code must have been compiled using the Commons Attributes compilation task.)

The /annotation directory of the JPetStore sample application contains an analogous example for auto-proxying driven by JDK 1.5+ annotations. The following configuration enables automatic detection of Spring’s Transactional annotation, leading to implicit proxies for beans containing that annotation:

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="transactionInterceptor"
        class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributeSource">
        <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
    </property>
</bean>

The TransactionInterceptor defined here depends on a PlatformTransactionManager definition, which is not included in this generic file (although it could be) because it will be specific to the application’s transaction requirements (typically JTA, as in this example, or Hibernate, JDO or JDBC):

<bean id="transactionManager"
        class="org.springframework.transaction.jta.JtaTransactionManager"/>
[Tip]Tip

If you require only declarative transaction management, using these generic XML definitions will result in Spring automatically proxying all classes or methods with transaction attributes. You won’t need to work directly with AOP, and the programming model is similar to that of .NET ServicedComponents.

This mechanism is extensible. It’s possible to do auto-proxying based on custom attributes. You need to:

  • Define your custom attribute.
  • Specify an Advisor with the necessary advice, including a pointcut that is triggered by the presence of the custom attribute on a class or method. You may be able to use an existing advice, merely implementing a static pointcut that picks up the custom attribute.

It’s possible for such advisors to be unique to each advised class (for example, mixins): they simply need to be defined as prototype, rather than singleton, bean definitions. For example, the LockMixin introduction interceptor from the Spring test suite, shown above, could be used in conjunction with a generic DefaultIntroductionAdvisor:

<bean id="lockMixin" class="test.mixin.LockMixin" scope="prototype"/>

<bean id="lockableAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor"
        scope="prototype">
    <constructor-arg ref="lockMixin"/>
</bean>

Note that both lockMixin and lockableAdvisor are defined as prototypes.

10.10 Using TargetSources

Spring offers the concept of a TargetSource, expressed in the org.springframework.aop.TargetSource interface. This interface is responsible for returning the "target object" implementing the join point. The TargetSource implementation is asked for a target instance each time the AOP proxy handles a method invocation.

Developers using Spring AOP don’t normally need to work directly with TargetSources, but this provides a powerful means of supporting pooling, hot swappable and other sophisticated targets. For example, a pooling TargetSource can return a different target instance for each invocation, using a pool to manage instances.

If you do not specify a TargetSource, a default implementation is used that wraps a local object. The same target is returned for each invocation (as you would expect).

Let’s look at the standard target sources provided with Spring, and how you can use them.

[Tip]Tip

When using a custom target source, your target will usually need to be a prototype rather than a singleton bean definition. This allows Spring to create a new target instance when required.

10.10.1 Hot swappable target sources

The org.springframework.aop.target.HotSwappableTargetSource exists to allow the target of an AOP proxy to be switched while allowing callers to keep their references to it.

Changing the target source’s target takes effect immediately. The HotSwappableTargetSource is threadsafe.

You can change the target via the swap() method on HotSwappableTargetSource as follows:

HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);

The XML definitions required look as follows:

<bean id="initialTarget" class="mycompany.OldTarget"/>

<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
    <constructor-arg ref="initialTarget"/>
</bean>

<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="swapper"/>
</bean>

The above swap() call changes the target of the swappable bean. Clients who hold a reference to that bean will be unaware of the change, but will immediately start hitting the new target.

Although this example doesn’t add any advice - and it’s not necessary to add advice to use a TargetSource - of course any TargetSource can be used in conjunction with arbitrary advice.

10.10.2 Pooling target sources

Using a pooling target source provides a similar programming model to stateless session EJBs, in which a pool of identical instances is maintained, with method invocations going to free objects in the pool.

A crucial difference between Spring pooling and SLSB pooling is that Spring pooling can be applied to any POJO. As with Spring in general, this service can be applied in a non-invasive way.

Spring provides out-of-the-box support for Jakarta Commons Pool 1.3, which provides a fairly efficient pooling implementation. You’ll need the commons-pool Jar on your application’s classpath to use this feature. It’s also possible to subclass org.springframework.aop.target.AbstractPoolingTargetSource to support any other pooling API.

Sample configuration is shown below:

<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
        scope="prototype">
    ... properties omitted
</bean>

<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
    <property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="poolTargetSource"/>
    <property name="interceptorNames" value="myInterceptor"/>
</bean>

Note that the target object - "businessObjectTarget" in the example - must be a prototype. This allows the PoolingTargetSource implementation to create new instances of the target to grow the pool as necessary. See the javadocs of AbstractPoolingTargetSource and the concrete subclass you wish to use for information about its properties: "maxSize" is the most basic, and always guaranteed to be present.

In this case, "myInterceptor" is the name of an interceptor that would need to be defined in the same IoC context. However, it isn’t necessary to specify interceptors to use pooling. If you want only pooling, and no other advice, don’t set the interceptorNames property at all.

It’s possible to configure Spring so as to be able to cast any pooled object to the org.springframework.aop.target.PoolingConfig interface, which exposes information about the configuration and current size of the pool through an introduction. You’ll need to define an advisor like this:

<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="poolTargetSource"/>
    <property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

This advisor is obtained by calling a convenience method on the AbstractPoolingTargetSource class, hence the use of MethodInvokingFactoryBean. This advisor’s name ("poolConfigAdvisor" here) must be in the list of interceptors names in the ProxyFactoryBean exposing the pooled object.

The cast will look as follows:

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
[Note]Note

Pooling stateless service objects is not usually necessary. We don’t believe it should be the default choice, as most stateless objects are naturally thread safe, and instance pooling is problematic if resources are cached.

Simpler pooling is available using auto-proxying. It’s possible to set the TargetSources used by any auto-proxy creator.

10.10.3 Prototype target sources

Setting up a "prototype" target source is similar to a pooling TargetSource. In this case, a new instance of the target will be created on every method invocation. Although the cost of creating a new object isn’t high in a modern JVM, the cost of wiring up the new object (satisfying its IoC dependencies) may be more expensive. Thus you shouldn’t use this approach without very good reason.

To do this, you could modify the poolTargetSource definition shown above as follows. (I’ve also changed the name, for clarity.)

<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
    <property name="targetBeanName" ref="businessObjectTarget"/>
</bean>

There’s only one property: the name of the target bean. Inheritance is used in the TargetSource implementations to ensure consistent naming. As with the pooling target source, the target bean must be a prototype bean definition.

10.10.4 ThreadLocal target sources

ThreadLocal target sources are useful if you need an object to be created for each incoming request (per thread that is). The concept of a ThreadLocal provide a JDK-wide facility to transparently store resource alongside a thread. Setting up a ThreadLocalTargetSource is pretty much the same as was explained for the other types of target source:

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
</bean>
[Note]Note

ThreadLocals come with serious issues (potentially resulting in memory leaks) when incorrectly using them in a multi-threaded and multi-classloader environments. One should always consider wrapping a threadlocal in some other class and never directly use the ThreadLocal itself (except of course in the wrapper class). Also, one should always remember to correctly set and unset (where the latter simply involved a call to ThreadLocal.set(null)) the resource local to the thread. Unsetting should be done in any case since not unsetting it might result in problematic behavior. Spring’s ThreadLocal support does this for you and should always be considered in favor of using ThreadLocals without other proper handling code.

10.11 定义新的通知类型

Spring AOP 是设计为可扩展的。虽然拦截实现策略目前在内部使用,但是它可以支持除开箱即用的环绕通知、前置通知、异常通知和后置通知外的任意通知类型。

org.springframework.aop.framework.adapter包是一个SPI包,它支持允许在不改变核心框架的情况下添加新的通知类型。定义Advice类型的唯一限制是必须继承org.aopalliance.aop.Advice标记接口。

请参考org.springframework.aop.framework.adapter文档了解更多信息。

10.12 更多资源

请参考Spring示例应用中更多关于Spring AOP的例子:

  • JPetStore的默认配置说明了如何使用TransactionProxyFactoryBean用于声明式事务管理。
  • JPetStore的/attributes目录说明了如何使用属性驱动的声明式事务管理。 == 测试

10.13 Spring 测试框架介绍

测试是企业级软件开发中不可或缺的一部分。 本章聚焦于Ioc原则在单元测试中的 附加价值以及Spring框架为集成测试带来的益处。 (企业中对测试的完整处理超出了本参考手册的范围。)

10.14 单元测试

相比在传统Java EE下开发,依赖注入使你的代码更少的依赖容器。通过简单的new操作,构成你应用 的POJOs对象即可在JUnit或TestNG下测试。 即使是没有Spring或者其他容器,你可以使用 模拟对象 (联合其他有价值的测试技术) 独立测试你的代码。 如果你遵循了Spring的架构建议, 清晰的分层和组件化的代码将会促进 单元测试的简化。例如,当运行单元测试的时候,你可以通过存根代码、模拟DAO或者是资源库接口测试服务层对象而不用访问 持久层数据。

即使没有运行时基础设施设置,真正的单元测试典型的要求是快速地运行。强调真正的单元测试是你开发方法论 中的一部分将会提高生产率。对你的Ioc-based应用,你可能不需要测试章节来帮你写出高效的单元测试。 尽管如此,Spring框架为单元测试场景提供了下列模拟对象和测试支持类。

10.14.1 模拟对象

Environment

org.springframework.mock.env 包包含了EnvironmentPropertySource(查看 Section 5.13.1, “Bean定义配置文件”Section 5.13.3, “PropertySource的抽象”)抽象的模拟实现。 MockEnvironmentMockPropertySourceout-of-container代码开发的测试是非常有用的,因为这些代码往往依赖于特定环境的属性。

JNDI

org.springframework.mock.jndi 包包含了JNDI SPI的模拟实现, 你可以使用它为测试套件或者独立的应用设置一个 简单的JNDI环境。比如,在测试代码和在Java EE容器中一样对JDBC DataSources 绑定相同名字的JNDI,这样,你就 可以在测试场景中复用应用配置而不用修改。

Servlet API

org.springframework.mock.web 包包含了Servlet API模拟对象的综合设置,定位是在Spring MVC框架中对Web上下文 和控制器进行测试。这些模拟对象的使用比类似 EasyMock或现存的Servlet API模拟对象如 MockObjects动态的模拟对象更方便。

Portlet API

org.springframework.mock.web.portlet 包包含了 Portlet API 模拟对象集和,定位ie是Spring的Portlet MVC 框架的使用。

10.14.2 单元测试支持类

通用组件

org.springframework.test.util 包包含 ReflectionTestUtils类, 这个类提供了一套基于反射的工具方法。 当测试应用代码时开发人员在单元或者是集成测试场景中使用这些方法设置一个非public的子段或调用一个非public的设值方法 例如:

  • 和领域实体对象中属性的public`setter方法不同,类似JPA和Hibernate的ORM框架放任对`privateprotected子段 的访问。
  • Spring对注解的支持,例如@Autowired, @Inject, and @Resource, 它为 private or protected 子段提供了依赖注入, setter方法和configuration方法。

Spring MVC

org.springframework.test.web 包包含ModelAndViewAssert对象, 使用它你可以组合JUnit,TestNG或者任意其他的 单元测试框架为你的Spring MVC`ModelAndView`对象进行单元测试。

[Tip]Spring MVC Controllers 单元测试

为了测试你的 Spring MVC Controllers, 可以使用 ModelAndViewAssert 组合MockHttpServletRequest, MockHttpSession, 或者其他来自 org.springframework.mock.web 包的对象。

注意: 目前,在 Spring 4.0中, 包org.springframework.mock.web 中的模拟对象是基于Servlet 3.0 API 。

10.15 集成测试

10.15.1 概览

不需要发布(程序)到你的应用服务器或者连接到其他企业设施的情况下能够执行一些集成测试是非常重要的。 这能使你测试下述情况:

  • Spring Ioc容器上下文的正确写入
  • 使用JDBC或者ORM工具进行数据访问。这包括SQL语句、Hibernate查询以及JPA实体映射的正确性,类似等等。

spring-test模块中,Spring框架为集成测试提供了第一等级的支持。实际的Jar包的名称包括发布版本的 名称,也可能包括长包名org.springframework.test的形式。这依赖你获取的来源(参阅依赖管理章节解释 )。这个库包括org.springframework.test包,它为在Sring容器中进行集成测试提供 一些有价值的类。这些测试不依赖应用服务器或者其他发布环境。这些测试比单元测试要慢,但是要比等价的分析测试 或者是依赖应用服务器发布的远程测试要快。

在Spring 2.5及以后的版本中已经提供了注解驱动Spring 测试上下文框架的 单元测试和集成测试。由于Spring 测试上下文框架对实际使用的测试框架是不可知的,所以允许使用各种测试设施, 如JUnit,TestNG等等。

10.15.2 集成测试的目标

Srping的集成测试支持有如下的主要目标:

后续的章节描述每一个目标并提供实现和配置详情的链接。

上下文管理和缓存

Spring TestContext 框架对ApplicationContexts 和WebApplicationContexts以及这些 上下文的缓存提供了一致的加载。对加载的上下文进行缓存是重要的,因为启动时间可能会成为一个问题,—— 并非 因为Spring本身的问题,而是因为被Spring容器管理的对象需要花时间进行实例化。例如,一个拥有50到100个 Hibernate映射文件的项目可能需要用掉10到20妙来加载这些文件,在运行每一个测试夹具之前都会产生这笔 开销,这将直接导致开发人员的生产率下降。

这些类有两种典型的声明方式,一种是在XML配置元数据中有一个 资源路径 的列表 ——通常是在类路径下, 或者是一个配置应用的 注解类 的列表。它们与在web.xml发布描述文件中声明的或者其他的发布配置 文件是相同或相似的。

默认情况下,一旦加载,配置的 ApplicationContext 对每一个测试都是复用的。因此 setup 的开销在每一个 测试套件中仅仅一次,后续的测试执行会更快。这种情况下指的 测试套件 意思是运行在同一个JVM中的所有的测试—— 例如,对指定的项目或模块,无论是Ant、Maven或是Gradle构建的所有的测试。一个极不可能发生的情况是测试崩溃 导致应用上下文的重新加载——例如,修改Bean定义或者应用对象的状态,这时在执行测试之前TestContext框架可以 被配置为重新加载配置和重新构建应用上下文。

参阅TestContext 框架中the section called “上下文管理”the section called “Context caching”

测试夹具的依赖注入

当TestContext 框架加载应用上下文的时候,可选地,你可以使用依赖注入的方式配置测试类的实例。它为使用预先配置 的Bean设置测试夹具提供了一个便利的机制。一个很大的好处是你可以跨越各种测试场景复用应用上下文。(例如,配置 Spring管理的对象图,事务代理, DataSources,等),因此可以避免为独立的测试用例重复地设置复杂的测试 夹具。

作为一个示例,考虑如下场景:我们有一个类, HibernateTitleRepository 为领域实体 Title 实现了数据访问 逻辑。我们想写一个集成测试,测试如下情况:

  • Spring配置:与 HibernateTitleRepository bean配置相关所有东西的正确性 ?
  • Hibernate 映射文件配置:映射的正确性,lazy-loading设置正确性?
  • HibernateTitleRepository对象的逻辑:配置的这个类的实例是否达到了预期?

参阅在TestContext framework中使用测试夹具的依赖注入。

事务管理

测试中一个通常的问题是访问真实的数据库对持久化存储状态的影响。甚至你使用开发数据库,状态的变化可能影响将来的测试。 另外,许多操作,如插入或修改持久化数据——不能在事务外执行(或验证)。

TestContext 框架定位了这个问题。默认情况下,框架将对每一个测试创建并回滚一个事务。你可以假设事务的存在, 而简单地写代码。在你的测试中,如果调用了一个事务性的代理对象,根据对它们的事务语义配置,它们将会行为正确地执行。 另外,在测试中,当运行在受管事务中时,如果一个方法删除了选定表的内容,默认情况下,事务将会回滚,数据库将会返回到 执行测试前的状态。在测试中事务的支持是通过在应用上下文中定一个bean PlatformTransactionManager

如果你想提交一个事务——极少出现但偶有发生,当你要一个特定的测试来迁移或修改数据库——TestContext框架可以通过注解 @TransactionConfiguration@Rollback提示进行事务提交而不是事务回滚。

参阅使用TestContext 框架进行事务管理。

集成测试支持类

Spring TestContext 框架为简化集成测试的编写提供了几个 abstract 支持类。这些基础类提供了定义良好的钩子以及 便利的实例变量和方法,以便你能够访问:

  • ApplicationContext对象,为了显式bean的查找以及整个上下文状态的测试。
  • JdbcTemplate对象,为了执行SQL语句查询数据库。这些查询,可以用来确认数据库相关的代码执行 前后 数据库状态。 Spring 还保证这些代码与应用代码运行在相同的事务方位内。当联合使用ORM工具使用时,确保避免false positives 问题。

另外,你可能想创建自定义的,应用范围的超类。这些超类拥有实例特定于你的项目的变量和方法。

请参阅TestContext 框架支持类。

10.15.3 JDBC 测试支持

org.springframework.test.jdbc 包包含了 JdbcTestUtils工具类,它是个JDBC相关的功能工具集,主要用来 应对简化标准化数据库的测试场景。 JdbcTestUtils 工具类提供了下列静态工具方法。

  • countRowsInTable(..): 对指定的表进行行计数
  • countRowsInTableWhere(..): 使用提供的WHERE 条件对指定的表进行行计数
  • deleteFromTables(..): 对指定的表删除所有的行
  • deleteFromTableWhere(..): 使用提供的WHERE 条件对指定的表删除记录
  • dropTables(..): 删除指定的表

注意,AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests中提供 的便利方法是委托给了前述 JdbcTestUtils 工具类中的方法。

spring-jdbc 模块提供了对配置和启动一个嵌入式数据库的支持,它可以用来在集成测试用与数据库进行交互。详情 请参阅Section 13.8, “Embedded database support”Section 13.8.8, “Testing data access logic with an embedded database”相关章节。

10.15.4 注解

Spring 测试相关注解

Spring框架提供了一组Spring特定的注解,你可以在测试中联合TestContext框架进行单元或者是集成测试。 想要获得进一步的信息,包括默认值、属性别名等等,请参阅相关的javadocs。

  • @ContextConfiguration

    定义类级别的元数据,用来决定如何为集成测试加载和配置一个 ApplicationContext@ContextConfiguration 用来声明应用上下文资源的 locations 或者加了注解的被用来加载上下文的 classes

    资源路径典型的是位于类路径中的XML配置文件;而注解的类典型的是加了注解 @Configuration 的类。尽管如此 资源路径也可以是执行文件系统中的文件,注解的类可以是组件类等等。

    @ContextConfiguration("/test-config.xml")
    public class XmlApplicationContextTests {
        // class body...
    }
    @ContextConfiguration(classes = TestConfig.class)
    public class ConfigClassApplicationContextTests {
        // class body...
    }

    对声明资源类路径或注解类而言,作为一个可选的或者是附加的方式,@ContextConfiguration可以被用来指定 用于自定义的 ApplicationContextInitializer 接口的实现类。

    @ContextConfiguration(initializers = CustomContextIntializer.class)
    public class ContextInitializerTests {
        // class body...
    }

    @ContextConfiguration也可以可选地被用来声明 ContextLoader 策略。注意,尽管如此,典型性情况下 你不必显式配置加载器,因为默认的加载其既支持资源 locations 或注解 classes 也支持 initializers 初始器方式。

    @ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class)
    public class CustomLoaderXmlApplicationContextTests {
        // class body...
    }
    [Note]Note

    @ContextConfiguration 默认地提供了对资源路径或配置类以及由超类声明的上下文初始化器的 继承 机制。

    参阅 the section called “上下文管理”@ContextConfiguration javadocs 进一步了解详情。

  • @WebAppConfiguration

    为集成测试加载的使用类级注解声明的 ApplicationContext 对象应该是一个 WebApplicationContext 对象。在测试类上存在@WebAppConfiguration 基本价值是在测试中确保一个 WebApplicationContext 对象被加载,该对象使用默认值 "file:src/main/webapp" 作为web应用(例如,资源基本路径)的根 路径。资源基本路径被用来在 WebApplicationContext 测试中创建一个模拟 ServletContext 对象的 MockServletContext 模拟对象来提供服务。

    @ContextConfiguration
    @WebAppConfiguration
    public class WebAppTests {
        // class body...
    }

    要覆盖默认值,可以通过 固有 value 属性指定另外一个基础资源路径。资源前缀支持 classpath:file: 两种方式。 如果没有指定资源前缀,则默认为文件系统资源。

    @ContextConfiguration
    @WebAppConfiguration("classpath:test-web-resources")
    public class WebAppTests {
        // class body...
    }

    注意: @WebAppConfiguration 必须和联合 @ContextConfiguration 一起使用,同时需要在单个 测试类或者是在一个测试类的层次内。 参考 @WebAppConfiguration javadocs 了解详情。

  • @ContextHierarchy

    一个类级别的注解,用来为集成测试定义 ApplicationContexts 的层次。 @ContextHierarchy 声明的 时候应该包含一个或多个 @ContextConfiguration 实例的列表。在上下文层次中,每一个实例都定义了一个级别。 下面的示例演示了单个测试类中 @ContextHierarchy 的使用;尽管如此, @ContextHierarchy也可以被用在 一个测试类层次中。

    @ContextHierarchy({
        @ContextConfiguration("/parent-config.xml"),
        @ContextConfiguration("/child-config.xml")
    })
    public class ContextHierarchyTests {
        // class body...
    }
    @WebAppConfiguration
    @ContextHierarchy({
        @ContextConfiguration(classes = AppConfig.class),
        @ContextConfiguration(classes = WebConfig.class)
    })
    public class WebIntegrationTests {
        // class body...
    }

    如果你需要在一个测试类层次中合并或覆盖指定的上下文层次的级别,在每一个类层次的相关级别中,你必须通过为 @ContextConfigurationname 属性提供相同的值显式命名那个级别。请参考 the section called “Context hierarchies”@ContextHierarchy javadocs 了解进一步的例子。

  • @ActiveProfiles

    一个类级别的注解,用来声明当为测试类加载一个 ApplicationContext时,哪些 bean 定义配置 应该 被激活。

    @ContextConfiguration
    @ActiveProfiles("dev")
    public class DeveloperTests {
        // class body...
    }
    @ContextConfiguration
    @ActiveProfiles({"dev", "integration"})
    public class DeveloperIntegrationTests {
        // class body...
    }
    [Note]Note

    默认情况下,@ActiveProfiles 提供了对 继承 超类声明的活bean定义配置的支持。也可以使用编程的 方式来解析已激活bean定义配置,只需一个ActiveProfilesResolver 的自定义实现并且通过 @ActiveProfilesresolver 属性进行注册。

    参考 the section called “使用环境概要进行上下文配置”@ActiveProfiles javadocs了解示例和 进一步的详情。

  • @TestPropertySource

    一个类级别的注解,用来配置属性文件和内联属性的位置,在集成测试的ApplicationContext 对象加载的时候, 这些属性要被加到 PropertySourcesEnvironment的设置中。

    测试属性源比操作系统环境加载的属性、Java系统属性、应用通过 @PropertySource 或编程方式声明的属性有更高 优先级。因此,测试属性源可以被用来可选地覆盖系统或者应用定义的属性源。此外,内联属性比在资源路径中加载的属性 有更高的优先级。

    下述示例演示了如何通过类路径声明一个属性文件。

    @ContextConfiguration
    @TestPropertySource("/test.properties")
    public class MyIntegrationTests {
        // class body...
    }

    下述示例演示了如何声明 内联 的属性。

    @ContextConfiguration
    @TestPropertySource(properties = { "timezone = GMT", "port: 4242" })
    public class MyIntegrationTests {
        // class body...
    }
  • @DirtiesContext

    指示Spring的 ApplicationContext 在测试执行期间(例如: 以某种方式被修改或遭到破坏—— 例如单例的 状态被改变)已经 变脏 ,不管测试是否通过都应该关闭。 一旦应用上下文被标记为 dirty ,它应该从 框架的缓存中清除并关闭。 因此, 接下来Spring容器将会为后续的依赖相同配置元数据的测试进行重建。

    @DirtiesContext 在同一个测试类中可以同时用作类级别的和方法级别的注解。在这种场景下,整个类已经所有加注了 这个注解的方法之后, ApplicationContext 都被标记为 dirty 。 如果 ClassMode 设置为 AFTER_EACH_TEST_METHOD ,在类中的每一个测试方法之后,上下文将被标记为 dirty

    下述的示例针对各种配置场景解释了上下文何时变脏:

    • 当前的测试类之后,类上声明,类模式设置为 AFTER_CLASS (例如:默认的类模式)。

      @DirtiesContext
      public class ContextDirtyingTests {
          // 一些导致spring容器变脏的测试
      }
    • 当前测试类的每一个测试方法之后,类上声明,类模式设置为 AFTER_EACH_TEST_METHOD

      @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
      public class ContextDirtyingTests {
          // 一些导致spring容器变脏的测试
      }
    • 当前的测试之后,在方法上声明。

      @DirtiesContext
      @Test
      public void testProcessWhichDirtiesAppCtx() {
          // 一些导致spring容器变脏的测试
      }

      如果一个测试的上下文通过 @ContextHierarchy 被配置为上下文层次中的一部分, 可以使用 @DirtiesContext 注解, 通过 hierarchyMode 标志来控制上下文缓存的清理。 默认情况下,将使用一个 EXHAUSTIVE 的算法清理 当前测试中通用的上下文缓存,不仅包括当前级别而且包括所有其他的共享一个祖先的上下文层次。所有的住留在通用祖先上下 文的子层次中的 ApplicationContexts 将从缓存中清除并关闭。 如果 EXHAUSTIVE 的算法在特定的用例中清理 过度,可以选择更简单的 CURRENT_LEVEL 算法来替换,参考如下。

      @ContextHierarchy({
          @ContextConfiguration("/parent-config.xml"),
          @ContextConfiguration("/child-config.xml")
      })
      public class BaseTests {
          // class body...
      }
      
      public class ExtendedTests extends BaseTests {
      
          @Test
          @DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL)
          public void test() {
              // 一些导致子上下文变脏的逻辑
          }
      }

    进一步了解 EXHAUSTIVECURRENT_LEVEL 算法的详情,清参考 DirtiesContext.HierarchyMode javadocs。

  • @TestExecutionListeners

    定义类级别的元数据配置, 在 TestContextManager 中注册 TestExecutionListeners 。 典型情况下, @TestExecutionListeners@ContextConfiguration 联合使用。

    @ContextConfiguration
    @TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class})
    public class CustomTestExecutionListenerTests {
        // class body...
    }

    @TestExecutionListeners 默认情况下支持监听器 inherited 。 参考javadocs查看示例和进一步的详情。

  • @TransactionConfiguration

    为事务性测试配置类级别的元数据。具体来说,如果在在测试中有多个 PlatformTransactionManager 类型的bean, 并且期望的 PlatformTransactionManager 的bean名不是 "transactionManager", PlatformTransactionManager 的bean名应该被显式地指定来驱动事务。另外,你可以改变 defaultRollback 标志为 false。 典型情况下, @TransactionConfiguration@ContextConfiguration 被联合使用。

    @ContextConfiguration
    @TransactionConfiguration(transactionManager = "txMgr", defaultRollback = false)
    public class CustomConfiguredTransactionalTests {
        // class body...
    }
    [Note]Note

    如果默认的传统对你的测试配置够用的话,你可以避免 与 @TransactionConfiguration 一起使用。换句话说 如果你只有一个事务管理器——或者你有多个事务管理器,但是事务管理器的名字都是 "transactionManager" 或者通过 TransactionManagementConfigurer 进行了指定 —— 并且你想事务自动回滚,那么你无需使用@TransactionConfiguration 注解你的测试类。

  • @Rollback

    指示注解的测试方法执行完成之后事务是否应该 rolled back。 如果设置为 true ,事务将会回滚, 否则,事务将会提交。 使用 @Rollback 能够覆盖在类级别配置的默认的回滚标志。

    @Rollback(false)
    @Test
    public void testProcessWithoutRollback() {
        // ...
    }
  • @BeforeTransaction

    指示一个 public void 方法在事务启动之前执行,这个方法是通过 @Transactional 注解配置事务的。

    @BeforeTransaction
    public void beforeTransaction() {
        // 在事务启动之前执行的逻辑
    }
  • @AfterTransaction

    指示一个 public void 方法在事务执行结束之后执行,这个方法是通过 @Transactional 注解配置事务的。

    @AfterTransaction
    public void afterTransaction() {
        // 事务执行结束后的逻辑
    }
  • @Sql

    在集成测试中用来对给定的数据库配置要执行的SQL脚本,这个注解用在类级别或者是方法级别。

    @Test
    @Sql({"/test-schema.sql", "/test-user-data.sql"})
    public void userTest {
        // 执行依赖的测试数据库和测试数据的代码
    }

    参阅 the section called “Executing SQL scripts declaratively with @Sql 查看详情。

  • @SqlConfig

    定义用来决定如何解析和执行通过 @Sql 注解配置的SQL脚本的元数据。

    @Test
    @Sql(
        scripts = "/test-user-data.sql",
        config = @SqlConfig(commentPrefix = "`", separator = "@@")
    )
    public void userTest {
        // 依赖测试数据的代码
    }
  • @SqlGroup

    这是一个容器注解,用来聚合若干个 @Sql 注解。 可以自然地用来声明若干个内置的 @Sql 注解。 也可以用来结合 Java8 可重复注解的支持,用来在类或者是方法上声明多次 @Sql 注解, 这种情况,将隐式生成这个容器注解。

    @Test
    @SqlGroup({
        @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
        @Sql("/test-user-data.sql")
    )}
    public void userTest {
        // 使用测试数据库和测试数据的执行代码
    }

标准注解支持

下述的注解对所有的Spring TestContext框架配置支持标准的语义。这些注解并非是在测试中专用的,而是可以用在 Spring框架的任何地方。

  • @Autowired
  • @Qualifier
  • @Resource (javax.annotation) if JSR-250 is present
  • @Inject (javax.inject) if JSR-330 is present
  • @Named (javax.inject) if JSR-330 is present
  • @PersistenceContext (javax.persistence) if JPA is present
  • @PersistenceUnit (javax.persistence) if JPA is present
  • @Required
  • @Transactional
[Note]JSR-250 声明周期注解

在Spring TestContext框架中, @PostConstruct@PreDestroy 可以以标准的语义被用在任何配置 在 @ApplicationContext 应用组件中; 然而,生命周期注解在实际的测试类中使用是受限的。

如果测试类中的一个方法增加了 @PostConstruct 注解,这个方法将会在测试框架(例如,加注了JUnit的 @Before 注解的方法)中的任何 before 方法之前被执行,并且它会被应用在测试类的每一个测试方法中。另一方面,如果一个 测试类中的方法加注了 @PreDestroy 注解, 这个方法将 绝对 不会执行。 因此,在测试类中建议使用测试框架 的生命周期回调而非 @PostConstruct@PreDestroy 注解。

Spring JUnit 测试注解

下述的注解 仅仅 支持联合 SpringJUnit4ClassRunner 或者是 JUnit 的支持类。

  • @IfProfileValue

    指示增加了该注解的测试对特定的测试环境可用。 如果配置的 ProfileValueSource 对提供的 name 返回 了匹配的 value, 这个测试即可用。 这个注解可以用在整个类上,也可以被用在独立的方法上。在类级别使用将覆盖 方法级别的配置。

    @IfProfileValue(name="java.vendor", value="Oracle Corporation")
    @Test
    public void testProcessWhichRunsOnlyOnOracleJvm() {
        // 一些仅应该在Oracle公司的JAVA JVM上运行的逻辑
    }

    可选地,你可以用一组 values (拥有 OR 语义)列表来配置 @IfProfileValue 注解,在JUnit环境中 以此来达到类似TestNG对 test group 的支持。 考虑如下的示例:

    @IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"})
    @Test
    public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
        // 一些仅能运行单元和集成测试组的逻辑
    }
  • @ProfileValueSourceConfiguration

    类级别的注解,用来指定搜索 profile valuesProfileValueSource 的类型,这些 profile values 来源于 @IfProfileValue 注解的配置。如果测试中没有声明 @ProfileValueSourceConfiguration 注解,默认 将使用 SystemProfileValueSource 注解。

    @ProfileValueSourceConfiguration(CustomProfileValueSource.class)
    public class CustomProfileValueSourceTests {
        // class body...
    }
  • @Timed

    指示一个注解的方法必须在指定的时间周期内(以毫秒为单位)完成执行。如果执行时间超过了指定的时间周期,测试将 失败。

    时间周期包括测试方法本身的执行,任何测试的重复(参阅 @Repeat 注解)执行,也包括任何测试夹具的 set up 或者 tear down 方法。

    @Timed(millis=1000)
    public void testProcessWithOneSecondTimeout() {
        // 一些执行时间不会超过1妙的代码逻辑
    }

    Spring的 @Timed 注解与JUnit的 @Test(timeout=...)支持有不同的语义。具体来说,归因于JUnit处理 测试执行超时(在一个独立的 Thread 中执行测试方法的时间)的方式, 如果测试花费太长的时间,那么 @Test(timeout=...) 注解会强制测试失败。 然而,Spring的 @Timed 注解不会强制测试失败,而是在失败之前 等待测试完成执行。

  • @Repeat

    指示重复执行增加了该注解的方法。执行的次数在该注解中指定。

    多次执行的方法包括测试方法本身, 测试夹具的 set up 方法和 tear down 方法。

    @Repeat(10)
    @Test
    public void testProcessRepeatedly() {
        // ...
    }

测试元注解的支持

在Spring 4.0中的测试套件中,使用类似 meta-annotations 测试相关的注解来创建 自定义的 组合的注解 并降低重复配置成为可能。

下述的每一个注解在联合 TestContext framework 使用时,都可以作为元注解。

  • @ContextConfiguration
  • @ContextHierarchy
  • @ActiveProfiles
  • @TestPropertySource
  • @DirtiesContext
  • @WebAppConfiguration
  • @TestExecutionListeners
  • @Transactional
  • @BeforeTransaction
  • @AfterTransaction
  • @TransactionConfiguration
  • @Rollback
  • @Sql
  • @SqlConfig
  • @SqlGroup
  • @Repeat
  • @Timed
  • @IfProfileValue
  • @ProfileValueSourceConfiguration

例如,如果我们发现正在重复配置基于JUnit的测试套件……

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }

我们可以通过引入一个自定义的 组合注解 来削减上述重复的配置,这个组合注解可以集中管理通用的测试配 置,如下所示:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTest { }

我们可以使用我们的自定义 @TransactionalDevTest 注解简化独立测试类的配置,如下所示:

@RunWith(SpringJUnit4ClassRunner.class)
@TransactionalDevTest
public class OrderRepositoryTests { }

@RunWith(SpringJUnit4ClassRunner.class)
@TransactionalDevTest
public class UserRepositoryTests { }

10.15.5 Spring TestContext Framework

Spring TestContext Framework (在 org.springframework.test.context 包中) 提供了通用的, 注解驱动的单元测试和集成测试支持,而不限定这些测试所使用的具体的测试套件。 Spring TestContext Framework 尤其重视 惯例配置 ,于是放置了大量的默认配置,你也可以基于注解的配置对默认值进行覆盖。

除了通用的测试设施,Spring TestContext framework 以 抽象 支持类的形式对JUnit和TestNG提供了直接的 支持。测试框架对JUnit提供了一个用户自定义的JUnit Runner ,这个运行器是一个被称为 POJO的测试类 , 它不需要继承一个特定的类层次。

接下来的章节将提供TestContext framework的概览。如果你仅仅对如何使用这个框架感兴趣,而对如何使用自定义的 监听器或加载器扩展它,那么,请直接参考配置(上下文管理依赖注入事务管理),支持类注解支持 章节。

核心概述

测试框架的核心由 TestContextTestContextManager 类以及 TestExecutionListener, ContextLoader, 和 SmartContextLoader 接口组成。 TestContextManager 实例在每一个基本测试单元(如JUnit中的测试方法执行时) 执行时被创建。TestContextManager对象用来管理持有当前测试上下文的TestContext实例,它也更新 TestContext类的 状态来指示测试进度,并且它将依赖注入,事务管理等功能委托给TestExecutionListeners类进行处理。ContextLoader 类负责对指定的测试类加载 ApplicationContext实例。请参考javadocs文件来了解进一步的信息和各种示例的实现。

  • TestContext: 测试执行时为测试实例封装测试上下文,对实际使用的测试框架透明,提供上下文管理和缓存支持 同时在必要时为加载 ApplicationContext实例委托于 ContextLoader类(或 SmartContextLoader类)。
  • TestContextManager: Spring TestContext Framework 的主要入口点,在良好定义的测试执行点 管理单个 TestContext和所有的已注册的 TestExecutionListeners类:

    • 在特定的测试框架中的任何 before class methods
    • 测试实例准备
    • 特定测试框架中的任何 before methods
    • 特定的测试框架中任的任何 after methods 之后
    • 特定的测试框架中的任何 after class methods 之后
  • TestExecutionListener: 为 TestContextManager 对象发布的测试执行事件定义一个 listener API ,查看 the section called “TestExecutionListener 配置”
  • ContextLoader: 在Spring 2.5中引入的策略接口,用来为Spring TestContext Framework管理的集成测试加载 一个 ApplicationContext 实例。

    为了提供对注解类、激活的bean定义配置、测试属性源、上下文层次和 WebApplicationContexts的支持, SmartContextLoader 是一个接口的替代实现。

  • SmartContextLoader: ContextLoader 接口的扩展在Spring 3.1中引入。

    SmartContextLoader SPI 在Spring 2.5中被引入,用来替代 ContextLoader SPI 。特定地, SmartContextLoader 可以选择用来处理资源 位置,注解的 ,或上下文 初始化器。进一步地,你可以在其加载的上下文中设置 SmartContextLoader类来激活bean定义配置和测试属性源。

    Spring 提供了如下的实现:

    • DelegatingSmartContextLoader: 两个默认的加载器中的一个,在内部委托给AnnotationConfigContextLoaderGenericXmlContextLoaderGenericGroovyXmlContextLoader 类,具体依赖于测试类的配置声明、默认位置 或默认的配置类。 Groovy的支持需要Groovy(相关Jar)在Java类路径上。
    • WebDelegatingSmartContextLoader: 两个默认的加载器中的一个,在内部委托给AnnotationConfigContextLoaderGenericXmlContextLoaderGenericGroovyXmlContextLoader 类,具体依赖于测试类的配置声明、默认位置 或默认的配置类。 如果在测试类上出现了 @WebAppConfiguration 注解,则仅仅使用 Web类加载器 ContextLoader 。 Groovy的支持需要Groovy(相关Jar)在Java类路径上。
    • AnnotationConfigContextLoader: 根据 注解的类 加载一个标准的应用上下文 ApplicationContext 实例。
    • AnnotationConfigWebContextLoader: 根据 注解的类 加载一个标准的应用上下文 WebApplicationContext 实例。
    • GenericGroovyXmlContextLoader: 根据 资源路径 加载一个标准的应用上下文 ApplicationContext 实例, 该资源路径可以是Groovy脚本或者是XML配置文件。
    • GenericGroovyXmlWebContextLoader: 根据 资源路径 加载一个标准的应用上下文 WebApplicationContext 实例, 该资源路径可以是Groovy脚本或者是XML配置文件。
    • GenericXmlContextLoader: 根据XML 资源路径 加载一个标准的应用上下文 ApplicationContext 实例。
    • GenericXmlWebContextLoader: 根据XML 资源路径 加载一个标准的应用上下文 WebApplicationContext 实例。
    • GenericPropertiesContextLoader: 根据 Java 属性文件加载一个标准的应用上下文 ApplicationContext 实例。

下述的章节将解释如何通过注解配置TestContext framework并且提供使用测试框架配置单元及集成测试的可以工作的示例。

TestExecutionListener 配置

Spring提供了如下测试执行监听器 TestExecutionListener 实现,它们将按如下顺序默认被注册。

  • ServletTestExecutionListener: 为WEB应用上下文 WebApplicationContext 对象配置Servlet API 模拟器
  • DependencyInjectionTestExecutionListener: 为测试实例提供依赖注入。
  • DirtiesContextTestExecutionListener: 处理 @DirtiesContext 注解
  • TransactionalTestExecutionListener: 提供带有默认回滚语义的事务性测试执行
  • SqlScriptsTestExecutionListener: 执行通过 @Sql 注解配置的SQL脚本
注册用户自定义的 TestExecutionListeners

用户自定义的测试执行监听器 TestExecutionListeners 可以通过注解 @TestExecutionListeners 注册到测试类及其 子类中。了解注解 @TestExecutionListeners 的详情和示例,请参阅注解支持 和javadocs。

默认 TestExecutionListeners的自动发现

通过注解 @TestExecutionListeners 注册用户自定义的测试执行监听器 TestExecutionListeners 适合用户 自定义监听器的有限测试场景;尽管如此,当用户自定义监听器跨越测试套件的时候将会非常麻烦。为了解决这个问题,Spring 框架 4.1 通过 SpringFactoriesLoader 机制支持 默认 测试执行监听器 TestExecutionListener 实现的自动发现。

具体的,spring-test 模块在属性文件 META-INF/spring.factories 下以org.springframework.test.context.TestExecutionListener 为键声明所有的核心默认测试执行监听器 TestExecutionListeners 。第三方框架和开发人员可以以相同的方式在属性文件中 增加他们自己的测试执行监听器 TestExecutionListeners 。

TestExecutionListeners 的顺序

当TestContext框架通过前述的 SpringFactoriesLoader 机制发现默认的测试执行监听器 TestExecutionListeners, 实例化的监听器使用spring的比较器 AnnotationAwareOrderComparator 进行排序,该比较器实现了 Ordered 接口并加注 了 @Order 注解。spring提供的 AbstractTestExecutionListener 和所有的默认的测试执行监听器 TestExecutionListeners 都使用适当的值实现了 Ordered 接口。 因此,第三方框架和开发人员也要确保他们的 默认 测试执行监听器 TestExecutionListeners 使用适当的顺序注册,通过实现 Ordered 接口或者声明 @Order 注解。为了弄清楚每一个核心监听器赋值详情,可以查阅Javadoc来了解 默认的核心测试执行监听器 TestExecutionListeners 的 getOrder() 方法。

合并 TestExecutionListeners

如果通过 @TestExecutionListeners 注解注册了用户自定义的测试执行监听器 TestExecutionListener,则 default 监听器将不再注册。在大多数测试场景下,这有效地强制了开发人员手动声明除了自定义监听器以外的所有默认监听器。下述的列表演示了 这种配置风格。

@ContextConfiguration
@TestExecutionListeners({
    MyCustomTestExecutionListener.class,
    ServletTestExecutionListener.class,
    DependencyInjectionTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
    SqlScriptsTestExecutionListener.class
})
public class MyTest {
    // 类体...
}

这种方法的挑战时它需要开发人员确切地了解哪些监听器会默认注册。而且,这些默认的监听器在不同的版本之间还会变更-- 例如,SqlScriptsTestExecutionListener 监听器时在spring 4.1中被引入的。此外,第三方框架喜欢通过前述的 自动发现机制 来注册他们自己的默认的测试执行监听器 TestExecutionListener

为了避免必须知道或者时重新声明 所有 默认 监听器,测试执行监听器注解 @TestExecutionListenersmergeMode 属性可以设置为 MergeMode.MERGE_WITH_DEFAULTS。 该属性用来指示本地声明的监听器应该与默认的 监听器进行合并。合并算法确保重复的监听器将从列表中删除并且合并的监听器结果集合是根据 AnnotationAwareOrderComparator 的语义有序排列的,其排序应该与 the section called “TestExecutionListeners 的顺序” 中的描述一致。如果一个监听器实现了 Ordered 接口或者时声明了 @Order 注解,它将影响与默认的监听器合并所在的位置,否则在合并的时候本地声明 的监听器将简单地追加到默认的监听器列表上。

例如,在前面的示例配置中,如果监听器 MyCustomTestExecutionListener 类的 order 值(例如,500) 小于 ServletTestExecutionListener 类的值(碰巧是1000),于是 MyCustomTestExecutionListener 将自动地合并到 ServletTestExecutionListener 类的 前面 ,前面的示例将替换成下述的形式。

@ContextConfiguration
@TestExecutionListeners(
    listeners = MyCustomTestExecutionListener.class,
    mergeMode = MERGE_WITH_DEFAULTS,
)
public class MyTest {
    // 类体...
}

上下文管理

每一个 TestContext 为它所负责的测试实例提供了上下文管理和缓存支持。测试实例不会自动地接受对配置的 应用上下为 ApplicationContext 的方法。 尽管如此,如果一个测试类实现了 ApplicationContextAware 接口,在测试类中一个应用上下文 ApplicationContext 的引用将被提供。注意, AbstractJUnit4SpringContextTestsAbstractTestNGSpringContextTests 实现了接口 ApplicationContextAware ,因此自动地提供 了对 ApplicationContext 对象的方法。

[Tip]@Autowired ApplicationContext

作为实现 ApplicationContextAware 接口的一种可选方式,你可以通过在字段或setter方法上增加注解 @Autowired 来为你的测试类注入应用上下文对象,例如:

@RunWith(SpringJUnit4ClassRunner.class)a
@ContextConfiguration
public class MyTest {

    @Autowired
    private ApplicationContext applicationContext;

    // 类体...
}

类似地,如果你的测试配置为加载一个 WebApplicationContext ,你可以注入web应用上下文到你的测试中,如下:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration
public class MyWebAppTest {
    @Autowired
    private WebApplicationContext wac;

    // 类体...
}

通过注解 @Autowired 依赖注入功能由 DependencyInjectionTestExecutionListener 提供,该 监听器属于默认配置(具体可参考 the section called “Dependency injection of test fixtures” )。

使用测试框架的类不需要继承特定的类或实现特定的接口。反而,在类级别声明一个 @ContextConfiguration 注解 就可以简单地配置。如果你的测试类没有明确地声明应用上下文资源 位置 或注解 ,配置的上下文加载器 ContextLoader 将决定如何从默认的位置或默认的配置类上加载上下文。 除了上下文资源 位置 和注解 的 方式,你还可以通过应用上下文初始化器 initializers 的方式。

下述章节,解释了配置应用上下文 ApplicationContext 的几种方式,通过XML配置文件,通过注解类(典型的是 增加了 @Configuration 注解的类),或者通过使用Spring的 @ContextConfiguration 注解的初始化器 的方式。 可选地, 为了高级的用例,你还可以实现和配置你自己的智能上下文加载器类 SmartContextLoader

使用XML资源的上下文配置

使用XML配置文件为你的测试加载一个应用上下文 ApplicationContext , 使用 @ContextConfiguration 注解你的测试类并以数组的形式配置 locations 属性,该属性包含XML配置元数据的资源位置。一个普通的相对路径 —— 例如,"context.xml" ——将会认为是相对于测试类定义所在包的相对路径。 如果一个路径以“/”开始,则该路径 被认为是一个绝对的类路径位置,例如,"/org/example/config.xml"。一个表示资源的URL(例如,一个路径前缀 为 classpath: , file: , http: , 等等)路径顾名思义。

@RunWith(SpringJUnit4ClassRunner.class)
// 应用上下文将会从类路径根下的 "/app-config.xml" 和 "/test-config.xml" 加载
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"})
public class MyTest {
    // 类体...
}

注解 @ContextConfiguration 通过标准的Java value 属性支持 locations 属性的别名。 因此,如果在注解 @ContextConfiguration 中没有别的属性需要设置, 你可以忽略 locations 属性 的名字使用一种如下示例的简洁方式进行声明。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"})
public class MyTest {
    // 类体...
}

如果你同时忽略了 @ContextConfiguration 注解的 locationsvalue 属性, 那么测试上下文框架 将会尝试探测默认的XML资源位置。确切地说,GenericXmlContextLoaderGenericXmlWebContextLoader 会 基于测试类的名字探测默认的位置,如果你的测试类命名为 com.example.MyTestGenericXmlContextLoader 类 将从 "classpath:com/example/MyTest-context.xml" 位置加载应用上下文。

package com.example;

@RunWith(SpringJUnit4ClassRunner.class)
// 应用上下文将从 "classpath:com/example/MyTest-context.xml" 位置加载
@ContextConfiguration
public class MyTest {
    // 类体...
}
使用Groovy脚本配置上下文

使用Groovy脚本为你的测试类加载一个应用上下文 ApplicationContext,可以利用 Groovy DSL 注解你的测试类, 在注解 @ContextConfigurationlocationsvalue 属性中使用一个包含Groovy 脚本 的资源位置的数组配置属性。资源查找的语义和XML 配置文件的描述是一致的。

[Tip]启动 Groovy 脚本支持

如果Groovy在类路径上,在测试上下文框架中使用Groovy脚本加载应用上下文 ApplicationContext 的支持是自动启动的。

@RunWith(SpringJUnit4ClassRunner.class)
// 应用上下文将从类路径根下的 "/AppConfig.groovy" 和 "/TestConfig.groovy" 加载
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"})
public class MyTest {
    // 类体...
}

如果你同时忽略了注解 @ContextConfigurationlocationsvalue 属性,则测试上下文框架将会 尝试探测默认的Groovy脚本。确切地说,加载器 GenericGroovyXmlContextLoaderGenericGroovyXmlWebContextLoader 基于测试类的名字探测默认的位置。如果你的测试类命名为 com.example.MyTest , 那么Groovy上下文加载器将会 从位置 "classpath:com/example/MyTestContext.groovy" 加载你的应用上下文。

package com.example;

@RunWith(SpringJUnit4ClassRunner.class)
// 应用上下文 ApplicationContext 将从位置 "classpath:com/example/MyTestContext.groovy" 加载
@ContextConfiguration
public class MyTest {
    // 类体...
}
[Tip]同时声明XML 配置和Groovy脚本

XML配置文件和Groovy脚本可以通过注解 @ContextConfiguration 的属性 locationsvalue 同时 同时声明。如果配置的资源位置以 .xml 结尾,则将使用 XmlBeanDefinitionReader 加载,否则将会使用 GroovyBeanDefinitionReader 加载。

下述示例演示在集成测试中混合两种方式。

@RunWith(SpringJUnit4ClassRunner.class)
// 应用上下文ApplicationContext 将从位置"/app-config.xml" 和 "/TestConfig.groovy"加载
@ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" })
public class MyTest {
    // 类体...
}
使用注解类配置上下文

使用注解类 (参考???)为你的测试类加载一个应用上下文 ApplicationContext ,可以 为你的测试类增加注解 @ContextConfiguration 并使用一个数组配置其 classes 属性,该数组包含注解 类的引用。

@RunWith(SpringJUnit4ClassRunner.class)
// 应用上下文ApplicationContext 将从AppConfig 和 TestConfig 配置类加载
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class})
public class MyTest {
    // 类体...
}
[Tip]注解类

注解类 可以指下述中的任何一种。

  • 使用注解 @Configuration 配置的类
  • 一个组件 (例如, 被 @Component, @Service, @Repository等注解的类.)
  • 一个使用 javax.inject 注解的 JSR-330 兼容类
  • 任意包含@Bean注解方法的类

参阅注解 @Configuration@Bean 的javadoc了解 注解类 的配置和语义的深入信息,请多关注 `@Bean` Lite Mode 的讨论。

如果你忽略了直接 @ContextConfigurationclasses 属性,测试上下文框架将会尝试探测默认配置 类的存在。确切地说,加载器 AnnotationConfigContextLoaderAnnotationConfigWebContextLoader 将会探测满足需求的所有测试类的静态内部类, 这些内部类通过注解 @Configuration 指定了其配置。下述示例 中,测试类 OrderServiceTest 声明了一个名字为 Config 的静态内部配置类, 该类被用来为测试类加载应用 上下文 ApplicationContext 。请注意,配置类的名字是任意的。除此之外,必要时,一个测试类可以包含多个 静态内部配置类。

@RunWith(SpringJUnit4ClassRunner.class)
// 应用上下文 ApplicationContext 将从静态内部配置类 Config加载
@ContextConfiguration
public class OrderServiceTest {

    @Configuration
    static class Config {

        // 这个Bean将注入到OrderServiceTest中
        @Bean
        public OrderService orderService() {
            OrderService orderService = new OrderServiceImpl();
            // 设置属性, 等等操作
            return orderService;
        }
    }

    @Autowired
    private OrderService orderService;

    @Test
    public void testOrderService() {
        // 测试服务 orderService
    }

}
组合XML,Groovy脚本和注解类三种配置方式

有时候,可以期望组合XML配置文件、Groovy脚本和注解类(例如,典型的 @Configuration 类)三种方式 来为你的测试类配置一个应用上下文。例如,你在产品中使用XML配置,在测试中你可能决定想使用注解类 @Configuration 来配置Springg管理的组件,反之亦然。

此外,一些第三方框架(就像Spring Boot)为加载应用上下文 ApplicationContext 提供了优秀的支持,他们 可以从不同类型的资源中同时加载(例如,XML配置文件,Groovy脚本和增加了 @Configuration 的类)。由于历史的原因, Spring框架没有以标准发布的形式支持混合配置。因此,Spring框架中的 spring-test 模块的大多数上下文 加载器 SmartContextLoader 的实现在每一个测试上下文中仅支持一种资源类型;尽管如此,这并不意味着你 不能同时使用它们。通用规则的一个例外是上下文加载器 GenericGroovyXmlContextLoaderGenericGroovyXmlWebContextLoader 同时支持XML配置文件和Groovy脚本。 此外,第三方框架可能通过注解 @ContextConfiguration 选择支持 locationsclasses 的声明以及测试框架的标准测试支持, 你可以有如下的选项。

如果你想使用资源位置(例如,XML或Groovy脚本) 注解 @Configuration类配置你的测试类,你必须 选择一个作为 入口点 ,并且另外一个必须包括或者导入到另一个。例如,在XML或Groovy脚本中,你可以通过组件 扫描或正常定义Spring beans包含 @Configuration 类;反过来,在注解 @Configuration 配置的类中, 你可以使用注解 @ImportResource 导入XML配置文件。注意,这种行为与在产品中配置您的应用在语义上是等价的: 在产品配置中,你可以定义XML或Groovy资源位置的集合,或者配置类 @Configuration 的集合,产品的应用上下文 ApplicationContext 将从中加载, 但是你仍然有包含或导入其他类型配置的自由。

使用上下文初始化器配置上下文

使用上下文初始化器为你的测试类配置一个应用上下文 ApplicationContext ,可以使用 @ContextConfiguration 注解你的测试类,然后使用一个数组配置其 initializers 属性,该数组包含实现了 ApplicationContextInitializer 的类的引用。 声明的上下文初始化器其后将被用来初始化测试类的 ConfigurableApplicationContext 。注意, 每一个具体的声明的初始化器支持的 ConfigurableApplicationContext 类型必须与使用 SmartContextLoader 创建的应用上下文 ApplicationContext 类型兼容。此外,初始化器被调用的顺序依赖于它们是否实现了Spring的 Ordered 接口或加注了Spring的 @Order 注解或加注了标准的 @Priority 注解。

@RunWith(SpringJUnit4ClassRunner.class)
// 应用上下文ApplicationContext将从配置类TestConfig加载并从TestAppCtxInitializer初始化
@ContextConfiguration(
    classes = TestConfig.class,
    initializers = TestAppCtxInitializer.class)
public class MyTest {
    //类体...
}

完全地忽略XML配置或注解类 @ContextConfiguration 方式的声明也成为了可能,取而代之的是仅仅声明在上 下文中负责注册beans的类 ApplicationContextInitializer —— 例如,从XML文件或配置类中以编程方式 加载bean定义。

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext 将被EntireAppInitializer初始化,假定EntireAppInitializer类在上下文中注册beans
@ContextConfiguration(initializers = EntireAppInitializer.class)
public class MyTest {
    // 类体...
}
上下文配置继承

注解 @ContextConfiguration 支持两个布尔属性 inheritLocationsinheritInitializers,这两个属性 指的是在潮类中声明的资源位置、注解类或上下文初始化器是否可以 继承 。它们两个的默认值都是 true。那意味着一个测试 类默认会继承超类的资源位置、注解类或上下文初始化器的配置。确切地说,一个测试类资源位置或者是注解类配置被追加到超类声明 的资源位置或注解类的列表上。类似地,指定测试类的初始化器将会增加到超类定义的初始化集合上。因此,子类拥有 继承 资源 位置、注解类或上下文初始化器的选项。

如何 @ContextConfiguration' 的 inheritLocationsinheritInitializers 属性设置为 false , 测试类的资源位置、注解类或上下文初始化器配置将会替代超类的配置。

在下述的使用XML资源位置的示例中,测试类 ExtendedTest 的应用上下文 ApplicationContext 将依序从 "base-config.xml""extended-config.xml" 两个配置文件中加载。 在 "extended-config.xml" 配置文件中定义的beans将会 覆盖 (例如,替换)在 "base-config.xml" 配置文件中定义的beans。

@RunWith(SpringJUnit4ClassRunner.class)
// 应用上下文ApplicationContext将从类路径根下的配置文件"/base-config.xml"中加载
@ContextConfiguration("/base-config.xml")
public class BaseTest {
    // 类体...
}

// 应用上下文ApplicationContext将从类路径根下的配置文件 "/base-config.xml"和"/extended-config.xml"中加载
@ContextConfiguration("/extended-config.xml")
public class ExtendedTest extends BaseTest {
    // 类体...
}

类似的,在下述使用注解类的示例中,ExtendedTest 的应用上下文 ApplicationContext 将会从 BaseConfig ExtendedConfig 两个配置类中加载。在配置类 ExtendedConfig 中定义的beans因此将会覆盖在 BaseConfig 配置类中 定义的beans。

@RunWith(SpringJUnit4ClassRunner.class)
// 应用上下文ApplicationContext将从配置类BaseConfig中加载
@ContextConfiguration(classes = BaseConfig.class)
public class BaseTest {
    // 类体...
}

// 应用上下文ApplicationContext将从配置类BaseConfig 和 ExtendedConfig中加载
@ContextConfiguration(classes = ExtendedConfig.class)
public class ExtendedTest extends BaseTest {
    // 类体...
}

在下述使用上下文初始化器的示例中,测试类 ExtendedTest 的应用上下文 ApplicationContext 将使用初始化器 BaseInitializer ExtendedInitializer 进行初始化。 注意,尽管如此,初始化器的调用顺序依赖于它们 是否实现了Spring的 Ordered 接口,加注 @Order 注解或是标准的 @Priority 注解。

@RunWith(SpringJUnit4ClassRunner.class)
// 应用上下文ApplicationContext 将通过初始化器BaseInitializer进行初始化
@ContextConfiguration(initializers = BaseInitializer.class)
public class BaseTest {
    // 类体...
}

// 应用上下文ApplicationContext 将通过BaseInitializer和ExtendedInitializer进行初始化
@ContextConfiguration(initializers = ExtendedInitializer.class)
public class ExtendedTest extends BaseTest {
    // 类体...
}
使用环境概要进行上下文配置

在Spring 3.1对环境和概要(也称为 bean 定义概要)的符号表示法引入了优秀的支持,集成测试可以 为各种不同的测试场景配置为可以激活特定的bean 定义概要。这些可以通过在测试类上增加 @ActiveProfiles 注解并为之提供一个概要的列表,这个概要列表将在为测试类加载应用上下文 ApplicationContext 的时候被 激活。

[Note]Note

注解 @ActiveProfiles 可以被用来与任何新的智能上下文加载器 SmartContextLoader SPI的实现配合 使用,但是却不支持旧的上下文加载器 ContextLoader SPI 的实现。

让我们看一些基于XML配置和基于配置类注解@Configuration的示例。

<!-- app-config.xml -->
<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="...">

    <bean id="transferService"
            class="com.bank.service.internal.DefaultTransferService">
        <constructor-arg ref="accountRepository"/>
        <constructor-arg ref="feePolicy"/>
    </bean>

    <bean id="accountRepository"
            class="com.bank.repository.internal.JdbcAccountRepository">
        <constructor-arg ref="dataSource"/>
    </bean>

    <bean id="feePolicy"
        class="com.bank.service.internal.ZeroFeePolicy"/>

    <beans profile="dev">
        <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 profile="default">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script
                location="classpath:com/bank/config/sql/schema.sql"/>
        </jdbc:embedded-database>
    </beans>

</beans>
package com.bank.service;

@RunWith(SpringJUnit4ClassRunner.class)
// 应用上下文 ApplicationContext 将从配置文件 "classpath:/app-config.xml" 中加载
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
public class TransferServiceTest {

    @Autowired
    private TransferService transferService;

    @Test
    public void testTransferService() {
        // test the transferService
    }
}

当测试类 TransferServiceTest 运行的时候,它的应用上下文 ApplicationContext 将从类路径根下的 配置文件 app-config.xml 中加载。如果检查配置文件,你会注意到bean accountRepository 依赖bean dataSource ;尽管如此,dataSource bean并没有被定义为一个顶层的bean。相反,它被定义了三次:分别 在 product 概要,dev 概要,以及 default 概要中。

通过使用 @ActiveProfiles("dev") 注解测试类 TransferServiceTest ,我们提示Spring 测试框架 使用激活的概要 {"dev"} 来加载应用上下文 ApplicationContext 。 于是,一个嵌入式数据库将被创建 并存储测试数据,并且 accountRepository bean将注入一个开发的 DataSource。这也是我们想在集成 测试中希望的。

将bean分配给 default 的概要有时候是很有用的。默认概要中的bean仅当没有其他的概要被激活时使用。这可以被 用来定义一个 fallback bean,该bean被用于应用的默认状态。例如,你可以显式地提供一个 dev 概要和一个 product 概要,但是当这两个都没有激活的情况下,你可以定义一个内存数据源作为默认值。

下述的示例代码演示了如何使用 @Configuration 类来实现与XML相同的配置。

@Configuration
@Profile("dev")
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
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
@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();
    }
}
@Configuration
public class TransferServiceConfig {

    @Autowired DataSource dataSource;

    @Bean
    public TransferService transferService() {
        return new DefaultTransferService(accountRepository(), feePolicy());
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public FeePolicy feePolicy() {
        return new ZeroFeePolicy();
    }

}
package com.bank.service;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
public class TransferServiceTest {

    @Autowired
    private TransferService transferService;

    @Test
    public void testTransferService() {
        // 测试相关逻辑
    }
}

这次的变化,我们将XML配置拆分成了四个独立注解了 @Configuration 的配置类:

  • TransferServiceConfig: 通过使用注解 @Autowired 依赖注入获取一个数据源 dataSource
  • StandaloneDataConfig: 定义了一个适合于开发人员测试的嵌入式数据库的数据源 dataSource
  • JndiDataConfig: 定义一个从JNDI产品环境中获得数据源 dataSource
  • DefaultDataConfig: 为防止没有激活的概要配置,在一个默认的嵌入式数据库中定义一个数据源 dataSource

在使用基于XML的配置示例中, 我们仍然使用 @ActiveProfiles("dev") 注解测试类 TransferServiceTest , 但是这次我们通过注解 @ContextConfiguration指定了所有的四个配置类。测试类的本身没有发生任何改变。

通常的用例是在一个项目中跨越多个测试类使用一组概要配置。因此,为了避免注解 @ActiveProfiles 的 重复声明,可以在一个基类上只声明一次注解 @ActiveProfiles, 其子类将自动的继承父类的注解配置。在下列 的示例中,注解 @ActiveProfiles 的声明被移入了抽象超类 AbstractIntegrationTest 中。

package com.bank.service;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
public abstract class AbstractIntegrationTest {
}
package com.bank.service;

// "dev" 概要从超类中继承
public class TransferServiceTest extends AbstractIntegrationTest {

    @Autowired
    private TransferService transferService;

    @Test
    public void testTransferService() {
        // 测试逻辑
    }
}

注解 @ActiveProfiles 也支持一个 inheritProfiles 属性,该属性可以用来禁止激活概要的继承特性。

package com.bank.service;

// "dev" 概要被 "production" 概要覆盖
@ActiveProfiles(profiles = "production", inheritProfiles = false)
public class ProductionTransferServiceTest extends AbstractIntegrationTest {
    // 测试体
}

此外,有些时候为测试类采用 可编程 的方式而非声明的方式解析激活的概要是必要的 —— 例如, 基于:

  • 当前的操作系统
  • 是否在持续集成构建服务器上被执行
  • 存在特定的环境变量
  • 存在用户定义的类级别的注解
  • 等等。

为了可编程地解析活动bean定义概要,简单地实现一个自定义的活动概要解析器 ActiveProfilesResolver 并 通过注解 @ActiveProfiles 的属性 resolver 来注册上。 下列的示例演示了如何实现一个用户自定义的 活动概要解析器 OperatingSystemActiveProfilesResolver 并注册之。想了解进一步的信息,可参考相关 的javadoc。

package com.bank.service;

// 通过自定义的解析器以编程方式覆盖 "dev" 概要
@ActiveProfiles(
    resolver = OperatingSystemActiveProfilesResolver.class,
    inheritProfiles = false)
public class TransferServiceTest extends AbstractIntegrationTest {
    // 测试体
}
package com.bank.service.test;

public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {

    @Override
    String[] resolve(Class<?> testClass) {
        String profile = ...;
        // 基于操作系统决定概要的取值
        return new String[] {profile};
    }
}
测试属性源上下文配置

Spring 3.1 introduced first-class support in the framework for the notion of an environment with a hierarchy of property sources, and since Spring 4.1 integration tests can be configured with test-specific property sources. In contrast to the @PropertySource annotation used on @Configuration classes, the @TestPropertySource annotation can be declared on a test class to declare resource locations for test properties files or inlined properties. These test property sources will be added to the Environment's set of PropertySources for the ApplicationContext loaded for the annotated integration test.

[Note]Note

@TestPropertySource may be used with any implementation of the SmartContextLoader SPI, but @TestPropertySource is not supported with implementations of the older ContextLoader SPI.

Implementations of SmartContextLoader gain access to merged test property source values via the getPropertySourceLocations() and getPropertySourceProperties() methods in MergedContextConfiguration.

Declaring test property sources

Test properties files can be configured via the locations or value attribute of @TestPropertySource as shown in the following example.

Both traditional and XML-based properties file formats are supported — for example, "classpath:/com/example/test.properties" or "file:///path/to/file.xml".

Each path will be interpreted as a Spring Resource. A plain path — for example, "test.properties" — will be treated as a classpath resource that is relative to the package in which the test class is defined. A path starting with a slash will be treated as an absolute classpath resource, for example: "/org/example/test.xml". A path which references a URL (e.g., a path prefixed with classpath:, file:, http:, etc.) will be loaded using the specified resource protocol. Resource location wildcards (e.g. **/*.properties) are not permitted: each location must evaluate to exactly one .properties or .xml resource.

@ContextConfiguration
@TestPropertySource("/test.properties")
public class MyIntegrationTests {
    // class body...
}

Inlined properties in the form of key-value pairs can be configured via the properties attribute of @TestPropertySource as shown in the following example. All key-value pairs will be added to the enclosing Environment as a single test PropertySource with the highest precedence.

The supported syntax for key-value pairs is the same as the syntax defined for entries in a Java properties file:

  • "key=value"
  • "key:value"
  • "key value"
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"})
public class MyIntegrationTests {
    // class body...
}

Default properties file detection

If @TestPropertySource is declared as an empty annotation (i.e., without explicit values for the locations or properties attributes), an attempt will be made to detect a default properties file relative to the class that declared the annotation. For example, if the annotated test class is com.example.MyTest, the corresponding default properties file is "classpath:com/example/MyTest.properties". If the default cannot be detected, an IllegalStateException will be thrown.

Precedence

Test property sources have higher precedence than those loaded from the operating system’s environment or Java system properties as well as property sources added by the application declaratively via @PropertySource or programmatically. Thus, test property sources can be used to selectively override properties defined in system and application property sources. Furthermore, inlined properties have higher precedence than properties loaded from resource locations.

In the following example, the timezone and port properties as well as any properties defined in "/test.properties" will override any properties of the same name that are defined in system and application property sources. Furthermore, if the "/test.properties" file defines entries for the timezone and port properties those will be overridden by the inlined properties declared via the properties attribute.

@ContextConfiguration
@TestPropertySource(
    locations = "/test.properties",
    properties = {"timezone = GMT", "port: 4242"}
)
public class MyIntegrationTests {
    // class body...
}

Inheriting and overriding test property sources

@TestPropertySource supports boolean inheritLocations and inheritProperties attributes that denote whether resource locations for properties files and inlined properties declared by superclasses should be inherited. The default value for both flags is true. This means that a test class inherits the locations and inlined properties declared by any superclasses. Specifically, the locations and inlined properties for a test class are appended to the locations and inlined properties declared by superclasses. Thus, subclasses have the option of extending the locations and inlined properties. Note that properties that appear later will shadow (i.e.., override) properties of the same name that appear earlier. In addition, the aforementioned precedence rules apply for inherited test property sources as well.

If @TestPropertySource's inheritLocations or inheritProperties attribute is set to false, the locations or inlined properties, respectively, for the test class shadow and effectively replace the configuration defined by superclasses.

In the following example, the ApplicationContext for BaseTest will be loaded using only the "base.properties" file as a test property source. In contrast, the ApplicationContext for ExtendedTest will be loaded using the "base.properties" and "extended.properties" files as test property source locations.

@TestPropertySource("base.properties")
@ContextConfiguration
public class BaseTest {
    // ...
}

@TestPropertySource("extended.properties")
@ContextConfiguration
public class ExtendedTest extends BaseTest {
    // ...
}

In the following example, the ApplicationContext for BaseTest will be loaded using only the inlined key1 property. In contrast, the ApplicationContext for ExtendedTest will be loaded using the inlined key1 and key2 properties.

@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
public class BaseTest {
    // ...
}

@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
public class ExtendedTest extends BaseTest {
    // ...
}
Loading a WebApplicationContext

Spring 3.2 introduced support for loading a WebApplicationContext in integration tests. To instruct the TestContext framework to load a WebApplicationContext instead of a standard ApplicationContext, simply annotate the respective test class with @WebAppConfiguration.

The presence of @WebAppConfiguration on your test class instructs the TestContext framework (TCF) that a WebApplicationContext (WAC) should be loaded for your integration tests. In the background the TCF makes sure that a MockServletContext is created and supplied to your test’s WAC. By default the base resource path for your MockServletContext will be set to "src/main/webapp". This is interpreted as a path relative to the root of your JVM (i.e., normally the path to your project). If you’re familiar with the directory structure of a web application in a Maven project, you’ll know that "src/main/webapp" is the default location for the root of your WAR. If you need to override this default, simply provide an alternate path to the @WebAppConfiguration annotation (e.g., @WebAppConfiguration("src/test/webapp")). If you wish to reference a base resource path from the classpath instead of the file system, just use Spring’s classpath: prefix.

Please note that Spring’s testing support for WebApplicationContexts is on par with its support for standard ApplicationContexts. When testing with a WebApplicationContext you are free to declare either XML configuration files or @Configuration classes via @ContextConfiguration. You are of course also free to use any other test annotations such as @TestExecutionListeners, @TransactionConfiguration, @ActiveProfiles, etc.

The following examples demonstrate some of the various configuration options for loading a WebApplicationContext.

Conventions. 

@RunWith(SpringJUnit4ClassRunner.class)

// defaults to "file:src/main/webapp"
@WebAppConfiguration

// detects "WacTests-context.xml" in same package
// or static nested @Configuration class
@ContextConfiguration

public class WacTests {
    //...
}

The above example demonstrates the TestContext framework’s support for convention over configuration. If you annotate a test class with @WebAppConfiguration without specifying a resource base path, the resource path will effectively default to "file:src/main/webapp". Similarly, if you declare @ContextConfiguration without specifying resource locations, annotated classes, or context initializers, Spring will attempt to detect the presence of your configuration using conventions (i.e., "WacTests-context.xml" in the same package as the WacTests class or static nested @Configuration classes).

Default resource semantics. 

@RunWith(SpringJUnit4ClassRunner.class)

// file system resource
@WebAppConfiguration("webapp")

// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")

public class WacTests {
    //...
}

This example demonstrates how to explicitly declare a resource base path with @WebAppConfiguration and an XML resource location with @ContextConfiguration. The important thing to note here is the different semantics for paths with these two annotations. By default, @WebAppConfiguration resource paths are file system based; whereas, @ContextConfiguration resource locations are classpath based.

Explicit resource semantics. 

@RunWith(SpringJUnit4ClassRunner.class)

// classpath resource
@WebAppConfiguration("classpath:test-web-resources")

// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")

public class WacTests {
    //...
}

In this third example, we see that we can override the default resource semantics for both annotations by specifying a Spring resource prefix. Contrast the comments in this example with the previous example.

To provide comprehensive web testing support, Spring 3.2 introduced a ServletTestExecutionListener that is enabled by default. When testing against a WebApplicationContext this TestExecutionListener sets up default thread-local state via Spring Web’s RequestContextHolder before each test method and creates a MockHttpServletRequest, MockHttpServletResponse, and ServletWebRequest based on the base resource path configured via @WebAppConfiguration. ServletTestExecutionListener also ensures that the MockHttpServletResponse and ServletWebRequest can be injected into the test instance, and once the test is complete it cleans up thread-local state.

Once you have a WebApplicationContext loaded for your test you might find that you need to interact with the web mocks — for example, to set up your test fixture or to perform assertions after invoking your web component. The following example demonstrates which mocks can be autowired into your test instance. Note that the WebApplicationContext and MockServletContext are both cached across the test suite; whereas, the other mocks are managed per test method by the ServletTestExecutionListener.

Injecting mocks. 

@WebAppConfiguration
@ContextConfiguration
public class WacTests {

    @Autowired
    WebApplicationContext wac; // cached

    @Autowired
    MockServletContext servletContext; // cached

    @Autowired
    MockHttpSession session;

    @Autowired
    MockHttpServletRequest request;

    @Autowired
    MockHttpServletResponse response;

    @Autowired
    ServletWebRequest webRequest;

    //...
}

Context caching

Once the TestContext framework loads an ApplicationContext (or WebApplicationContext) for a test, that context will be cached and reused for all subsequent tests that declare the same unique context configuration within the same test suite. To understand how caching works, it is important to understand what is meant by unique and test suite.

An ApplicationContext can be uniquely identified by the combination of configuration parameters that are used to load it. Consequently, the unique combination of configuration parameters are used to generate a key under which the context is cached. The TestContext framework uses the following configuration parameters to build the context cache key:

  • locations (from @ContextConfiguration)
  • classes (from @ContextConfiguration)
  • contextInitializerClasses (from @ContextConfiguration)
  • contextLoader (from @ContextConfiguration)
  • parent (from @ContextHierarchy)
  • activeProfiles (from @ActiveProfiles)
  • propertySourceLocations (from @TestPropertySource)
  • propertySourceProperties (from @TestPropertySource)
  • resourceBasePath (from @WebAppConfiguration)

For example, if TestClassA specifies {"app-config.xml", "test-config.xml"} for the locations (or value) attribute of @ContextConfiguration, the TestContext framework will load the corresponding ApplicationContext and store it in a static context cache under a key that is based solely on those locations. So if TestClassB also defines {"app-config.xml", "test-config.xml"} for its locations (either explicitly or implicitly through inheritance) but does not define @WebAppConfiguration, a different ContextLoader, different active profiles, different context initializers, different test property sources, or a different parent context, then the same ApplicationContext will be shared by both test classes. This means that the setup cost for loading an application context is incurred only once (per test suite), and subsequent test execution is much faster.

[Note]Test suites and forked processes

The Spring TestContext framework stores application contexts in a static cache. This means that the context is literally stored in a static variable. In other words, if tests execute in separate processes the static cache will be cleared between each test execution, and this will effectively disable the caching mechanism.

To benefit from the caching mechanism, all tests must run within the same process or test suite. This can be achieved by executing all tests as a group within an IDE. Similarly, when executing tests with a build framework such as Ant, Maven, or Gradle it is important to make sure that the build framework does not fork between tests. For example, if the forkMode for the Maven Surefire plug-in is set to always or pertest, the TestContext framework will not be able to cache application contexts between test classes and the build process will run significantly slower as a result.

Since having a large number of application contexts loaded within a given test suite can cause the suite to take an unnecessarily long time to execute, it is often beneficial to know exactly how many contexts have been loaded and cached. To view the statistics for the underlying context cache, simply set the log level for the org.springframework.test.context.cache logging category to DEBUG.

In the unlikely case that a test corrupts the application context and requires reloading — for example, by modifying a bean definition or the state of an application object — you can annotate your test class or test method with @DirtiesContext (see the discussion of @DirtiesContext in the section called “Spring 测试相关注解”). This instructs Spring to remove the context from the cache and rebuild the application context before executing the next test. Note that support for the @DirtiesContext annotation is provided by the DirtiesContextTestExecutionListener which is enabled by default.

Context hierarchies

When writing integration tests that rely on a loaded Spring ApplicationContext, it is often sufficient to test against a single context; however, there are times when it is beneficial or even necessary to test against a hierarchy of ApplicationContexts. For example, if you are developing a Spring MVC web application you will typically have a root WebApplicationContext loaded via Spring’s ContextLoaderListener and a child WebApplicationContext loaded via Spring’s DispatcherServlet. This results in a parent-child context hierarchy where shared components and infrastructure configuration are declared in the root context and consumed in the child context by web-specific components. Another use case can be found in Spring Batch applications where you often have a parent context that provides configuration for shared batch infrastructure and a child context for the configuration of a specific batch job.

As of Spring Framework 3.2.2, it is possible to write integration tests that use context hierarchies by declaring context configuration via the @ContextHierarchy annotation, either on an individual test class or within a test class hierarchy. If a context hierarchy is declared on multiple classes within a test class hierarchy it is also possible to merge or override the context configuration for a specific, named level in the context hierarchy. When merging configuration for a given level in the hierarchy the configuration resource type (i.e., XML configuration files or annotated classes) must be consistent; otherwise, it is perfectly acceptable to have different levels in a context hierarchy configured using different resource types.

The following JUnit-based examples demonstrate common configuration scenarios for integration tests that require the use of context hierarchies.

ControllerIntegrationTests represents a typical integration testing scenario for a Spring MVC web application by declaring a context hierarchy consisting of two levels, one for the root WebApplicationContext (loaded using the TestAppConfig @Configuration class) and one for the dispatcher servlet WebApplicationContext (loaded using the WebConfig @Configuration class). The WebApplicationContext that is autowired into the test instance is the one for the child context (i.e., the lowest context in the hierarchy).

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = TestAppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
public class ControllerIntegrationTests {

    @Autowired
    private WebApplicationContext wac;

    // ...
}

The following test classes define a context hierarchy within a test class hierarchy. AbstractWebTests declares the configuration for a root WebApplicationContext in a Spring-powered web application. Note, however, that AbstractWebTests does not declare @ContextHierarchy; consequently, subclasses of AbstractWebTests can optionally participate in a context hierarchy or simply follow the standard semantics for @ContextConfiguration. SoapWebServiceTests and RestWebServiceTests both extend AbstractWebTests and define a context hierarchy via @ContextHierarchy. The result is that three application contexts will be loaded (one for each declaration of @ContextConfiguration), and the application context loaded based on the configuration in AbstractWebTests will be set as the parent context for each of the contexts loaded for the concrete subclasses.

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml")
public class SoapWebServiceTests extends AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml")
public class RestWebServiceTests extends AbstractWebTests {}

The following classes demonstrate the use of named hierarchy levels in order to merge the configuration for specific levels in a context hierarchy. BaseTests defines two levels in the hierarchy, parent and child. ExtendedTests extends BaseTests and instructs the Spring TestContext Framework to merge the context configuration for the child hierarchy level, simply by ensuring that the names declared via ContextConfiguration's name attribute are both "child". The result is that three application contexts will be loaded: one for "/app-config.xml", one for "/user-config.xml", and one for {"/user-config.xml", "/order-config.xml"}. As with the previous example, the application context loaded from "/app-config.xml" will be set as the parent context for the contexts loaded from "/user-config.xml" and {"/user-config.xml", "/order-config.xml"}.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
public class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(name = "child", locations = "/order-config.xml")
)
public class ExtendedTests extends BaseTests {}

In contrast to the previous example, this example demonstrates how to override the configuration for a given named level in a context hierarchy by setting ContextConfiguration's inheritLocations flag to false. Consequently, the application context for ExtendedTests will be loaded only from "/test-user-config.xml" and will have its parent set to the context loaded from "/app-config.xml".

@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
public class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(
        name = "child",
        locations = "/test-user-config.xml",
        inheritLocations = false
))
public class ExtendedTests extends BaseTests {}
[Note]Dirtying a context within a context hierarchy

If @DirtiesContext is used in a test whose context is configured as part of a context hierarchy, the hierarchyMode flag can be used to control how the context cache is cleared. For further details consult the discussion of @DirtiesContext in Spring Testing Annotations and the @DirtiesContext javadocs.

Dependency injection of test fixtures

When you use the DependencyInjectionTestExecutionListener — which is configured by default — the dependencies of your test instances are injected from beans in the application context that you configured with @ContextConfiguration. You may use setter injection, field injection, or both, depending on which annotations you choose and whether you place them on setter methods or fields. For consistency with the annotation support introduced in Spring 2.5 and 3.0, you can use Spring’s @Autowired annotation or the @Inject annotation from JSR 300.

[Tip]Tip

The TestContext framework does not instrument the manner in which a test instance is instantiated. Thus the use of @Autowired or @Inject for constructors has no effect for test classes.

Because @Autowired is used to perform autowiring by type, if you have multiple bean definitions of the same type, you cannot rely on this approach for those particular beans. In that case, you can use @Autowired in conjunction with @Qualifier. As of Spring 3.0 you may also choose to use @Inject in conjunction with @Named. Alternatively, if your test class has access to its ApplicationContext, you can perform an explicit lookup by using (for example) a call to applicationContext.getBean("titleRepository").

If you do not want dependency injection applied to your test instances, simply do not annotate fields or setter methods with @Autowired or @Inject. Alternatively, you can disable dependency injection altogether by explicitly configuring your class with @TestExecutionListeners and omitting DependencyInjectionTestExecutionListener.class from the list of listeners.

Consider the scenario of testing a HibernateTitleRepository class, as outlined in the Goals section. The next two code listings demonstrate the use of @Autowired on fields and setter methods. The application context configuration is presented after all sample code listings.

[Note]Note

The dependency injection behavior in the following code listings is not specific to JUnit. The same DI techniques can be used in conjunction with any testing framework.

The following examples make calls to static assertion methods such as assertNotNull() but without prepending the call with Assert. In such cases, assume that the method was properly imported through an import static declaration that is not shown in the example.

The first code listing shows a JUnit-based implementation of the test class that uses @Autowired for field injection.

@RunWith(SpringJUnit4ClassRunner.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
public class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    @Autowired
    private HibernateTitleRepository titleRepository;

    @Test
    public void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}

Alternatively, you can configure the class to use @Autowired for setter injection as seen below.

@RunWith(SpringJUnit4ClassRunner.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
public class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    private HibernateTitleRepository titleRepository;

    @Autowired
    public void setTitleRepository(HibernateTitleRepository titleRepository) {
        this.titleRepository = titleRepository;
    }

    @Test
    public void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}

The preceding code listings use the same XML context file referenced by the @ContextConfiguration annotation (that is, repository-config.xml), which looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- this bean will be injected into the HibernateTitleRepositoryTests class -->
    <bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <!-- configuration elided for brevity -->
    </bean>

</beans>
[Note]Note

If you are extending from a Spring-provided test base class that happens to use @Autowired on one of its setter methods, you might have multiple beans of the affected type defined in your application context: for example, multiple DataSource beans. In such a case, you can override the setter method and use the @Qualifier annotation to indicate a specific target bean as follows, but make sure to delegate to the overridden method in the superclass as well.

// ...

    @Autowired
    @Override
    public void setDataSource(@Qualifier("myDataSource") DataSource dataSource) {
        super.setDataSource(dataSource);
    }

// ...

The specified qualifier value indicates the specific DataSource bean to inject, narrowing the set of type matches to a specific bean. Its value is matched against <qualifier> declarations within the corresponding <bean> definitions. The bean name is used as a fallback qualifier value, so you may effectively also point to a specific bean by name there (as shown above, assuming that "myDataSource" is the bean id).

Testing request and session scoped beans

Request and session scoped beans have been supported by Spring for several years now, but it’s always been a bit non-trivial to test them. As of Spring 3.2 it’s a breeze to test your request-scoped and session-scoped beans by following these steps.

  • Ensure that a WebApplicationContext is loaded for your test by annotating your test class with @WebAppConfiguration.
  • Inject the mock request or session into your test instance and prepare your test fixture as appropriate.
  • Invoke your web component that you retrieved from the configured WebApplicationContext (i.e., via dependency injection).
  • Perform assertions against the mocks.

The following code snippet displays the XML configuration for a login use case. Note that the userService bean has a dependency on a request-scoped loginAction bean. Also, the LoginAction is instantiated using SpEL expressions that retrieve the username and password from the current HTTP request. In our test, we will want to configure these request parameters via the mock managed by the TestContext framework.

Request-scoped bean configuration. 

<beans>

    <bean id="userService"
            class="com.example.SimpleUserService"
            c:loginAction-ref="loginAction" />

    <bean id="loginAction" class="com.example.LoginAction"
            c:username="{request.getParameter('user')}"
            c:password="{request.getParameter('pswd')}"
            scope="request">
        <aop:scoped-proxy />
    </bean>

</beans>

In RequestScopedBeanTests we inject both the UserService (i.e., the subject under test) and the MockHttpServletRequest into our test instance. Within our requestScope() test method we set up our test fixture by setting request parameters in the provided MockHttpServletRequest. When the loginUser() method is invoked on our userService we are assured that the user service has access to the request-scoped loginAction for the current MockHttpServletRequest (i.e., the one we just set parameters in). We can then perform assertions against the results based on the known inputs for the username and password.

Request-scoped bean test. 

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class RequestScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpServletRequest request;

    @Test
    public void requestScope() {

        request.setParameter("user", "enigma");
        request.setParameter("pswd", "$pr!ng");

        LoginResults results = userService.loginUser();

        // assert results
    }
}

The following code snippet is similar to the one we saw above for a request-scoped bean; however, this time the userService bean has a dependency on a session-scoped userPreferences bean. Note that the UserPreferences bean is instantiated using a SpEL expression that retrieves the theme from the current HTTP session. In our test, we will need to configure a theme in the mock session managed by the TestContext framework.

Session-scoped bean configuration. 

<beans>

    <bean id="userService"
            class="com.example.SimpleUserService"
            c:userPreferences-ref="userPreferences" />

    <bean id="userPreferences"
            class="com.example.UserPreferences"
            c:theme="#{session.getAttribute('theme')}"
            scope="session">
        <aop:scoped-proxy />
    </bean>

</beans>

In SessionScopedBeanTests we inject the UserService and the MockHttpSession into our test instance. Within our sessionScope() test method we set up our test fixture by setting the expected "theme" attribute in the provided MockHttpSession. When the processUserPreferences() method is invoked on our userService we are assured that the user service has access to the session-scoped userPreferences for the current MockHttpSession, and we can perform assertions against the results based on the configured theme.

Session-scoped bean test. 

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class SessionScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpSession session;

    @Test
    public void sessionScope() throws Exception {

        session.setAttribute("theme", "blue");

        Results results = userService.processUserPreferences();

        // assert results
    }
}

Transaction management

In the TestContext framework, transactions are managed by the TransactionalTestExecutionListener which is configured by default, even if you do not explicitly declare @TestExecutionListeners on your test class. To enable support for transactions, however, you must configure a PlatformTransactionManager bean in the ApplicationContext that is loaded via @ContextConfiguration semantics (further details are provided below). In addition, you must declare Spring’s @Transactional annotation either at the class or method level for your tests.

Test-managed transactions

Test-managed transactions are transactions that are managed declaratively via the TransactionalTestExecutionListener or programmatically via TestTransaction (see below). Such transactions should not be confused with Spring-managed transactions (i.e., those managed directly by Spring within the ApplicationContext loaded for tests) or application-managed transactions (i.e., those managed programmatically within application code that is invoked via tests). Spring-managed and application-managed transactions will typically participate in test-managed transactions; however, caution should be taken if Spring-managed or application-managed transactions are configured with any propagation type other than REQUIRED or SUPPORTS (see the discussion on transaction propagation for details).

Enabling and disabling transactions

Annotating a test method with @Transactional causes the test to be run within a transaction that will, by default, be automatically rolled back after completion of the test. If a test class is annotated with @Transactional, each test method within that class hierarchy will be run within a transaction. Test methods that are not annotated with @Transactional (at the class or method level) will not be run within a transaction. Furthermore, tests that are annotated with @Transactional but have the propagation type set to NOT_SUPPORTED will not be run within a transaction.

Note that AbstractTransactionalJUnit4SpringContextTests and AbstractTransactionalTestNGSpringContextTests are preconfigured for transactional support at the class level.

The following example demonstrates a common scenario for writing an integration test for a Hibernate-based UserRepository. As explained in the section called “Transaction rollback and commit behavior”, there is no need to clean up the database after the createUser() method is executed since any changes made to the database will be automatically rolled back by the TransactionalTestExecutionListener. See Section 10.15.7, “PetClinic Example” for an additional example.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@Transactional
public class HibernateUserRepositoryTests {

    @Autowired
    HibernateUserRepository repository;

    @Autowired
    SessionFactory sessionFactory;

    JdbcTemplate jdbcTemplate;

    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    public void createUser() {
        // track initial state in test database:
        final int count = countRowsInTable("user");

        User user = new User(...);
        repository.save(user);

        // Manual flush is required to avoid false positive in test
        sessionFactory.getCurrentSession().flush();
        assertNumUsers(count + 1);
    }

    protected int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
    }
}
Transaction rollback and commit behavior

By default, test transactions will be automatically rolled back after completion of the test; however, transactional commit and rollback behavior can be configured declaratively via the class-level @TransactionConfiguration and method-level @Rollback annotations. See the corresponding entries in the annotation support section for further details.

Programmatic transaction management

As of Spring Framework 4.1, it is possible to interact with test-managed transactions programmatically via the static methods in TestTransaction. For example, TestTransaction may be used within test methods, before methods, and after methods to start or end the current test-managed transaction or to configure the current test-managed transaction for rollback or commit. Support for TestTransaction is automatically available whenever the TransactionalTestExecutionListener is enabled.

The following example demonstrates some of the features of TestTransaction. Consult the javadocs for TestTransaction for further details.

@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
        AbstractTransactionalJUnit4SpringContextTests {

    @Test
    public void transactionalTest() {
        // assert initial state in test database:
        assertNumUsers(2);

        deleteFromTables("user");

        // changes to the database will be committed!
        TestTransaction.flagForCommit();
        TestTransaction.end();
        assertFalse(TestTransaction.isActive());
        assertNumUsers(0);

        TestTransaction.start();
        // perform other actions against the database that will
        // be automatically rolled back after the test completes...
    }

    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
    }
}
Executing code outside of a transaction

Occasionally you need to execute certain code before or after a transactional test method but outside the transactional context — for example, to verify the initial database state prior to execution of your test or to verify expected transactional commit behavior after test execution (if the test was configured not to roll back the transaction). TransactionalTestExecutionListener supports the @BeforeTransaction and @AfterTransaction annotations exactly for such scenarios. Simply annotate any public void method in your test class with one of these annotations, and the TransactionalTestExecutionListener ensures that your before transaction method or after transaction method is executed at the appropriate time.

[Tip]Tip

Any before methods (such as methods annotated with JUnit’s @Before) and any after methods (such as methods annotated with JUnit’s @After) are executed within a transaction. In addition, methods annotated with @BeforeTransaction or @AfterTransaction are naturally not executed for test methods that are not configured to run within a transaction.

Configuring a transaction manager

TransactionalTestExecutionListener expects a PlatformTransactionManager bean to be defined in the Spring ApplicationContext for the test. In case there are multiple instances of PlatformTransactionManager within the test’s ApplicationContext, @TransactionConfiguration supports configuring the bean name of the PlatformTransactionManager that should be used to drive transactions. Alternatively, a qualifier may be declared via @Transactional("myQualifier"), or TransactionManagementConfigurer can be implemented by an @Configuration class. Consult the javadocs for TestContextTransactionUtils.retrieveTransactionManager() for details on the algorithm used to look up a transaction manager in the test’s ApplicationContext.

Demonstration of all transaction-related annotations

The following JUnit-based example displays a fictitious integration testing scenario highlighting all transaction-related annotations. The example is not intended to demonstrate best practices but rather to demonstrate how these annotations can be used. Consult the annotation support section for further information and configuration examples. Transaction management for @Sql contains an additional example using @Sql for declarative SQL script execution with default transaction rollback semantics.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
@Transactional
public class FictitiousTransactionalTest {

    @BeforeTransaction
    public void verifyInitialDatabaseState() {
        // logic to verify the initial state before a transaction is started
    }

    @Before
    public void setUpTestDataWithinTransaction() {
        // set up test data within the transaction
    }

    @Test
    // overrides the class-level defaultRollback setting
    @Rollback(true)
    public void modifyDatabaseWithinTransaction() {
        // logic which uses the test data and modifies database state
    }

    @After
    public void tearDownWithinTransaction() {
        // execute "tear down" logic within the transaction
    }

    @AfterTransaction
    public void verifyFinalDatabaseState() {
        // logic to verify the final state after transaction has rolled back
    }

}
[Note]Avoid false positives when testing ORM code

When you test application code that manipulates the state of the Hibernate session, make sure to flush the underlying session within test methods that execute that code. Failing to flush the underlying session can produce false positives: your test may pass, but the same code throws an exception in a live, production environment. In the following Hibernate-based example test case, one method demonstrates a false positive, and the other method correctly exposes the results of flushing the session. Note that this applies to JPA and any other ORM frameworks that maintain an in-memory unit of work.

// ...

@Autowired
private SessionFactory sessionFactory;

@Test // no expected exception!
public void falsePositive() {
    updateEntityInHibernateSession();
    // False positive: an exception will be thrown once the session is
    // finally flushed (i.e., in production code)
}

@Test(expected = GenericJDBCException.class)
public void updateWithSessionFlush() {
    updateEntityInHibernateSession();
    // Manual flush is required to avoid false positive in test
    sessionFactory.getCurrentSession().flush();
}

// ...

Executing SQL scripts

When writing integration tests against a relational database, it is often beneficial to execute SQL scripts to modify the database schema or insert test data into tables. The spring-jdbc module provides support for initializing an embedded or existing database by executing SQL scripts when the Spring ApplicationContext is loaded. See Section 13.8, “Embedded database support” and Section 13.8.8, “Testing data access logic with an embedded database” for details.

Although it is very useful to initialize a database for testing once when the ApplicationContext is loaded, sometimes it is essential to be able to modify the database during integration tests. The following sections explain how to execute SQL scripts programmatically and declaratively during integration tests.

Executing SQL scripts programmatically

Spring provides the following options for executing SQL scripts programmatically within integration test methods.

  • org.springframework.jdbc.datasource.init.ScriptUtils
  • org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
  • org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
  • org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests

ScriptUtils provides a collection of static utility methods for working with SQL scripts and is mainly intended for internal use within the framework. However, if you require full control over how SQL scripts are parsed and executed, ScriptUtils may suit your needs better than some of the other alternatives described below. Consult the javadocs for individual methods in ScriptUtils for further details.

ResourceDatabasePopulator provides a simple object-based API for programmatically populating, initializing, or cleaning up a database using SQL scripts defined in external resources. ResourceDatabasePopulator provides options for configuring the character encoding, statement separator, comment delimiters, and error handling flags used when parsing and executing the scripts, and each of the configuration options has a reasonable default value. Consult the javadocs for details on default values. To execute the scripts configured in a ResourceDatabasePopulator, you can invoke either the populate(Connection) method to execute the populator against a java.sql.Connection or the execute(DataSource) method to execute the populator against a javax.sql.DataSource. The following example specifies SQL scripts for a test schema and test data, sets the statement separator to "@@", and then executes the scripts against a DataSource.

@Test
public void databaseTest {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScripts(
        new ClassPathResource("test-schema.sql"),
        new ClassPathResource("test-data.sql"));
    populator.setSeparator("@@");
    populator.execute(this.dataSource);
    // execute code that uses the test schema and data
}

Note that ResourceDatabasePopulator internally delegates to ScriptUtils for parsing and executing SQL scripts. Similarly, the executeSqlScript(..) methods in AbstractTransactionalJUnit4SpringContextTests and AbstractTransactionalTestNGSpringContextTests internally use a ResourceDatabasePopulator for executing SQL scripts. Consult the javadocs for the various executeSqlScript(..) methods for further details.

Executing SQL scripts declaratively with @Sql

In addition to the aforementioned mechanisms for executing SQL scripts programmatically, SQL scripts can also be configured declaratively in the Spring TestContext Framework. Specifically, the @Sql annotation can be declared on a test class or test method to configure the resource paths to SQL scripts that should be executed against a given database either before or after an integration test method. Note that method-level declarations override class-level declarations and that support for @Sql is provided by the SqlScriptsTestExecutionListener which is enabled by default.

Path resource semantics

Each path will be interpreted as a Spring Resource. A plain path — for example, "schema.sql" — will be treated as a classpath resource that is relative to the package in which the test class is defined. A path starting with a slash will be treated as an absolute classpath resource, for example: "/org/example/schema.sql". A path which references a URL (e.g., a path prefixed with classpath:, file:, http:, etc.) will be loaded using the specified resource protocol.

The following example demonstrates how to use @Sql at the class level and at the method level within a JUnit-based integration test class.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Sql("/test-schema.sql")
public class DatabaseTests {

    @Test
    public void emptySchemaTest {
        // execute code that uses the test schema without any test data
    }

    @Test
    @Sql({"/test-schema.sql", "/test-user-data.sql"})
    public void userTest {
        // execute code that uses the test schema and test data
    }
}

Default script detection

If no SQL scripts are specified, an attempt will be made to detect a default script depending on where @Sql is declared. If a default cannot be detected, an IllegalStateException will be thrown.

  • class-level declaration: if the annotated test class is com.example.MyTest, the corresponding default script is "classpath:com/example/MyTest.sql".
  • method-level declaration: if the annotated test method is named testMethod() and is defined in the class com.example.MyTest, the corresponding default script is "classpath:com/example/MyTest.testMethod.sql".

Declaring multiple @Sql sets

If multiple sets of SQL scripts need to be configured for a given test class or test method but with different syntax configuration, different error handling rules, or different execution phases per set, it is possible to declare multiple instances of @Sql. With Java 8, @Sql can be used as a repeatable annotation. Otherwise, the @SqlGroup annotation can be used as an explicit container for declaring multiple instances of @Sql.

The following example demonstrates the use of @Sql as a repeatable annotation using Java 8. In this scenario the test-schema.sql script uses a different syntax for single-line comments.

@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
public void userTest {
    // execute code that uses the test schema and test data
}

The following example is identical to the above except that the @Sql declarations are grouped together within @SqlGroup for compatibility with Java 6 and Java 7.

@Test
@SqlGroup({
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
)}
public void userTest {
    // execute code that uses the test schema and test data
}

Script execution phases

By default, SQL scripts will be executed before the corresponding test method. However, if a particular set of scripts needs to be executed after the test method — for example, to clean up database state — the executionPhase attribute in @Sql can be used as seen in the following example. Note that ISOLATED and AFTER_TEST_METHOD are statically imported from Sql.TransactionMode and Sql.ExecutionPhase respectively.

@Test
@Sql(
    scripts = "create-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED)
)
@Sql(
    scripts = "delete-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED),
    executionPhase = AFTER_TEST_METHOD
)
public void userTest {
    // execute code that needs the test data to be committed
    // to the database outside of the test's transaction
}

Script configuration with @SqlConfig

Configuration for script parsing and error handling can be configured via the @SqlConfig annotation. When declared as a class-level annotation on an integration test class, @SqlConfig serves as global configuration for all SQL scripts within the test class hierarchy. When declared directly via the config attribute of the @Sql annotation, @SqlConfig serves as local configuration for the SQL scripts declared within the enclosing @Sql annotation. Every attribute in @SqlConfig has an implicit default value which is documented in the javadocs of the corresponding attribute. Due to the rules defined for annotation attributes in the Java Language Specification, it is unfortunately not possible to assign a value of null to an annotation attribute. Thus, in order to support overrides of inherited global configuration, @SqlConfig attributes have an explicit default value of either "" for Strings or DEFAULT for Enums. This approach allows local declarations of @SqlConfig to selectively override individual attributes from global declarations of @SqlConfig by providing a value other than "" or DEFAULT. Global @SqlConfig attributes are inherited whenever local @SqlConfig attributes do not supply an explicit value other than "" or DEFAULT. Explicit local configuration therefore overrides global configuration.

The configuration options provided by @Sql and @SqlConfig are equivalent to those supported by ScriptUtils and ResourceDatabasePopulator but are a superset of those provided by the <jdbc:initialize-database/> XML namespace element. Consult the javadocs of individual attributes in @Sql and @SqlConfig for details.

Transaction management for @Sql

By default, the SqlScriptsTestExecutionListener will infer the desired transaction semantics for scripts configured via @Sql. Specifically, SQL scripts will be executed without a transaction, within an existing Spring-managed transaction — for example, a transaction managed by the TransactionalTestExecutionListener for a test annotated with @Transactional — or within an isolated transaction, depending on the configured value of the transactionMode attribute in @SqlConfig and the presence of a PlatformTransactionManager in the test’s ApplicationContext. As a bare minimum however, a javax.sql.DataSource must be present in the test’s ApplicationContext.

If the algorithms used by SqlScriptsTestExecutionListener to detect a DataSource and PlatformTransactionManager and infer the transaction semantics do not suit your needs, you may specify explicit names via the dataSource and transactionManager attributes of @SqlConfig. Furthermore, the transaction propagation behavior can be controlled via the transactionMode attribute of @SqlConfig — for example, if scripts should be executed in an isolated transaction. Although a thorough discussion of all supported options for transaction management with @Sql is beyond the scope of this reference manual, the javadocs for @SqlConfig and SqlScriptsTestExecutionListener provide detailed information, and the following example demonstrates a typical testing scenario using JUnit and transactional tests with @Sql. Note that there is no need to clean up the database after the usersTest() method is executed since any changes made to the database (either within the the test method or within the /test-data.sql script) will be automatically rolled back by the TransactionalTestExecutionListener (see transaction management for details).

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestDatabaseConfig.class)
@Transactional
public class TransactionalSqlScriptsTests {

    protected JdbcTemplate jdbcTemplate;

    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    @Sql("/test-data.sql")
    public void usersTest() {
        // verify state in test database:
        assertNumUsers(2);
        // execute code that uses the test data...
    }

    protected int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
    }
}

TestContext Framework support classes

JUnit support classes

The org.springframework.test.context.junit4 package provides the following support classes for JUnit-based test cases.

  • AbstractJUnit4SpringContextTests
  • AbstractTransactionalJUnit4SpringContextTests

AbstractJUnit4SpringContextTests is an abstract base test class that integrates the Spring TestContext Framework with explicit ApplicationContext testing support in a JUnit 4.9+ environment. When you extend AbstractJUnit4SpringContextTests, you can access a protected applicationContext instance variable that can be used to perform explicit bean lookups or to test the state of the context as a whole.

AbstractTransactionalJUnit4SpringContextTests is an abstract transactional extension of AbstractJUnit4SpringContextTests that adds some convenience functionality for JDBC access. This class expects a javax.sql.DataSource bean and a PlatformTransactionManager bean to be defined in the ApplicationContext. When you extend AbstractTransactionalJUnit4SpringContextTests you can access a protected jdbcTemplate instance variable that can be used to execute SQL statements to query the database. Such queries can be used to confirm database state both prior to and after execution of database-related application code, and Spring ensures that such queries run in the scope of the same transaction as the application code. When used in conjunction with an ORM tool, be sure to avoid false positives. As mentioned in Section 10.15.3, “JDBC 测试支持”, AbstractTransactionalJUnit4SpringContextTests also provides convenience methods which delegate to methods in JdbcTestUtils using the aforementioned jdbcTemplate. Furthermore, AbstractTransactionalJUnit4SpringContextTests provides an executeSqlScript(..) method for executing SQL scripts against the configured DataSource.

[Tip]Tip

These classes are a convenience for extension. If you do not want your test classes to be tied to a Spring-specific class hierarchy, you can configure your own custom test classes by using @RunWith(SpringJUnit4ClassRunner.class), @ContextConfiguration, @TestExecutionListeners, and so on.

Spring JUnit Runner

The Spring TestContext Framework offers full integration with JUnit 4.9+ through a custom runner (tested on JUnit 4.9 — 4.11). By annotating test classes with @RunWith(SpringJUnit4ClassRunner.class), developers can implement standard JUnit-based unit and integration tests and simultaneously reap the benefits of the TestContext framework such as support for loading application contexts, dependency injection of test instances, transactional test method execution, and so on. The following code listing displays the minimal requirements for configuring a test class to run with the custom Spring Runner. @TestExecutionListeners is configured with an empty list in order to disable the default listeners, which otherwise would require an ApplicationContext to be configured through @ContextConfiguration.

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({})
public class SimpleTest {

    @Test
    public void testMethod() {
        // execute test logic...
    }
}
TestNG support classes

The org.springframework.test.context.testng package provides the following support classes for TestNG based test cases.

  • AbstractTestNGSpringContextTests
  • AbstractTransactionalTestNGSpringContextTests

AbstractTestNGSpringContextTests is an abstract base test class that integrates the Spring TestContext Framework with explicit ApplicationContext testing support in a TestNG environment. When you extend AbstractTestNGSpringContextTests, you can access a protected applicationContext instance variable that can be used to perform explicit bean lookups or to test the state of the context as a whole.

AbstractTransactionalTestNGSpringContextTests is an abstract transactional extension of AbstractTestNGSpringContextTests that adds some convenience functionality for JDBC access. This class expects a javax.sql.DataSource bean and a PlatformTransactionManager bean to be defined in the ApplicationContext. When you extend AbstractTransactionalTestNGSpringContextTests you can access a protected jdbcTemplate instance variable that can be used to execute SQL statements to query the database. Such queries can be used to confirm database state both prior to and after execution of database-related application code, and Spring ensures that such queries run in the scope of the same transaction as the application code. When used in conjunction with an ORM tool, be sure to avoid false positives. As mentioned in Section 10.15.3, “JDBC 测试支持”, AbstractTransactionalTestNGSpringContextTests also provides convenience methods which delegate to methods in JdbcTestUtils using the aforementioned jdbcTemplate. Furthermore, AbstractTransactionalTestNGSpringContextTests provides an executeSqlScript(..) method for executing SQL scripts against the configured DataSource.

[Tip]Tip

These classes are a convenience for extension. If you do not want your test classes to be tied to a Spring-specific class hierarchy, you can configure your own custom test classes by using @ContextConfiguration, @TestExecutionListeners, and so on, and by manually instrumenting your test class with a TestContextManager. See the source code of AbstractTestNGSpringContextTests for an example of how to instrument your test class.

10.15.6 Spring MVC Test Framework

The Spring MVC Test framework provides first class JUnit support for testing client and server-side Spring MVC code through a fluent API. Typically it loads the actual Spring configuration through the TestContext framework and always uses the DispatcherServlet to process requests thus approximating full integration tests without requiring a running Servlet container.

Client-side tests are RestTemplate-based and allow tests for code that relies on the RestTemplate without requiring a running server to respond to the requests.

Server-Side Tests

Before Spring Framework 3.2, the most likely way to test a Spring MVC controller was to write a unit test that instantiates the controller, injects it with mock or stub dependencies, and then calls its methods directly, using a MockHttpServletRequest and MockHttpServletResponse where necessary.

Although this is pretty easy to do, controllers have many annotations, and much remains untested. Request mappings, data binding, type conversion, and validation are just a few examples of what isn’t tested. Furthermore, there are other types of annotated methods such as @InitBinder, @ModelAttribute, and @ExceptionHandler that get invoked as part of request processing.

The idea behind Spring MVC Test is to be able to re-write those controller tests by performing actual requests and generating responses, as they would be at runtime, along the way invoking controllers through the Spring MVC DispatcherServlet. Controllers can still be injected with mock dependencies, so tests can remain focused on the web layer.

Spring MVC Test builds on the familiar "mock" implementations of the Servlet API available in the spring-test module. This allows performing requests and generating responses without the need for running in a Servlet container. For the most part everything should work as it does at runtime with the exception of JSP rendering, which is not available outside a Servlet container. Furthermore, if you are familiar with how the MockHttpServletResponse works, you’ll know that forwards and redirects are not actually executed. Instead "forwarded" and "redirected" URLs are saved and can be asserted in tests. This means if you are using JSPs, you can verify the JSP page to which the request was forwarded.

All other means of rendering including @ResponseBody methods and View types (besides JSPs) such as Freemarker, Velocity, Thymeleaf, and others for rendering HTML, JSON, XML, and so on should work as expected, and the response will contain the generated content.

Below is an example of a test requesting account information in JSON format:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-servlet-context.xml")
public class ExampleTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    @Test
    public void getAccount() throws Exception {
        this.mockMvc.perform(get("/accounts/1").accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json"))
            .andExpect(jsonPath("$.name").value("Lee"));
    }

}

The test relies on the WebApplicationContext support of the TestContext framework. It loads Spring configuration from an XML configuration file located in the same package as the test class (also supports JavaConfig) and injects the created WebApplicationContext into the test so a MockMvc instance can be created with it.

The MockMvc is then used to perform a request to "/accounts/1" and verify the resulting response status is 200, the response content type is "application/json", and response content has a JSON property called "name" with the value "Lee". JSON content is inspected with the help of Jayway’s JsonPath project. There are lots of other options for verifying the result of the performed request and those will be discussed later.

Static Imports

The fluent API in the example above requires a few static imports such as MockMvcRequestBuilders.*, MockMvcResultMatchers.*, and MockMvcBuilders.*. An easy way to find these classes is to search for types matching "MockMvc*". If using Eclipse, be sure to add them as "favorite static members" in the Eclipse preferences underJava → Editor → Content Assist → Favorites. That will allow use of content assist after typing the first character of the static method name. Other IDEs (e.g. IntelliJ) may not require any additional configuration. Just check the support for code completion on static members.

Setup Options

The goal of server-side test setup is to create an instance of MockMvc that can be used to perform requests. There are two main options.

The first option is to point to Spring MVC configuration through the TestContext framework, which loads the Spring configuration and injects a WebApplicationContext into the test to use to create a MockMvc:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("my-servlet-context.xml")
public class MyWebTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    // ...

}

The second option is to simply register a controller instance without loading any Spring configuration. Instead basic Spring MVC configuration suitable for testing annotated controllers is automatically created. The created configuration is comparable to that of the MVC JavaConfig (and the MVC namespace) and can be customized to a degree through builder-style methods:

public class MyWebTests {

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
    }

    // ...

}

Which option should you use?

The "webAppContextSetup" loads the actual Spring MVC configuration resulting in a more complete integration test. Since the TestContext framework caches the loaded Spring configuration, it helps to keep tests running fast even as more tests get added. Furthermore, you can inject mock services into controllers through Spring configuration, in order to remain focused on testing the web layer. Here is an example of declaring a mock service with Mockito:

<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="org.example.AccountService"/>
</bean>

Then you can inject the mock service into the test in order set up and verify expectations:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-servlet-context.xml")
public class AccountTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Autowired
    private AccountService accountService;

    // ...

}

The "standaloneSetup" on the other hand is a little closer to a unit test. It tests one controller at a time, the controller can be injected with mock dependencies manually, and it doesn’t involve loading Spring configuration. Such tests are more focused in style and make it easier to see which controller is being tested, whether any specific Spring MVC configuration is required to work, and so on. The "standaloneSetup" is also a very convenient way to write ad-hoc tests to verify some behavior or to debug an issue.

Just like with integration vs unit testing, there is no right or wrong answer. Using the "standaloneSetup" does imply the need for some additional "webAppContextSetup" tests to verify the Spring MVC configuration. Alternatively, you can decide write all tests with "webAppContextSetup" and always test against actual Spring MVC configuration.

Performing Requests

To perform requests, use the appropriate HTTP method and additional builder-style methods corresponding to properties of MockHttpServletRequest. For example:

mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));

In addition to all the HTTP methods, you can also perform file upload requests, which internally creates an instance of MockMultipartHttpServletRequest:

mockMvc.perform(fileUpload("/doc").file("a1", "ABC".getBytes("UTF-8")));

Query string parameters can be specified in the URI template:

mockMvc.perform(get("/hotels?foo={foo}", "bar"));

Or by adding Servlet request parameters:

mockMvc.perform(get("/hotels").param("foo", "bar"));

If application code relies on Servlet request parameters, and doesn’t check the query string, as is most often the case, then it doesn’t matter how parameters are added. Keep in mind though that parameters provided in the URI template will be decoded while parameters provided through the param(...) method are expected to be decoded.

In most cases it’s preferable to leave out the context path and the Servlet path from the request URI. If you must test with the full request URI, be sure to set the contextPath and servletPath accordingly so that request mappings will work:

mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))

Looking at the above example, it would be cumbersome to set the contextPath and servletPath with every performed request. That’s why you can define default request properties when building the MockMvc:

public class MyWebTests {

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = standaloneSetup(new AccountController())
            .defaultRequest(get("/")
            .contextPath("/app").servletPath("/main")
            .accept(MediaType.APPLICATION_JSON).build();
    }

The above properties will apply to every request performed through the MockMvc. If the same property is also specified on a given request, it will override the default value. That is why, the HTTP method and URI don’t matter, when setting default request properties, since they must be specified on every request.

Defining Expectations

Expectations can be defined by appending one or more .andExpect(..) after call to perform the request:

mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());

MockMvcResultMatchers.* defines a number of static members, some of which return types with additional methods, for asserting the result of the performed request. The assertions fall in two general categories.

The first category of assertions verify properties of the response, i.e the response status, headers, and content. Those are the most important things to test for.

The second category of assertions go beyond the response, and allow inspecting Spring MVC specific constructs such as which controller method processed the request, whether an exception was raised and handled, what the content of the model is, what view was selected, what flash attributes were added, and so on. It is also possible to verify Servlet specific constructs such as request and session attributes. The following test asserts that binding/validation failed:

mockMvc.perform(post("/persons"))
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));

Many times when writing tests, it’s useful to dump the result of the performed request. This can be done as follows, where print() is a static import from MockMvcResultHandlers:

mockMvc.perform(post("/persons"))
    .andDo(print())
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));

As long as request processing causes an unhandled exception, the print() method will print all the available result data to System.out.

In some cases, you may want to get direct access to the result and verify something that cannot be verified otherwise. This can be done by appending .andReturn() at the end after all expectations:

MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...

When all tests repeat the same expectations, you can define the common expectations once when building the MockMvc:

standaloneSetup(new SimpleController())
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build()

Note that the expectation is always applied and cannot be overridden without creating a separate MockMvc instance.

When JSON response content contains hypermedia links created with Spring HATEOAS, the resulting links can be verified:

mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON))
    .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));

When XML response content contains hypermedia links created with Spring HATEOAS, the resulting links can be verified:

Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
    .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
Filter Registrations

When setting up a MockMvc, you can register one or more Filter instances:

mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();

Registered filters will be invoked through MockFilterChain from spring-test and the last filter will delegates to the DispatcherServlet.

Further Server-Side Test Examples

The framework’s own tests include many sample tests intended to demonstrate how to use Spring MVC Test. Browse these examples for further ideas. Also the spring-mvc-showcase has full test coverage based on Spring MVC Test.

Client-Side REST Tests

Client-side tests are for code using the RestTemplate. The goal is to define expected requests and provide "stub" responses:

RestTemplate restTemplate = new RestTemplate();

MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess("Hello world", MediaType.TEXT_PLAIN));

// use RestTemplate ...

mockServer.verify();

In the above example, MockRestServiceServer — the central class for client-side REST tests — configures the RestTemplate with a custom ClientHttpRequestFactory that asserts actual requests against expectations and returns "stub" responses. In this case we expect a single request to "/greeting" and want to return a 200 response with "text/plain" content. We could define as many additional requests and stub responses as necessary.

Once expected requests and stub responses have been defined, the RestTemplate can be used in client-side code as usual. At the end of the tests mockServer.verify() can be used to verify that all expected requests were performed.

Static Imports

Just like with server-side tests, the fluent API for client-side tests requires a few static imports. Those are easy to find by searching "MockRest*". Eclipse users should add "MockRestRequestMatchers.*" and "MockRestResponseCreators.*" as "favorite static members" in the Eclipse preferences under Java → Editor → Content Assist → Favorites. That allows using content assist after typing the first character of the static method name. Other IDEs (e.g. IntelliJ) may not require any additional configuration. Just check the support for code completion on static members.

Further Examples of Client-side REST Tests

Spring MVC Test’s own tests include example tests of client-side REST tests.

10.15.7 PetClinic Example

The PetClinic application, available on GitHub, illustrates several features of the Spring TestContext Framework in a JUnit environment. Most test functionality is included in the AbstractClinicTests, for which a partial listing is shown below:

import static org.junit.Assert.assertEquals;
// import ...

@ContextConfiguration
public abstract class AbstractClinicTests extends AbstractTransactionalJUnit4SpringContextTests {

    @Autowired
    protected Clinic clinic;

    @Test
    public void getVets() {
        Collection<Vet> vets = this.clinic.getVets();
        assertEquals("JDBC query must show the same number of vets",
            super.countRowsInTable("VETS"), vets.size());
        Vet v1 = EntityUtils.getById(vets, Vet.class, 2);
        assertEquals("Leary", v1.getLastName());
        assertEquals(1, v1.getNrOfSpecialties());
        assertEquals("radiology", (v1.getSpecialties().get(0)).getName());
        // ...
    }

    // ...
}

Notes:

  • This test case extends the AbstractTransactionalJUnit4SpringContextTests class, from which it inherits configuration for Dependency Injection (through the DependencyInjectionTestExecutionListener) and transactional behavior (through the TransactionalTestExecutionListener).
  • The clinic instance variable — the application object being tested — is set by Dependency Injection through @Autowired semantics.
  • The getVets() method illustrates how you can use the inherited countRowsInTable() method to easily verify the number of rows in a given table, thus verifying correct behavior of the application code being tested. This allows for stronger tests and lessens dependency on the exact test data. For example, you can add additional rows in the database without breaking tests.
  • Like many integration tests that use a database, most of the tests in AbstractClinicTests depend on a minimum amount of data already in the database before the test cases run. Alternatively, you might choose to populate the database within the test fixture set up of your test cases — again, within the same transaction as the tests.

The PetClinic application supports three data access technologies: JDBC, Hibernate, and JPA. By declaring @ContextConfiguration without any specific resource locations, the AbstractClinicTests class will have its application context loaded from the default location, AbstractClinicTests-context.xml, which declares a common DataSource. Subclasses specify additional context locations that must declare a PlatformTransactionManager and a concrete implementation of Clinic.

For example, the Hibernate implementation of the PetClinic tests contains the following implementation. For this example, HibernateClinicTests does not contain a single line of code: we only need to declare @ContextConfiguration, and the tests are inherited from AbstractClinicTests. Because @ContextConfiguration is declared without any specific resource locations, the Spring TestContext Framework loads an application context from all the beans defined in AbstractClinicTests-context.xml (i.e., the inherited locations) and HibernateClinicTests-context.xml, with HibernateClinicTests-context.xml possibly overriding beans defined in AbstractClinicTests-context.xml.

@ContextConfiguration
public class HibernateClinicTests extends AbstractClinicTests { }

In a large-scale application, the Spring configuration is often split across multiple files. Consequently, configuration locations are typically specified in a common base class for all application-specific integration tests. Such a base class may also add useful instance variables — populated by Dependency Injection, naturally — such as a SessionFactory in the case of an application using Hibernate.

As far as possible, you should have exactly the same Spring configuration files in your integration tests as in the deployed environment. One likely point of difference concerns database connection pooling and transaction infrastructure. If you are deploying to a full-blown application server, you will probably use its connection pool (available through JNDI) and JTA implementation. Thus in production you will use a JndiObjectFactoryBean or <jee:jndi-lookup> for the DataSource and JtaTransactionManager. JNDI and JTA will not be available in out-of-container integration tests, so you should use a combination like the Commons DBCP BasicDataSource and DataSourceTransactionManager or HibernateTransactionManager for them. You can factor out this variant behavior into a single XML file, having the choice between application server and a local configuration separated from all other configuration, which will not vary between the test and production environments. In addition, it is advisable to use properties files for connection settings. See the PetClinic application for an example.

10.16 Further Resources

Consult the following resources for more information about testing:

  • JUnit: "A programmer-oriented testing framework for Java". Used by the Spring Framework in its test suite.
  • TestNG: A testing framework inspired by JUnit with added support for annotations, test groups, data-driven testing, distributed testing, etc.
  • MockObjects.com: Web site dedicated to mock objects, a technique for improving the design of code within test-driven development.
  • "Mock Objects": Article in Wikipedia.
  • EasyMock: Java library " that provides Mock Objects for interfaces (and objects through the class extension) by generating them on the fly using Java’s proxy mechanism. " Used by the Spring Framework in its test suite.
  • JMock: Library that supports test-driven development of Java code with mock objects.
  • Mockito: Java mock library based on the test spy pattern.
  • DbUnit: JUnit extension (also usable with Ant and Maven) targeted for database-driven projects that, among other things, puts your database into a known state between test runs.
  • The Grinder: Java load testing framework.