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.
Let’s look at how Spring handles the crucial pointcut concept.
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 | |
---|---|
If possible, try to make pointcuts static, allowing the AOP framework to cache the results of pointcut evaluation when an AOP proxy is created. |
Spring supports operations on pointcuts: notably, union and intersection.
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.
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 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.
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.
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.
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 | |
---|---|
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. |
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.
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 | |
---|---|
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." |
Let’s now look at how Spring AOP handles advice. 让我们看看 Spring AOP 是怎么处理 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
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类型
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 | |
---|---|
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号开始
前置 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 | |
---|---|
Before advice can be used with any pointcut. 前置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个方法可以用到一个类中,处理RemoteException
和 ServletException
。
任何数目的 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 | |
---|---|
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 | |
---|---|
Throws advice can be used with any pointcut. Throws 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 | |
---|---|
After returning advice can be used with any pointcut. 后置advice 可以被用在任何的切入点上 |
7月8号开始
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:
介绍需要IntroductionAdvisor
和 IntroductionInterceptor
,需要实现以下接口:
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.
在Spring中, 一个通知器就是一个包含通知和切入点表达式的切面.
除了一些特殊的情况, 任意的通知可以应用于任何的通知器.
org.springframework.aop.support.DefaultPointcutAdvisor
是最常用的通知器类.
例如, 它可以被用于 MethodInterceptor
, BeforeAdvice
or
ThrowsAdvice
.
在Spring的相同的AOP代理中,通知和通知器混用是可行. 例如, 在一个代理配置中你 可以使用环绕通知,异常通知和前置通知: Spring将会自动创建拦截器链.
如果你正在使用 Spring IoC 容器(ApplicationContext或者BeanFactory) 管理你的业务对象 - 你会想要使用Spring的AOP FactoryBeans. (请记住一个factory bean是为了引进一种分层的 机制,可以创建不同类型的对象.)
Note | |
---|---|
Spring AOP支持使用factory beans避免被覆盖. |
在Spring当中创建代理的最好的方式是使用org.springframework.aop.framework.ProxyFactoryBean. 对于切入点和通知的应用和顺序,它提供了完成的控制. 然后, 当你需要太多的控制时,更好的方式是通过使用简单的选项.
ProxyFactoryBean
的实现就像其他的Spring的FactoryBean
, 起到了分层的作用.
如果你定义了一个名为foo
的ProxyFactoryBean
, 当引用了foo
看到不会是ProxyFactoryBean
的实例,
而是通过ProxyFactoryBean
实现的getObject()
方法创建的对象.这个方法将会一个AOP的代理包装在目标类上.
使用ProxyFactoryBean
或者其他IoC-aware类来创建AOP代理,其中一个很大的好处就是通知和切入点将会
被IoC容器管理. 这是一个很有用特性,当它开启时可以很好的确保不被其他的AOP框架所获得.
例如, 一个通知可能引用到应用内的对象 (除了目标类之外, 这些都是可以在任何AOP框架内获得的),
有利的是通过依赖注入,这些都是可插拔的.
通常情况下,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”.
getObject()
的方法有多频繁。默认值是true
.
你想要使用普通状态的对象,可以配置成false
.
这个章节是关于 ProxyFactoryBean
最后的文档,提供了说明对于特别类是要是要使用基于类的代理还是JDK动态代理.
Note | |
---|---|
|
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.
Let’s look at a simple example of ProxyFactoryBean
in action. This example involves:
<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 | |
---|---|
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.
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’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.
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"/>
特别是在定义事务代理的时候, 你有可能最后都会有许多相似的代理定义. 使用父子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, 否则应用上下文将会尝试 预先实例化它.
在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 | |
---|---|
在IoC框架中集成AOP代理的创建在大多数应用中是最好的选择. 我们建议你在一般情况下使用AOP的Java代码来 完成具体的配置. |
当你创建了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 | |
---|---|
对业务对象修改通知器是否是明智的(没有双关语意), 即使那是没有疑问的合法使用, 这是富有争议的. 然而, 它在 开发中是很有用的: 例如, 在测试中. 我有时候发觉它是很有用的用来向拦截器的表单里面添加代码或者其他通知器, 使得进入我想测试的方法调用. (例如, 通知器可以进入一个方法创建好的事务中: 例如, 在标记事务回滚之前, 运行 一个SQL来检查数据库已经被正确的更新了.) |
取决于你是怎样创建的代理, 你通常都可以设置一个frozen
标志位, 这样Advised
isFrozen()
方法都将
返回true, 并且任何试图通过添加或者移除修改通知器都将产生AopConfigException
. 一个advised对象的封冻
状态的能力在某些情况下是很有用的, 例如, 阻止调用代码来移除安全拦截器. 它也可以被用于Spring 1.1中来允许
积极的优化如果运行时的通知器修改是已知的不需要的.
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:
The org.springframework.aop.framework.autoproxy
package provides the following
standard auto-proxy creators.
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.
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:
DefaultAdvisorAutoProxyCreator
bean definition.
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.
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
.
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 | |
---|---|
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:
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.
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 | |
---|---|
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. |
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.
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 | |
---|---|
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.
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.
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 | |
---|---|
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 |
Spring AOP 是设计为可扩展的。虽然拦截实现策略目前在内部使用,但是它可以支持除开箱即用的环绕通知、前置通知、异常通知和后置通知外的任意通知类型。
org.springframework.aop.framework.adapter
包是一个SPI包,它支持允许在不改变核心框架的情况下添加新的通知类型。定义Advice
类型的唯一限制是必须继承org.aopalliance.aop.Advice
标记接口。
请参考org.springframework.aop.framework.adapter
文档了解更多信息。
请参考Spring示例应用中更多关于Spring AOP的例子:
TransactionProxyFactoryBean
用于声明式事务管理。
/attributes
目录说明了如何使用属性驱动的声明式事务管理。
== 测试
相比在传统Java EE下开发,依赖注入使你的代码更少的依赖容器。通过简单的new
操作,构成你应用
的POJOs对象即可在JUnit或TestNG下测试。 即使是没有Spring或者其他容器,你可以使用 模拟对象
(联合其他有价值的测试技术) 独立测试你的代码。 如果你遵循了Spring的架构建议, 清晰的分层和组件化的代码将会促进
单元测试的简化。例如,当运行单元测试的时候,你可以通过存根代码、模拟DAO或者是资源库接口测试服务层对象而不用访问
持久层数据。
即使没有运行时基础设施设置,真正的单元测试典型的要求是快速地运行。强调真正的单元测试是你开发方法论 中的一部分将会提高生产率。对你的Ioc-based应用,你可能不需要测试章节来帮你写出高效的单元测试。 尽管如此,Spring框架为单元测试场景提供了下列模拟对象和测试支持类。
org.springframework.mock.env
包包含了Environment
和 PropertySource
(查看 Section 5.13.1, “Bean定义配置文件”
和 Section 5.13.3, “PropertySource的抽象”)抽象的模拟实现。 MockEnvironment
和MockPropertySource
对out-of-container代码开发的测试是非常有用的,因为这些代码往往依赖于特定环境的属性。
org.springframework.mock.jndi
包包含了JNDI SPI的模拟实现, 你可以使用它为测试套件或者独立的应用设置一个
简单的JNDI环境。比如,在测试代码和在Java EE容器中一样对JDBC DataSource
s 绑定相同名字的JNDI,这样,你就
可以在测试场景中复用应用配置而不用修改。
org.springframework.mock.web
包包含了Servlet API模拟对象的综合设置,定位是在Spring MVC框架中对Web上下文
和控制器进行测试。这些模拟对象的使用比类似 EasyMock或现存的Servlet API模拟对象如
MockObjects动态的模拟对象更方便。
org.springframework.test.util
包包含 ReflectionTestUtils
类, 这个类提供了一套基于反射的工具方法。
当测试应用代码时开发人员在单元或者是集成测试场景中使用这些方法设置一个非public
的子段或调用一个非public
的设值方法
例如:
public`setter方法不同,类似JPA和Hibernate的ORM框架放任对`private
或protected
子段
的访问。
@Autowired
, @Inject
, and @Resource,
它为 private
or protected
子段提供了依赖注入, setter方法和configuration方法。
org.springframework.test.web
包包含ModelAndViewAssert
对象, 使用它你可以组合JUnit,TestNG或者任意其他的
单元测试框架为你的Spring MVC`ModelAndView`对象进行单元测试。
Spring MVC Controllers 单元测试 | |
---|---|
为了测试你的 Spring MVC 注意: 目前,在 Spring 4.0中, 包 |
不需要发布(程序)到你的应用服务器或者连接到其他企业设施的情况下能够执行一些集成测试是非常重要的。 这能使你测试下述情况:
在spring-test
模块中,Spring框架为集成测试提供了第一等级的支持。实际的Jar包的名称包括发布版本的
名称,也可能包括长包名org.springframework.test
的形式。这依赖你获取的来源(参阅依赖管理章节解释 )。这个库包括org.springframework.test
包,它为在Sring容器中进行集成测试提供
一些有价值的类。这些测试不依赖应用服务器或者其他发布环境。这些测试比单元测试要慢,但是要比等价的分析测试
或者是依赖应用服务器发布的远程测试要快。
在Spring 2.5及以后的版本中已经提供了注解驱动Spring 测试上下文框架的 单元测试和集成测试。由于Spring 测试上下文框架对实际使用的测试框架是不可知的,所以允许使用各种测试设施, 如JUnit,TestNG等等。
Srping的集成测试支持有如下的主要目标:
后续的章节描述每一个目标并提供实现和配置详情的链接。
Spring TestContext 框架对ApplicationContext
s 和WebApplicationContext
s以及这些
上下文的缓存提供了一致的加载。对加载的上下文进行缓存是重要的,因为启动时间可能会成为一个问题,—— 并非
因为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管理的对象图,事务代理, DataSource
s,等),因此可以避免为独立的测试用例重复地设置复杂的测试
夹具。
作为一个示例,考虑如下场景:我们有一个类, HibernateTitleRepository
为领域实体 Title
实现了数据访问
逻辑。我们想写一个集成测试,测试如下情况:
HibernateTitleRepository
bean配置相关所有东西的正确性 ?
HibernateTitleRepository
对象的逻辑:配置的这个类的实例是否达到了预期?
参阅在TestContext framework中使用测试夹具的依赖注入。
测试中一个通常的问题是访问真实的数据库对持久化存储状态的影响。甚至你使用开发数据库,状态的变化可能影响将来的测试。 另外,许多操作,如插入或修改持久化数据——不能在事务外执行(或验证)。
TestContext 框架定位了这个问题。默认情况下,框架将对每一个测试创建并回滚一个事务。你可以假设事务的存在,
而简单地写代码。在你的测试中,如果调用了一个事务性的代理对象,根据对它们的事务语义配置,它们将会行为正确地执行。
另外,在测试中,当运行在受管事务中时,如果一个方法删除了选定表的内容,默认情况下,事务将会回滚,数据库将会返回到
执行测试前的状态。在测试中事务的支持是通过在应用上下文中定一个bean PlatformTransactionManager
。
如果你想提交一个事务——极少出现但偶有发生,当你要一个特定的测试来迁移或修改数据库——TestContext框架可以通过注解
@TransactionConfiguration
和
@Rollback
提示进行事务提交而不是事务回滚。
参阅使用TestContext 框架进行事务管理。
Spring TestContext 框架为简化集成测试的编写提供了几个 abstract
支持类。这些基础类提供了定义良好的钩子以及
便利的实例变量和方法,以便你能够访问:
ApplicationContext
对象,为了显式bean的查找以及整个上下文状态的测试。
JdbcTemplate
对象,为了执行SQL语句查询数据库。这些查询,可以用来确认数据库相关的代码执行 前后 数据库状态。
Spring 还保证这些代码与应用代码运行在相同的事务方位内。当联合使用ORM工具使用时,确保避免false positives
问题。
另外,你可能想创建自定义的,应用范围的超类。这些超类拥有实例特定于你的项目的变量和方法。
请参阅TestContext 框架支持类。
org.springframework.test.jdbc
包包含了 JdbcTestUtils
工具类,它是个JDBC相关的功能工具集,主要用来
应对简化标准化数据库的测试场景。 JdbcTestUtils
工具类提供了下列静态工具方法。
countRowsInTable(..)
: 对指定的表进行行计数
countRowsInTableWhere(..)
: 使用提供的WHERE
条件对指定的表进行行计数
deleteFromTables(..)
: 对指定的表删除所有的行
deleteFromTableWhere(..)
: 使用提供的WHERE
条件对指定的表删除记录
dropTables(..)
: 删除指定的表
注意,AbstractTransactionalJUnit4SpringContextTests
和AbstractTransactionalTestNGSpringContextTests
中提供
的便利方法是委托给了前述 JdbcTestUtils
工具类中的方法。
spring-jdbc
模块提供了对配置和启动一个嵌入式数据库的支持,它可以用来在集成测试用与数据库进行交互。详情
请参阅Section 13.8, “Embedded database support” 和 Section 13.8.8, “Testing data access logic with an embedded database”相关章节。
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 | |
---|---|
|
参阅 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
一个类级别的注解,用来为集成测试定义 ApplicationContext
s 的层次。 @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... }
如果你需要在一个测试类层次中合并或覆盖指定的上下文层次的级别,在每一个类层次的相关级别中,你必须通过为
@ContextConfiguration
的 name
属性提供相同的值显式命名那个级别。请参考 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 | |
---|---|
默认情况下, |
参考 the section called “使用环境概要进行上下文配置” 和 @ActiveProfiles
javadocs了解示例和
进一步的详情。
@TestPropertySource
一个类级别的注解,用来配置属性文件和内联属性的位置,在集成测试的ApplicationContext
对象加载的时候,
这些属性要被加到 PropertySources
的 Environment
的设置中。
测试属性源比操作系统环境加载的属性、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 的算法清理
当前测试中通用的上下文缓存,不仅包括当前级别而且包括所有其他的共享一个祖先的上下文层次。所有的住留在通用祖先上下
文的子层次中的 ApplicationContext
s 将从缓存中清除并关闭。 如果 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() { // 一些导致子上下文变脏的逻辑 } }
进一步了解 EXHAUSTIVE
和 CURRENT_LEVEL
算法的详情,清参考 DirtiesContext.HierarchyMode
javadocs。
@TestExecutionListeners
定义类级别的元数据配置, 在 TestContextManager
中注册 TestExecutionListener
s 。 典型情况下,
@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 | |
---|---|
如果默认的传统对你的测试配置够用的话,你可以避免 与 |
@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
JSR-250 声明周期注解 | |
---|---|
在Spring TestContext框架中, 如果测试类中的一个方法增加了 |
下述的注解 仅仅 支持联合 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 values 的 ProfileValueSource
的类型,这些 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 { }
Spring TestContext Framework (在 org.springframework.test.context
包中) 提供了通用的,
注解驱动的单元测试和集成测试支持,而不限定这些测试所使用的具体的测试套件。 Spring TestContext Framework
尤其重视 惯例配置 ,于是放置了大量的默认配置,你也可以基于注解的配置对默认值进行覆盖。
除了通用的测试设施,Spring TestContext framework 以 抽象
支持类的形式对JUnit和TestNG提供了直接的
支持。测试框架对JUnit提供了一个用户自定义的JUnit Runner
,这个运行器是一个被称为 POJO的测试类 ,
它不需要继承一个特定的类层次。
接下来的章节将提供TestContext framework的概览。如果你仅仅对如何使用这个框架感兴趣,而对如何使用自定义的 监听器或加载器扩展它,那么,请直接参考配置(上下文管理, 依赖注入,事务管理),支持类 和 注解支持 章节。
测试框架的核心由 TestContext
和 TestContextManager
类以及 TestExecutionListener
, ContextLoader
, 和
SmartContextLoader
接口组成。 TestContextManager
实例在每一个基本测试单元(如JUnit中的测试方法执行时)
执行时被创建。TestContextManager
对象用来管理持有当前测试上下文的TestContext
实例,它也更新 TestContext
类的
状态来指示测试进度,并且它将依赖注入,事务管理等功能委托给TestExecutionListener
s类进行处理。ContextLoader
类负责对指定的测试类加载 ApplicationContext
实例。请参考javadocs文件来了解进一步的信息和各种示例的实现。
TestContext
: 测试执行时为测试实例封装测试上下文,对实际使用的测试框架透明,提供上下文管理和缓存支持
同时在必要时为加载 ApplicationContext
实例委托于 ContextLoader
类(或 SmartContextLoader
类)。
TestContextManager
: Spring TestContext Framework 的主要入口点,在良好定义的测试执行点
管理单个 TestContext
和所有的已注册的 TestExecutionListener
s类:
TestExecutionListener
: 为 TestContextManager
对象发布的测试执行事件定义一个 listener API
,查看 the section called “TestExecutionListener 配置” 。
ContextLoader
: 在Spring 2.5中引入的策略接口,用来为Spring TestContext Framework管理的集成测试加载
一个 ApplicationContext
实例。
为了提供对注解类、激活的bean定义配置、测试属性源、上下文层次和 WebApplicationContext
s的支持, SmartContextLoader
是一个接口的替代实现。
SmartContextLoader
: ContextLoader
接口的扩展在Spring 3.1中引入。
SmartContextLoader
SPI 在Spring 2.5中被引入,用来替代 ContextLoader
SPI 。特定地, SmartContextLoader
可以选择用来处理资源 位置
,注解的 类
,或上下文 初始化器
。进一步地,你可以在其加载的上下文中设置
SmartContextLoader
类来激活bean定义配置和测试属性源。
Spring 提供了如下的实现:
DelegatingSmartContextLoader
: 两个默认的加载器中的一个,在内部委托给AnnotationConfigContextLoader
、
GenericXmlContextLoader
或 GenericGroovyXmlContextLoader
类,具体依赖于测试类的配置声明、默认位置
或默认的配置类。 Groovy的支持需要Groovy(相关Jar)在Java类路径上。
WebDelegatingSmartContextLoader
: 两个默认的加载器中的一个,在内部委托给AnnotationConfigContextLoader
、
GenericXmlContextLoader
或 GenericGroovyXmlContextLoader
类,具体依赖于测试类的配置声明、默认位置
或默认的配置类。 如果在测试类上出现了 @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并且提供使用测试框架配置单元及集成测试的可以工作的示例。
Spring提供了如下测试执行监听器 TestExecutionListener
实现,它们将按如下顺序默认被注册。
ServletTestExecutionListener
: 为WEB应用上下文 WebApplicationContext
对象配置Servlet API 模拟器
DependencyInjectionTestExecutionListener
: 为测试实例提供依赖注入。
DirtiesContextTestExecutionListener
: 处理 @DirtiesContext
注解
TransactionalTestExecutionListener
: 提供带有默认回滚语义的事务性测试执行
SqlScriptsTestExecutionListener
: 执行通过 @Sql
注解配置的SQL脚本
用户自定义的测试执行监听器 TestExecutionListener
s 可以通过注解 @TestExecutionListeners
注册到测试类及其
子类中。了解注解 @TestExecutionListeners
的详情和示例,请参阅注解支持
和javadocs。
通过注解 @TestExecutionListeners
注册用户自定义的测试执行监听器 TestExecutionListener
s 适合用户
自定义监听器的有限测试场景;尽管如此,当用户自定义监听器跨越测试套件的时候将会非常麻烦。为了解决这个问题,Spring 框架
4.1 通过 SpringFactoriesLoader
机制支持 默认 测试执行监听器 TestExecutionListener
实现的自动发现。
具体的,spring-test
模块在属性文件 META-INF/spring.factories
下以org.springframework.test.context.TestExecutionListener
为键声明所有的核心默认测试执行监听器 TestExecutionListener
s 。第三方框架和开发人员可以以相同的方式在属性文件中
增加他们自己的测试执行监听器 TestExecutionListener
s 。
当TestContext框架通过前述的 SpringFactoriesLoader
机制发现默认的测试执行监听器 TestExecutionListeners
,
实例化的监听器使用spring的比较器 AnnotationAwareOrderComparator
进行排序,该比较器实现了 Ordered
接口并加注
了 @Order
注解。spring提供的 AbstractTestExecutionListener
和所有的默认的测试执行监听器 TestExecutionListener
s
都使用适当的值实现了 Ordered
接口。 因此,第三方框架和开发人员也要确保他们的 默认 测试执行监听器 TestExecutionListener
s
使用适当的顺序注册,通过实现 Ordered
接口或者声明 @Order
注解。为了弄清楚每一个核心监听器赋值详情,可以查阅Javadoc来了解
默认的核心测试执行监听器 TestExecutionListener
s 的 getOrder()
方法。
如果通过 @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
。
为了避免必须知道或者时重新声明 所有 默认 监听器,测试执行监听器注解 @TestExecutionListeners
的
mergeMode
属性可以设置为 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
的引用将被提供。注意, AbstractJUnit4SpringContextTests
和 AbstractTestNGSpringContextTests
实现了接口 ApplicationContextAware
,因此自动地提供
了对 ApplicationContext
对象的方法。
@Autowired ApplicationContext | |
---|---|
作为实现 @RunWith(SpringJUnit4ClassRunner.class)a @ContextConfiguration public class MyTest { @Autowired private ApplicationContext applicationContext; // 类体... } 类似地,如果你的测试配置为加载一个 @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration public class MyWebAppTest { @Autowired private WebApplicationContext wac; // 类体... } 通过注解 |
使用测试框架的类不需要继承特定的类或实现特定的接口。反而,在类级别声明一个 @ContextConfiguration
注解
就可以简单地配置。如果你的测试类没有明确地声明应用上下文资源 位置
或注解 类
,配置的上下文加载器
ContextLoader
将决定如何从默认的位置或默认的配置类上加载上下文。 除了上下文资源 位置
和注解 类
的
方式,你还可以通过应用上下文初始化器 initializers
的方式。
下述章节,解释了配置应用上下文 ApplicationContext
的几种方式,通过XML配置文件,通过注解类(典型的是
增加了 @Configuration
注解的类),或者通过使用Spring的 @ContextConfiguration
注解的初始化器
的方式。 可选地, 为了高级的用例,你还可以实现和配置你自己的智能上下文加载器类 SmartContextLoader
。
使用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
注解的 locations
和 value
属性, 那么测试上下文框架
将会尝试探测默认的XML资源位置。确切地说,GenericXmlContextLoader
和 GenericXmlWebContextLoader
会
基于测试类的名字探测默认的位置,如果你的测试类命名为 com.example.MyTest
,GenericXmlContextLoader
类
将从 "classpath:com/example/MyTest-context.xml"
位置加载应用上下文。
package com.example; @RunWith(SpringJUnit4ClassRunner.class) // 应用上下文将从 "classpath:com/example/MyTest-context.xml" 位置加载 @ContextConfiguration public class MyTest { // 类体... }
使用Groovy脚本为你的测试类加载一个应用上下文 ApplicationContext
,可以利用 Groovy DSL
注解你的测试类, 在注解 @ContextConfiguration
的 locations
和 value
属性中使用一个包含Groovy 脚本
的资源位置的数组配置属性。资源查找的语义和XML 配置文件的描述是一致的。
启动 Groovy 脚本支持 | |
---|---|
如果Groovy在类路径上,在测试上下文框架中使用Groovy脚本加载应用上下文 |
@RunWith(SpringJUnit4ClassRunner.class) // 应用上下文将从类路径根下的 "/AppConfig.groovy" 和 "/TestConfig.groovy" 加载 @ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) public class MyTest { // 类体... }
如果你同时忽略了注解 @ContextConfiguration
的 locations
和 value
属性,则测试上下文框架将会
尝试探测默认的Groovy脚本。确切地说,加载器 GenericGroovyXmlContextLoader
和 GenericGroovyXmlWebContextLoader
基于测试类的名字探测默认的位置。如果你的测试类命名为 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 { // 类体... }
同时声明XML 配置和Groovy脚本 | |
---|---|
XML配置文件和Groovy脚本可以通过注解 下述示例演示在集成测试中混合两种方式。 @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 { // 类体... }
注解类 | |
---|---|
注解类 可以指下述中的任何一种。
参阅注解 |
如果你忽略了直接 @ContextConfiguration
的 classes
属性,测试上下文框架将会尝试探测默认配置
类的存在。确切地说,加载器 AnnotationConfigContextLoader
和 AnnotationConfigWebContextLoader
将会探测满足需求的所有测试类的静态内部类, 这些内部类通过注解 @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脚本和注解类(例如,典型的 @Configuration
类)三种方式
来为你的测试类配置一个应用上下文。例如,你在产品中使用XML配置,在测试中你可能决定想使用注解类 @Configuration
来配置Springg管理的组件,反之亦然。
此外,一些第三方框架(就像Spring Boot)为加载应用上下文 ApplicationContext
提供了优秀的支持,他们
可以从不同类型的资源中同时加载(例如,XML配置文件,Groovy脚本和增加了 @Configuration
的类)。由于历史的原因,
Spring框架没有以标准发布的形式支持混合配置。因此,Spring框架中的 spring-test
模块的大多数上下文
加载器 SmartContextLoader
的实现在每一个测试上下文中仅支持一种资源类型;尽管如此,这并不意味着你
不能同时使用它们。通用规则的一个例外是上下文加载器 GenericGroovyXmlContextLoader
和 GenericGroovyXmlWebContextLoader
同时支持XML配置文件和Groovy脚本。 此外,第三方框架可能通过注解 @ContextConfiguration
选择支持
locations
和 classes
的声明以及测试框架的标准测试支持, 你可以有如下的选项。
如果你想使用资源位置(例如,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
支持两个布尔属性 inheritLocations
和 inheritInitializers
,这两个属性
指的是在潮类中声明的资源位置、注解类或上下文初始化器是否可以 继承 。它们两个的默认值都是 true
。那意味着一个测试
类默认会继承超类的资源位置、注解类或上下文初始化器的配置。确切地说,一个测试类资源位置或者是注解类配置被追加到超类声明
的资源位置或注解类的列表上。类似地,指定测试类的初始化器将会增加到超类定义的初始化集合上。因此,子类拥有 继承 资源
位置、注解类或上下文初始化器的选项。
如何 @ContextConfiguration
' 的 inheritLocations
或 inheritInitializers
属性设置为 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 | |
---|---|
注解 |
让我们看一些基于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 | |
---|---|
Implementations of |
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 { // ... }
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; //... }
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.
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 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 |
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.
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 ApplicationContext
s. 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 {}
Dirtying a context within a context hierarchy | |
---|---|
If |
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 | |
---|---|
The TestContext framework does not instrument the manner in which a test instance is
instantiated. Thus the use of |
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 | |
---|---|
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 |
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 | |
---|---|
If you are extending from a Spring-provided test base class that happens to use
// ... @Autowired @Override public void setDataSource(@Qualifier("myDataSource") DataSource dataSource) { super.setDataSource(dataSource); } // ... The specified qualifier value indicates the specific |
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.
WebApplicationContext
is loaded for your test by annotating your test
class with @WebAppConfiguration
.
WebApplicationContext
(i.e., via dependency injection).
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 } }
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 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).
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")); } }
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.
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")); } }
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 | |
---|---|
Any before methods (such as methods annotated with JUnit’s |
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
.
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 } }
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.
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.
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.
com.example.MyTest
, the
corresponding default script is "classpath:com/example/MyTest.sql"
.
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")); } }
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 | |
---|---|
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 |
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... } }
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 | |
---|---|
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 |
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.
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.
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.
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.
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.
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"));
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
.
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 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.
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.
Spring MVC Test’s own tests include example tests of client-side REST tests.
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:
AbstractTransactionalJUnit4SpringContextTests
class, from
which it inherits configuration for Dependency Injection (through the
DependencyInjectionTestExecutionListener
) and transactional behavior (through the
TransactionalTestExecutionListener
).
clinic
instance variable — the application object being tested — is set by
Dependency Injection through @Autowired
semantics.
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.
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.
Consult the following resources for more information about testing: