Tutoriel sur la configuration de Spring en Java

Antoine Rey

Cet article s'intéresse à la configuration de Spring en Java.

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum. Commentez .

- Titre : Tutoriel sur la configuration de Spring en Java

- Auteur : Antoine Rey

- Parution : 20 octobre 2014

- Licence : Copyright ® 2014 Antoine Rey. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Préambule

- III. Contextes applicatifs Spring

- IV. Tester la configuration Spring

- V. DataSource

- VI. Couche de persistance

- VII. Couche services

- VIII. Sécurité

- IX. Couche présentation

- X. Conclusion

- XI. Références

- XII. Remerciements

I - Introduction

Dans ce billet, nous verrons comment configurer en Java le contexte Spring d'une application basée sur Spring MVC, Spring Security, Spring Data JPA et Hibernate, et cela sans utiliser la moindre ligne de XML. Personnellement, je n'ai rien contre la syntaxe XML à laquelle j'étais habituée. D'autant plus que la verbosité de la configuration avait considérablement diminué grâce à l'introduction des namespaces XML et des annotations. Avant d'utiliser la syntaxe Java sur une application d'entreprise, j'étais même sceptique quant aux gains qu'elle pouvait apporter. Aujourd'hui, je comprends mieux son intérêt et pourquoi les projets du portfolio Spring tels Spring Integration 4.0, Spring Web Service 2.2 ou bien Spring Security 3.2 proposent dans leur dernière version un niveau de configuration Java iso-fonctionnel avec leur équivalent XML. Sans compter que le support de la configuration Java leur ouvre la porte d'une intégration plus poussée à Spring Boot, le nouveau fer de lance de Pivotal.

II - Préambule

Avant de faire partie intégrante du framework Spring, la configuration Java était proposée aux développeurs Spring dans un projet externe : Spring JavaConfig. Depuis la version 3.0 du framework Spring, des fonctionnalités équivalentes ont été introduites au fil des versions. La version 4.0 voit l'aboutissement de ce travail.

Tous les extraits de code suivants sont issus d'une application web disponible sur Github : spring-javaconfig-sample.
Les dépendances maven requises sont déclarées dans le pom.xml. Les toutes dernières versions des frameworks ont été exploitées :

- Spring Framework 4.0 ;

- Spring Data JPA 1.6 ;

- Spring Security 3.2 ;

- Hibernate 4.3.

Afin que cette application puisse être déployée dans un conteneur de Servlet 2.5, l'initialisation de la configuration Spring repose intégralement sur les déclarations réalisées en XML dans le web.xml. À cet effet, lors de l'exécution de la commande mvn clean install, un serveur Jetty 6 compatible servlet 2.5 démarre puis arrête l'application.
Dans un conteneur de Servlet 3.x, la déclaration du « DispatcherServlet » pourrait être réalisée en Java via la classe AbstractAnnotationConfigDispatcherServletInitializer introduite dans Spring 3.2.

Ce billet n'a pas pour vocation de se substituer au manuel de référence du framework Spring. Il ne vous montrera pas non plus l'équivalent XML de la configuration Java. Son objectif est de vous donner un exemple de configuration afin que vous puissiez monter rapidement une application utilisant la syntaxe Java.

III - Contextes applicatifs Spring

L'application web donnée en exemple reprend l'architecture type d'une application Spring MVC dans laquelle un WebApplicationContext parent (ou root) est chargé avec le listener ContextLoaderListener et un WebApplicationContext enfant est chargé via la DispatcherServlet.

Le contexte parent met à disposition les beans Spring de la couche service métier et de la couche de persistance (infrastructure compris). La sécurité est également définie au niveau du contexte parent, car les services métiers peuvent être sécurisés (@Secured). Chacune de ces couches étant configurée dans une classe qui lui est dédiée, la classe MainConfig a pour rôle de toutes les référencer :

Configuration of the business, persistence and security layers.

