Configurez Spring en Java

logo-spring-highres

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é. D’autant 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.

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. La toute dernière version 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. A cet effet, lors 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.

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 comprise). 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
@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 l’annotation @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.

    @Autowired
    private Environment         env;

    @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éfinie 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.

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 pas été déclaré :

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 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 :

@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);
    }
}

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 2 manières :

  1. Par lookup JNDI lorsque l’application est déployée dans un serveur d’application JavaEE ou un conteneur web gérant ses propres ressources.
  2. Initialisée au 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 est réalisé 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 :

@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 2 contextes présentés ci-dessus. Chaque méthode et annotée avec un @Profile Spring différent : javaee et test.
Le profile javaee est activé dans le web.xml :

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

Le profile 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ée à l’aide de l’annotation @PropertySource.

A 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 :

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 null :

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

 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 2 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 :

@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.
Viennent ensuite la 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 2 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 :

Ex    @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() sont nécessaires 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 :

@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 bean de configuration. Ainsi, le bean DataSourceConfig aurait pu être injecté dans InfrastructureConfig. Il devient alors possible d’appeler dataSourceConfig.dataSource()  pour récupérer la dataSource :

@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 2 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.

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

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

Couche services

L’objectif premier de ServiceConfig consiste à détecter tous les beans Spring annotés par l’annotation @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 :

@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 :

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

@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é lors de traitements asynchrone, l’annotation @EnableAsync encourage à définir son propre pool en implémentant l’interface AsyncConfigurer et sa méthode public Executor 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é.

Sécurité

Reposant sur projet Spring Security, la configuration de la sécurité applicative est centralisée dans le fichier SecurityConfig. L’annotation @EnableWebMvcSecuritys’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.

@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 utilisateurs (ex : login, nom, habilitations) de l’utilisateur authentifié. 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é.

    @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) authentication.getPrincipal();
            }
            if (authentication instanceof RememberMeAuthenticationToken) {
                return (UserDetails) 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.

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
@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 qui est paramétré dans init() 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 …

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 :

AvantagesInconvé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 conf 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

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)

1 thoughts on “Configurez Spring en Java

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.