Spring AOP advice et pointcut sur Servlet
Par guillaume le samedi 2 octobre 2010, 23:37 - java / j2ee - Lien permanent
La puissance de Spring AOP se limite a priori à son contexte (cf ce post http://forum.springsource.org/archive/index.php/t-11673.html )... Les servlets sont hors contexte Spring car gérées par le serveur d'application JEE et semblent exclues du champ de Spring AOP. Cependant, il est tentant de vouloir quand même utiliser Spring AOP sur les servlets sans avoir à passer par d'autres tisseurs d'aspects (comme AspectJ) qui complexifient la configuration du serveur d'application et le déploiement de l'application. Il s'avère que c'est possible par un moyen détourné. Il fallait juste y penser.
Mon besoin initial consistait à calculer le temps d'exécution des servlets CXF pour des webservices.
L'astuce consiste à créer un filtre de servlet. Mais les filtres sont aussi gérés par le conteneur me direz-vous?! Oui mais il est possible de raccrocher ces filtres au contexte spring de 2 façons :
La première solution
consiste à raccrocher le filtre au contexte spring lors de l'initialisation
du filtre :
public class SpringAwareFilter implements Filter {
@Autowire MyBean myBean;
public void destroy(...) { ... }
public void doFilter(...) {
....
myBean.methodeToIntercept(...);
....
}
public void init(FilterConfig filterConfig) throws ServletException {
ServletContext servletContext = filterConfig.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
AutowireCapableBeanFactory autowireCapableBeanFactory = webApplicationContext.getAutowireCapableBeanFactory();
autowireCapableBeanFactory.configureBean(this, "myBean");
}
Une fois le filtre accroché au contexte Spring, il est possible d'ajouter le
pointcut sur methodToIntercept du bean Spring myBean.
Source : http://forum.springsource.org/showthread.php?t=60983
La deuxième solution
est d'utiliser directement la classe Spring
DelegatingFilterProxy qui fait sensiblement la même
chose.
On crée d'abord un filtre en implémentant Filter de la façon suivante :
package fr.openfarm.aop;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class ServletPointcutFilter implements Filter {
@Override
public void destroy() {
// cette partie "lifecycle" du filtre est normalement prise en charge
// par la classe org.springframework.web.filter.DelegatingFilterProxy
// déclarée dans le web.xml
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// filtre identité que ne à rien d'autre que donner la main
// au filtre de servlet suivant
// c'est cette méthode publique qui va pouvoir être interceptée
// et donc utilisée comme pointcut par Spring AOP
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// cette partie "lifecycle" du filtre est normalement prise en charge
// par la classe org.springframework.web.filter.DelegatingFilterProxy
// déclarée dans le web.xml
}
}
Ensuite, il faut déclarer ce filtre comme un bean spring :
<bean id="servletPointcutFilter" class="fr.openfarm.aop.ServletPointcutFilter"/>
Visible du contexte Spring, il peut alors être candidat aux advices.
Il faut ensuite déclarer le filtre DelegatingFilterProxy de Spring et son application dans le fichier web.xml :
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>CXF WS-S</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Il est impossible d'intercepter les servlets par Spring AOP
car elles sont hors contexte spring
l'astuce consiste à appliquer un filtre de servlet sur les CXF Servlets
grâce au filtre DelegatingFilterProxy qui s'enregistre dans le contexte spring
et qui déléguera au filtre déclaré comme bean servletPointcutFilter
-->
<filter>
<filter-name>servletPointcutFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>servletPointcutFilter</filter-name>
<servlet-name>CXFServlet</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>CXFServlet</servlet-name>
<display-name>CXF Servlet</display-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
Toujours fidèle à la devise "convention over configuration", il faut utiliser comme valeur de <filter-name> dans le fichier web.xml l'identifiant du bean déclaré dans Spring. Le DelegatingFilterProxy s'enregistre auprès du context Spring et fera office de Filtre proxy en appelant notre servletPointcutFilter.
Le filtre de servlet d'exécutant systématiquement avec les servlets, on a donc un moyen de s'insérer dans le fil d'exécution, à défaut d'être au plus près de la servlet. Comme dans la première méthode, on peut alors appliquer un advice sur le filtre de servlet avec par exemple l'advice @Around et le pointcut suivant :
@Aspect
public class TimeLoggerAspect {
@Pointcut("execution(public void fr.openfarm.aop.ServletPointcutFilter.doFilter(..))")
public void servlet() {}
@Around("servlet()")
public void logTimeExec() { ... }
}
Source :
http://forum.springsource.org/showthread.php?t=58083
Sinon un très bon article sur AOP AOP : mythes et réalités ( http://www.ibm.com/developerworks/java/library/j-aopwork15/ )