@Configuration
@Import(value = {
DataSourceConfig.class,
InfrastructureConfig.class,
RepositoryConfig.class,
ServiceConfig.class,
SecurityConfig.class
} )
public class MainConfig {

L'annotation @Configuration joue un rôle central. Les classes où elle est apposée se substituent en effet aux traditionnels fichiers de configuration XML. Nous y retrouvons la déclaration de beans Spring, l'import de fichiers ou de classes de configuration, la détection automatique de beans annotés par analyse de classpath ou bien encore l'activation de fonctionnalités avancées et des annotations associées (@Transactional, @Cacheable, @Scheduled, @Async …).

Remarque : la déclaration de beans Spring peut se faire en dehors d'une classe de @Configuration. C'est ce qu'on appelle le mode lite Beans. Non recommandé, le manuel de référence de Spring explique quels en sont les limitations et les dangers.

La classe MainConfig est également annotée avec @Import permettant d'importer d'autres classes de configuration. Via l'annotation @ImportResource, Spring offre la possibilité d'importer des fichiers de configuration XML. Très pratique lorsqu'on dépend de librairies tierces n'offrant qu'une configuration XML.

Pour Spring, les classes annotées avec @Configuration sont des beans Spring :

08:02:13.246 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'mainConfig'
08:02:13.246 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'mainConfig'

Cette spécificité infère aux beans de configuration la possibilité d'utiliser l'injection de dépendance via les annotations @Autowired et @Inject. Leur cycle de vie permet également d'utiliser les annotations @PostConstruct et @PreDestroy. La classe MainConfig exploite cette possibilité pour injecter l'Environment modélisant l'environnement d'exécution de l'application. Cette interface permet notamment d'accéder aux profils Spring activés. Lors de l'initialisation de la configuration spécifiée par MainConfig, la méthode initApp() annotée avec @PostConstruct génère une trace listant les profils actifs.

@PostConstruct and @Autowired annotations

private static final Logger LOG = LoggerFactory.getLogger(MainConfig.class);

@Autowired
private Environment         env;

/**
* Application custom initialization code.
* <p/>
* Spring profiles can be configured with a system property
* -Dspring.profiles.active=javaee
* <p/>
*/
@PostConstruct
public void initApp() {
LOG.debug("Looking for Spring profiles...");
if (env.getActiveProfiles().length == 0) {
LOG.info("No Spring profile configured, running with default configuration.");
} else {
for (String profile : env.getActiveProfiles()) {
LOG.info("Detected Spring profile: {}", profile);
}
}
}

Le contexte enfant est quant à lui défini au travers d'une seule classe WebMvcConfig que nous détaillerons par la suite. Cette hiérarchie de contexte permet aux beans de type @Service déclarés dans le contexte parent d'être visibles par les beans de type @Controller, l'inverse n'étant pas vrai.

IV - Tester la configuration Spring

De par le fait qu'elle est vérifiée à la compilation, la configuration codée en Java permet d'éviter de facto les erreurs que l'on retrouvait couramment en XML : fautes de frappe, déplacement de classes, JAR non présent dans le classpath…
Ce premier garde-fou n'empêche pas d'autres erreurs. Par exemple, l'annotation @EnableCaching génère une exception lorsqu'aucun bean de type CacheManager n'a été déclaré :

CacheManager could be found

Caused by: java.lang.IllegalStateException: No bean of type CacheManager could be found. Register a CacheManager bean or remove the @EnableCaching annotation from your configuration.

Pour se prémunir de ce genre d'exceptions découvertes au démarrage de votre application, le module Spring Test propose tout un jeu d'annotations : @WebAppConfiguration, @ContextConfiguration, @ActiveProfiles ou bien encore @ContextHierarchy. Introduite avec la version 3.2.2 de Spring, cette dernière permet de reproduire une hiérarchie de contextes Spring et de tester ainsi le chargement de contextes en conditions réelles.
La classe SpringConfigTest donne un exemple de test d'intégration de configuration Spring :

Unit test of the Java Spring configuration.

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = MainConfig.class),
@ContextConfiguration(classes = WebMvcConfig.class) })
@ActiveProfiles("test")
public class SpringConfigTest {

@Autowired
private WebApplicationContext wac;

@Test
public void springConfiguration() {
assertNotNull(wac);
}
}

V - DataSource

La couche de persistance reposant sur JPA, une connexion à une base de données relationnelle est nécessaire. Selon le contexte dans lequel s'exécute l'application, cette DataSource peut être récupérée de deux manières :

