Partage de beans Spring au sein de portlets J2EE

De Fabien Gaujous dans Technique

25 oct 2013

Nous ne trouvons pas forcément beaucoup de ressources sur internet sur l’utilisation de Spring MVC Portlet au sein d’un portail d’entreprise J2EE.
Notament la configuration à effectuer pour pouvoir utiliser les mêmes beans Spring dans plusieurs portlets JAVA (JSR168 ou 286).
Cet article présente donc la configuration nécessaire sur le portail Liferay mais ce même principe peut être utilisé dans d’autres portails J2EE implémentant les JSRs.
Pour comprendre pleinement cet article, il est nécessaire d’avoir une expérience même minimale sur le développement de Portlets et les fichiers de configuration mis en oeuvre.

Tout d’abord, toutes les portlets sont incluses dans un même projet web (war).
Notre projet, géré sous maven, possède une dépendance vers la librairie spring-webmvc-portlet et toutes ses dépendances transitives nécessaires à Spring.

1
2
3
4
5
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc-portlet</artifactId>
            <version>XXXX</version>
        </dependency>

Cette librairie nous permet d’utiliser les annotation Spring MVC (revues pour les Portlets) telles que @RequestMapping,@Controller,@RenderMapping

Exemple de Portlet :

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("VIEW")
@Controller("dernieresNouveautesController")
public class DernieresNouveautesController {
    @Autowired
    private DevisHelper devisHelper;
    @RenderMapping
    public String handleRenderRequest(RenderRequest request, RenderResponse response, Model model) {    
...

Dans WEB-INF/web.xml, comme pour un projet Spring MVC normal, nous devons configurer le listener et la servlet Spring.
Configuration du web.xml :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.5"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <jsp-config>
        <taglib>
            <taglib-uri>http://java.sun.com/portlet_2_0</taglib-uri>
            <taglib-location>/WEB-INF/tld/liferay-portlet.tld</taglib-location>
        </taglib>
        <taglib>
            <taglib-uri>http://liferay.com/tld/aui</taglib-uri>
            <taglib-location>/WEB-INF/tld/aui.tld</taglib-location>
        </taglib>
    </jsp-config>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>view-servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>view-servlet</servlet-name>
        <url-pattern>/WEB-INF/servlet/view</url-pattern>
    </servlet-mapping>
</web-app>

Le fichier de configuration Spring GLOBAL à toutes les portlets est /WEB-INF/applicationContext.xml

Dans WEB-INF/portlet.xml, chaque Portlet doit être configurée pour utiliser son propre fichier de configuration Spring.
Dans cet exemple, la Portlet “ACC-SUIV-NOUVEAUTES” possède le fichier de configuration Spring qui lui est propre : /WEB-INF/accsuivnouveautes-portlet.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    <portlet>
        <portlet-name>ACC-SUIV-NOUVEAUTES</portlet-name>
        <display-name>Nouveautés</display-name>
        <portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
        <init-param>
            <name>contextConfigLocation</name>
            <value>/WEB-INF/accsuivnouveautes-portlet.xml</value>
        </init-param>
        <expiration-cache>0</expiration-cache>
        <supports>
            <mime-type>text/html</mime-type>
        </supports>
        <portlet-info>
            <title>Nouveautés</title>
            <short-title>Nouveautés</short-title>
            <keywords>Nouveautés</keywords>
        </portlet-info>
        <security-role-ref>
            <role-name>administrator</role-name>
        </security-role-ref>
    </portlet>

Dans le fichier de configuration GLOBAL (WEB-INF/applicationContext.xml) se trouve la configuration de tous les beans partagés pour toutes les Portlets.
Il y a souvent les DAO / Services…et tout ce qui concerne l’accès à la base de données.

1
2
3
4
5
6
7
8
9
10
    <!-- Attention garder le context:annotation-config seulement dans ce fichier -->
    <context:annotation-config />
    <!-- Scan des services (pas des controllers) -->
    <context:component-scan base-package="com.gh.common.spring.session,
        com.gh.common.spring.metier
        ">
    <bean id="springApplicationContext" class="com.gh.portal.spring.SpringApplicationContext"/>
    ....

La classe SpringApplicationContext (tirée de ce blog ), permet de récupérer un Bean Spring via une méthode statique depuis une Portlet.

Exemple de récupération du Service “contactService” :

1
ContactService contactsService = (ContactService) SpringApplicationContext.getBean("contactService");

Dans le fichier de configuration Spring de chaque Portlet, il y a seulement la configuration pour instancier le Portlet (Controlleur).

Voici le contenu du fichier WEB-INF/accsuivinouveautes-portlet.xml avec l’utilisation du “component-scan” pour instancier les portlets annotés @Controller

1
<context:component-scan base-package="com.gh.portlet.acc_suiv_nouveautes" />

Avec cette configuration mise en place, les services et toutes les entités gérées par l’ORM (Hibernate dans notre cas) sont chargés une fois pour toute au démarrage du projet Web.

Attention avec la version de Liferay 6.1.1-CE-GA2, lors du déploiement du war et donc des portlets, Liferay régénère la configuration du web.xml et notament les listeners.
Hors, l’ordre des listeners est important et celui lié à Spring com.liferay.portal.spring.context.PortalContextLoaderListener se retrouve en dernier, les beans partagés ne sont pas chargés avant le chargement de la Portlet et des Exceptions type BeanNotFound sont levées.
Le problème est connu (https://issues.liferay.com/browse/LPS-29103) et est résolu dans la 6.1.1-CE-GA3
Il suffit ainsi de le mettre en premier :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    <listener>
        <listener-class>com.liferay.portal.spring.context.PortalContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>com.liferay.portal.servlet.PortalSessionListener</listener-class>
    </listener>
    <listener>
        <listener-class>com.liferay.portal.kernel.servlet.PortletSessionListenerManager</listener-class>
    </listener>
    <listener>
        <listener-class>com.liferay.portal.kernel.servlet.SerializableSessionAttributeListener</listener-class>
    </listener>
    <listener>
        <listener-class>com.liferay.portal.servlet.SharedSessionAttributeListener</listener-class>
    </listener>

Ressources complémentaires :

Le chargement des différents context Spring : http://forum.springsource.org/showthread.php?120853-Share-application-context-between-portlets-in-single-war
Plus d’informations sur la configuration de Spring MVC Portlet : http://www.opensource-techblog.com/2012/09/spring-mvc-portlet-in-liferay.html

Commentaire

2 + deux =

iMDEO recrute !

REJOIGNEZ-NOUS

A la recherche de nouveaux talents (développeurs web et mobile, chefs de projet,...)

Voir les annonces