New in Spring 3.1: Bean definition profiles at a glimpse
The next major release of Spring Framework, 3.1, brings a new feature called bean definition profiles. This is a great add-on which makes the definition of the application context even easier, both in xml and Java-based style. With this new functionality it is possible to group beans into profiles which can be activated at runtime. I will show in this blog post how to leverage bean definition profiles, based on a monitoring use case.
Bean definition profiles where designed as a core mechanism allowing the use of different bean definitions in different circumstances. These different circumstances are usually a deployment on a different environment. It is quite a common need to have some beans defined when the application is deployed on a test environment and other ones when in acceptance or production. Stubbing external services, datasource definition (local vs. JNDI), notification mechanisms, just to name a few.
A profile is defined using the “profile” property of the “beans” element in the context definition. The rules are quite simple: Beans not belonging to any profile are always available in the application context. Beans defined in a profile are only included in the application context if the given profile is active. The profile(s) can be defined in the top-level beans element of the context definition or in an embedded beans element [2]. It is also possible to define multiple profiles at once [3]. Additionally the @Profile annotation can be used in @Configuration classes.
applicationContext-dev.xml [1]
<
beans
profile
=
"dev"
>
// beans
</
bean
>
applicationContext.xml [2]
<
beans
>
// beans
<
beans
profile
=
"dev"
>
// beans
</
beans
>
<
beans
profile
=
"prod"
>
// beans
</
beans
>
</
beans
>
applicationContext-dev-acceptance.xml[3]
<
beans
profile
=
"dev,acceptance"
>
// beans
</
bean
>
Let’s analyze how we can leverage bean definition profiles in a real-life use case. We will spy on an application’s
@Controller
s to see how long it takes to handle the incoming requests. For the sake of this example let’s assume that logging of the times is enough. First, let’s write an aspect to intercept the method invocation and log the execution time:
public
class
ControllerAspect {
private
static
Logger logger = Logger.getLogger(
"monitor"
);
@Pointcut
(
"within(@org.springframework.stereotype.Controller *)"
)
public
void
controllerBean() {
}
@Pointcut
(
"execution(* *(..))"
)
public
void
methodPointcut() {
}
@Around
(
"controllerBean() && methodPointcut()"
)
public
Object aroundControllerMethods(ProceedingJoinPoint pjp)
throws
Throwable {
String signature = pjp.getSignature().toShortString();
StopWatch stopWatch =
new
StopWatch(signature);
stopWatch.start();
Object retVal = pjp.proceed();
stopWatch.stop();
logger.info(stopWatch.shortSummary());
return
retVal;
}
}
We also need a controller to spy on. Let’s use this trivial implementation of an encoding controller:
@Controller
public
class
EncodingController {
private
static
final
List<String> algorithms = Arrays.asList(
"MD5"
,
"SHA"
);
@RequestMapping
(value =
"algorithms"
, method = RequestMethod.GET)
public
ResponseEntity<String> supportedAlgorithms()
throws
InterruptedException, UnsupportedEncodingException, NoSuchAlgorithmException {
return
new
ResponseEntity<String>(
"Supporter algorithms: "
+ algorithms.toString() , HttpStatus.OK);
}
@RequestMapping
(value =
"encode"
, method = RequestMethod.GET)
public
ResponseEntity<String>; encode(
@RequestParam
(
"value"
) String value,
@RequestParam
(
"algorithm"
) String algorithm)
throws
InterruptedException, UnsupportedEncodingException, NoSuchAlgorithmException {
if
(!algorithms.contains(algorithm)) {
return
new
ResponseEntity<String>(
"Algorithm not supported"
, HttpStatus.UNPROCESSABLE_ENTITY);
}
MessageDigest md = MessageDigest.getInstance(algorithm);
byte
[] digest = md.digest(value.getBytes());
return
new
ResponseEntity<String>(String.valueOf(Hex.encodeHex(digest)), HttpStatus.OK);
}
}
Once our aspect and controller code is ready we can move to defining a separate profile for the monitoring. We expect the monitoring to be on only in certain cases, for instance when deployed on a performance testing environment. This is where the bean definition profiles come into play. We will define the aspect in a separate profile which can be activated at runtime:
<
beans
profile
=
"monitoring"
>
<
bean
id
=
"controllerAspect"
class
=
"nl.jteam.sample.aop.ControllerAspect"
/>
<
aop:aspectj-autoproxy
>
<
aop:include
name
=
"controllerAspect"
/>
</
aop:aspectj-autoproxy
>
</
beans
>
The “monitoring” profile can be easily activated by passing -Dspring.profiles.active=”monitoring” parameter to the Java VM. More than one profile can be activated by specifying a coma-separated list, eg. -Dspring.profiles.active=”profile1,profile2,profile3″. When running the application with the “monitoring” profile we should notice logs upon each request:
INFO [monitor] - <StopWatch 'EncodingController.supportedAlgorithms()': running time (millis) = 1> INFO [monitor] - <StopWatch 'EncodingController.encode(..)': running time (millis) = 29>To keep in mind:
– Do not use the profiles when the difference between the beans lies in properties. ThePropertyPlaceholderConfigurer
will do the job.
– Watch out for any functionality which could compromise your application when accidentally activated on a production environment.A working project with the code from the above example can be cloned from github: https://github.com/annagos/beanprofiles-monitoring-sample