1. Par lookup JNDI lorsque l'application est déployée dans un serveur d'application JavaEE ou dans un conteneur web gérant ses propres ressources ;

2. Initialisationau démarrage de l'application par création d'une base de données en mémoire. Utile pour les tests d'intégration ou pour faciliter le déploiement de l'application.

Déclaré dans le bean de configuration DataSourceConfig, le bean dataSource sera par la suite injecté dans le bean de configuration InfrastructureConfig.

L'instanciation, la configuration et l'initialisation d'un bean Spring sont réalisées dans une méthode annotée par @Bean. Par défaut, le nom du bean Spring est déduit du nom de sa méthode. Voici un exemple de déclaration de beans :

Depending active spring profile, lookup RDBMS DataSource from JNDI or from an embbeded H2 database.

@Configuration
@PropertySource({ "classpath:com/javaetmoi/sample/config/datasource.properties" })
public class DataSourceConfig {

@Autowired
private Environment env;

@Bean
@Profile("javaee")
public JndiObjectFactoryBean dataSource() throws IllegalArgumentException {
JndiObjectFactoryBean dataSource = new JndiObjectFactoryBean();
dataSource.setExpectedType(DataSource.class);
dataSource.setJndiName(env.getProperty("jdbc.jndiDataSource"));
return dataSource;
}

@Bean
@Profile("test")
public DataSource testDataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}
}

On retrouve une méthode de déclaration de DataSource pour chacun des deux contextes présentés ci-dessus. Chaque méthode est annotée avec un @Profile Spring différent : javaee et test.
Le profile javaee est activé dans le web.xml :

Activation du profil javaeee dans le web.xml

<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>javaee</param-value>
</context-param>

Le profil test est quant à lui activé par l'annotation @ActiveProfiles(“test”) apposée sur la classe de test SpringConfigTest.
La méthode dataSource() utilise la fabrique de beans JndiObjectFactoryBean pour récupérer la DataSource à partir de son nom JNDI.
Le bean Environment est de nouveau utilisé. Cette fois-ci, pour récupérer la valeur de la propriété “jdbc.jndiDataSource” définie dans le fichier datasource.properties chargé à l'aide de l'annotation @PropertySource.

À noter que le type de retour de cette méthode est un JndiObjectFactoryBean. Pour retourner directement un DataSource, il aurait été nécessaire de se substituer au conteneur Spring en invoquant la méthode afterPropertiesSet() de la fabrique de beans :

Retourner directement un bean de type DataSource

dataSource.afterPropertiesSet();
return (DataSource) dataSource.getObject();

En effet, appelée pendant la phase d'initialisation du bean, afterPropertiesSet() effectue le lookup JNDI. Sans cet appel, la dataSource serait nulle :

Exception levée lorsque l'appel à afterPropertiesSet() est omis

Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public javax.persistence.EntityManagerFactory com.javaetmoi.sample.config.InfrastructureConfig.entityManagerFactoryBean()] threw exception; nested exception is java.lang.IllegalArgumentException: DataSource must not be null

VI - Couche de persistance

Déjà amorcée par la déclaration de la source de données, la mise en œuvre de la couche de persistance est complétée par deux autres beans de configuration :

1. InfrastructureConfig : infrastructure JPA composée d'une fabrique du gestionnaire d'entités JPA et du gestionnaire de transactions associé ;

2. RepositoryConfig : activation de Spring Data JPA et détection des beans de type Repository (DAOs).

L'annotation @EnableTransactionManagement portée par le bean InfrastructureConfig permet d'activer l'utilisation des annotations @Transactional chargées de délimiter les transactions base de données :

Activation des annotations @Transactional

@Configuration
@EnableTransactionManagement
@PropertySource({ "classpath:com/javaetmoi/sample/config/infrastructure.properties" })
public class InfrastructureConfig {

@Autowired
Environment        env;

@Autowired
private DataSource dataSource;

Récupérée par JNDI ou instanciée en mémoire, la DataSource est injectée sur le même modèle que n'importe quel bean Spring.
Vient ensuite la déclaration des beans transactionManager et transactionTemplate :

Déclaration des beans transactionManager et transactionTemplate

@Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory());
return jpaTransactionManager;
}

@Bean
public TransactionTemplate transactionTemplate() {
TransactionTemplate transactionTemplate = new TransactionTemplate();
transactionTemplate.setTransactionManager(transactionManager());
return transactionTemplate;
}

Mettons de côté la méthode entityManagerFactory() sur laquelle nous reviendrons plus loin. Cette configuration montre comment mettre en relations deux beans Spring : le bean transactionTemplate utilise en effet le bean transactionManager. En XML, cette mise en relation est habituellement réalisée à l'aide de la balise ref et de l'identifiant du bean. En Java, l'injection d'un bean dans un autre se fait en utilisant la méthode de déclaration du bean à injecter, en l'occurrence ici transactionManager(). Ce qui ressemble à un appel de méthode est trompeur. En effet, Spring interprète ce type d'appel afin de gérer le cycle de vie des Beans. Par exemple, lorsqu'un bean est de portée singleton, la méthode de création du bean n'est invoquée qu'une seule et unique fois, même si le bean est injecté dans plusieurs beans. Techniquement, Spring instrumente les classes annotées avec @Configuration au démarrage du contexte applicatif.

Le bean entityManagerFactory est déclaré de la façon suivante :

Déclaration du beanentityManagerFactoryBean

@Bean
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPersistenceUnitName("javaconfigSamplePersistenceUnit");
em.setPackagesToScan("com.javaetmoi.sample.domain");
em.setJpaVendorAdapter(jpaVendorAdaper());
em.setJpaPropertyMap(additionalProperties());
em.afterPropertiesSet();
return em.getObject();
}

Il utilise le bean dataSource injecté plus haut dans la classe de configuration. Derrière la méthode jpaVendorAdaper() se cache un autre bean. Quant à additionalProperties(), il s'agit d'un vrai appel de méthode. De la même manière que pour la DataSource, l'appel aux méthodes afterPropertiesSet() et getObject() est nécessaire pour retourner un bean de type EntityManagerFactory.

Une autre possibilité aurait consisté à renvoyer un LocalContainerEntityManagerFactoryBean puis à utiliser l'annotation @Autowired sur la méthode transactionManager. Charge à Spring de demander à la fabrique de beans de lui retourner un EntityManagerFactory :

Injection d'un entityManagerFactory

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
// …
return em;
}

@Bean
@Autowired
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
return jpaTransactionManager;
}

L'injection du bean dataSource dans le LocalContainerEntityManagerFactoryBean aurait pu être réalisée d'une autre manière. En effet, en tant que beans Spring, les beans de configuration peuvent être injectés dans d'autres beans. Ainsi, le bean DataSourceConfig aurait pu être injecté dans InfrastructureConfig. Il devient alors possible d'appeler dataSourceConfig.dataSource() pour récupérer la dataSource :

Exemple d'injection d'un bean de configuration

@Autowired
private DataSourceConfig dataSourceConfig;
…
@Bean
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSourceConfig.dataSource());
…
}

Bien que facilitant la navigation dans la configuration Spring, cette technique induit un couplage fort entre les deux beans de configuration.
Une fois l'infrastructure Hibernate/JPA mis en place, le bean de configuration RepositoryConfig initie la couche d'accès aux données. Par commodité, son implémentation est réalisée à l'aide du projet Spring Data JPA. L'annotation @EnableJpaRepositories active ce dernier : toutes les interfaces contenues dans un package donné et étendant l'interface Repository de Spring Data sont détectées puis interprétées. Par défaut, les requêtes JPA sont générées à partir du nom des méthodes exposées dans ces interfaces. Les implémentations personnalisées des Repository sont recherchées à l'aide du suffixe Impl.

Beans de type Repository

@Configuration
@EnableJpaRepositories("com.javaetmoi.sample.repository")
public class RepositoryConfig {
//
}

La couche de persistance est sans doute celle nécessitant le plus de configuration. À côté, la couche services métiers est particulièrement simple.

VII - Couche services

L'objectif premier de ServiceConfig consiste à détecter tous les beans Spring annotés par @Service. L'annotation @ComponentScan joue ce rôle en analysant récursivement le package Java dédié aux services métiers, dans notre exemple le package com.javaetmoi.sample.service :

En-tête de la classe ServiceConfig

@Configuration
@EnableAsync
@EnableScheduling
@EnableAspectJAutoProxy
@EnableCaching
@ComponentScan(basePackages = { "com.javaetmoi.sample.service" })
public class ServiceConfig implements AsyncConfigurer {]

C'est dans cette couche que je vous propose d'activer des fonctionnalités avancées de Spring :

- @EnableAsync : appel de méthode asynchrone via l'annotation @Async ;

- @EnableScheduling : planification d'appel de méthode via l'annotation @Scheduled ;

- @EnableAspectJAutoProxy : proxyfication de beans Spring ne possédant pas d'interface ;

- @EnableCaching : résultat de l'appel de méthode mis en cache avec @Cacheable, évincé avec @CacheEvict ou bien rafraichi avec @CachePut. Si vous n'êtes pas encore passés à Java 8, l'annotation @Caching permet d'utiliser plusieurs fois l'une des trois annotations précédentes sur la même méthode.

Comme indiqué plus haut, l'annotation @EnableCaching nécessite un CacheManager. La définition d'un bean cacheManager et d'un defaultCache est donnée ici à titre indicatif :

Déclaration des beans cacheManager et defaultCache

@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<Cache> caches = new ArrayList<Cache>();
// to customize
caches.add(defaultCache());
cacheManager.setCaches(caches);
return cacheManager;
}

@Bean
public Cache defaultCache() {
ConcurrentMapCacheFactoryBean cacheFactoryBean = new ConcurrentMapCacheFactoryBean();
cacheFactoryBean.setName("default");
cacheFactoryBean.afterPropertiesSet();
return cacheFactoryBean.getObject();

}

Afin de pouvoir personnaliser le pool de threads utilisés lors de traitements asynchrones, l'annotation @EnableAsync encourage à définir son propre pool en implémentant l'interface AsyncConfigurer et sa méthode public Executor getAsyncExecutor() :

Implémentation de la méthode getAsyncExecutor

@Override
public Executor getAsyncExecutor() {
log.debug("Creating Async Task Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// to customize with your requirements
executor.setCorePoolSize(5);
executor.setMaxPoolSize(40);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("MyExecutor-");
executor.initialize();
return executor;
}

Avant de passer à la configuration de la couche présentation, terminons l'initialisation du contexte racine par la mise en place de la sécurité.

VIII - Sécurité

Reposant sur le projet Spring Security, la configuration de la sécurité applicative est centralisée dans le fichier SecurityConfig. L'annotation @EnableWebMvcSecurity s'utilise de pair avec la classe abstraite WebSecurityConfigurerAdapter. Cette dernière permet d'activer la configuration par défaut de Spring Security. La redéfinition de certaines méthodes permet de personnaliser la configuration via les classes AuthenticationManagerBuilder, HttpSecurity et WebSecurity.

Définition de la classe SecurityConfig basée sur Spring Security

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

Publié sur le blog de Spring, le billet Spring Security Java Config Preview : Web Security explique comment utiliser la syntaxe Java.
Dans notre exemple, la déclaration du filtre springSecurityFilterChain dans le web.xml permet de rester compatible avec les conteneurs de Servlet 2.5.

L'un des apports du fichier SecurityConfig réside dans la mise à disposition du bean authenticatedUserDetails. Implémentant l'interface UserDetails de Spring Security, ce bean permet d'accéder aux données de l'utilisateur authentifié (ex : login, nom, habilitations). De portée session (spécifiée avec l'annotation @Scope), ce bean peut être injecté dans des beans de scopes différents, par exemple dans les contrôleurs ou les services métiers de portée singleton. Très pratique pour les logs et/ou les données de traçabilité.

Définition du bean authenticatedUserDetails

@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserDetails authenticatedUserDetails() {
SecurityContextHolder.getContext().getAuthentication();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
if (authentication instanceof UsernamePasswordAuthenticationToken) {
return (UserDetails) ((UsernamePasswordAuthenticationToken) authentication).getPrincipal();
}
if (authentication instanceof RememberMeAuthenticationToken) {
return (UserDetails) ((RememberMeAuthenticationToken) authentication).getPrincipal();
}
}
return null;
}

Remarque : le système d'authentification sur lequel est branchée votre application peut renvoyer des données supplémentaires. Il est alors fréquent d'avoir à spécialiser la classe User implémentant UserDetails. Si vous ne définissez pas d'interface sur votre classe MyUser, l'utilisation du ScopedProxyMode.TARGET_CLASS est requise par Spring pour proxyfier votre classe.

IX - Couche présentation

La configuration Java de Spring MVC ressemble à celle de Spring Security. L'annotation @EnableWebMvc s'utilise conjointement à la classe abstraite WebMvcConfigurerAdapter. L'application bénéficie d'une configuration par défaut qu'il est possible de personnaliser par redéfinition de méthodes. La classe WebMvcConfig joue ce rôle :

Configuration Java de Spring MVC

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.javaetmoi.sample.web" })
public class WebMvcConfig extends WebMvcConfigurerAdapter {

private static final int CACHE_PERIOD = 31556926; // one year

@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

@PostConstruct
public void init() {
requestMappingHandlerAdapter.setIgnoreDefaultModelOnRedirect(true);
}

@Bean
public ViewResolver viewResolver() {
// Example: the 'info' view logical name is mapped to the file '/WEB-INF/jsp/info.jsp'
InternalResourceViewResolver bean = new InternalResourceViewResolver();
bean.setViewClass(JstlView.class);
bean.setPrefix("/WEB-INF/jsp/");
bean.setSuffix(".jsp");
return bean;
}

@Bean(name = "messageSource")
public ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:com/javaetmoi/sample/web/messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Static ressources from both WEB-INF and webjars
registry
.addResourceHandler("/resources/**")
.addResourceLocations("/resources/")
.setCachePeriod(CACHE_PERIOD);
registry
.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(CACHE_PERIOD);
}

@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/about"); // the about page did not required any controller
}

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// Serving static files using the Servlet container's default Servlet.
configurer.enable();
}

@Override
public void addFormatters(FormatterRegistry formatterRegistry) {
// add your custom formatters
}
}

En complément de la redéfinition de méthode, il est possible de configurer Spring MVC en paramétrant les beans créés automatiquement par l'annotation @EnableWebMvc et la classe DelegatingWebMvcConfiguration qu'elle référence.
C'est précisément le cas du bean RequestMappingHandlerAdapter paramétré dans la fonction init() qui est appelée au chargement du contexte applicatif Spring.
Pour les nouvelles applications Spring MVC, il est recommandé par Spring de forcer à true le flag ignoreDefaultModelOnRedirect. Lors de redirection, l'utilisation de l'interface RedirectAttributes devient alors nécessaire pour spécifier quelle donnée doit être passée à la RedirectView.

La configuration donnée en exemple ne donne qu'un aperçu de l'éventail des possibilités de configuration offertes par Spring MVC : choix de la technologie de rendu (ici JSP), accès aux ressources statiques, définition du cache, internationalisation, déclaration des formateurs globaux…

X - Conclusion

En guise de conclusion, voici un tableau récapitulant les avantages et les inconvénients qu'a un développeur Java à passer sur une syntaxe full Java :

Avantages

Inconvénients

- Type safe : syntaxe vérifiée à la compilation

- Complétion dans l'IDE sans plugin

- Import Java mettant en évidence les dépendances non présentes

- Refactoring facilité de la configuration

- Ajout de code possible lors de la déclaration de beans (ex : logs)

- Accélère le chargement du contexte applicatif

- Traite la configuration comme du code

- Navigation dans la configuration facilitée

- Mixité possible avec la configuration XML

- Plus besoin de connaitre les namespaces XML

- Nouvelle syntaxe à appréhender

- Subtilités à maîtriser : afterPropertiesSet(), appels de méthodes annotées avec @Bean qui n'en sont pas

XI - Références

1. Manuel de référence Spring Framework 4 (Pivotal)

2. Spring Java Based Configuration (Tutorials Point)

3. Spring Data, une autre façon d'accéder aux données (So@t)

4. Spring Security Java Config Preview : Web Security (Pivotal)

5. Spring Java Config 101 (DZone)

6. Spring Cache (Zenika)

XII - Remerciements

Cet article a été publié avec l'aimable autorisation d'Antoine Rey.

Nous tenons à remercier milkoseck pour sa relecture orthographique attentive de cet article et Régis Pouiller pour la mise au gabarit.