Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why my Aspect is not detected for Jersey controller (using custom annotation)?

I want to create an Aspect over a Jersey controller to measure how long the services take to be executed. I'm fighting against my pointcut since it isn't detected and my aspect never gets launched.

I have tried using lots of pointcuts like:

execution(@Monitor * *.*(..))
execution(public * *(..))
change the order of @Aspect and @Component

Added a pointcut like this:
@Pointcut("execution(@Monitor * *.*(..))")
public void monitorRequestTargets(){}
@Around("monitorRequestTargets()")

Tried using AOP and CGLIB
<aop:aspectj-autoproxy proxy-target-class="true"/>

Also tried changing the order of configuration in context.xml

Eclipse detects that my methods are being adviced by my aspect but it isn't executed at runtime. Could you give me any hint about why aspect is not created or the pointcut doesn't launch?

My code is the following.

Context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <!-- Enables the Spring MVC @Controller -->
    <annotation-driven />

    <!-- Enables AspectJ -->
    <aop:aspectj-autoproxy />

    <!-- .....more definitions -->

    <context:component-scan base-package="com.mypackage" />

</beans:beans>

My MonitorAspect

@Component
@Aspect
public class MonitorAspect
{
    private static final Logger logger = LoggerFactory.getLogger(MonitorAspect.class);

    @Around("@annotation(com.mypackage.Monitor)")
    public void logTimeUsage(ProceedingJoinPoint joinPoint) throws Throwable 
    {
        // Store executing method
        String method = joinPoint.getSignature().getName();

        // Track time
        long startTime = System.currentTimeMillis();
        joinPoint.proceed();
        long endTime = System.currentTimeMillis();

        long duration = endTime - startTime;

        // Log time consumed by executing method
        logger.info(method + ": " + duration);

    }
}

My Monitor custom annotation is

@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Monitor
{

}

My controller which I want to use the aspect:

    @Monitor
    @POST
    @Consumes("application/json")
    @Produces("application/json")
    @Path("/{tkn}/test/")
    public Response test(
            @Context HttpServletRequest httpReq,
            @Context UriInfo uri,
                     String enrollReqJson
            ) {
          Thread.sleep(1000); // Implementation is not important
    }

My pom.xml

<!-- Spring -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${org.springframework-version}</version>
    <exclusions>
        <!-- Exclude Commons Logging in favor of SLF4j -->
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
  <!-- Jersey / Spring -->
<dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-spring</artifactId>
    <version>1.14</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-bundle</artifactId>
    <version>1.8</version>
</dependency>
        <dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-multipart</artifactId>
    <version>1.8</version>
</dependency>
        <!-- Spring AOP / AspectJ -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>${org.aspectj-version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${org.aspectj-version}</version>
</dependency>
like image 412
Federico Piazza Avatar asked Jan 21 '14 23:01

Federico Piazza


People also ask

Why can't Spring AOP see my annotation?

By default it is not, so Spring AOP would not be able to see the annotation. This is why it's been reconfigured. 5. Creating Our Aspect Now we have our annotation, let's create our aspect. This is just the module that will encapsulate our cross-cutting concern, which is our case is method execution time logging.

What is the use of @spring controller annotation?

Spring Controller annotation is typically used in combination with annotated handler methods based on the @RequestMapping annotation. It can be applied to classes only. It’s used to mark a class as a web request handler. It’s mostly used with Spring MVC applications. This annotation acts as a stereotype for the annotated class, indicating its role.

Why do we need @component annotation in componentscan?

The classes of which instances are acquired, also have to be known to the Spring framework (to be picked up by the ComponentScan) so they require some Spring annotation such as @Component, @Repository, @Service, @Controller, @Configuration. Spring manages the life-cycle of instances of those classes.

How do I create a custom annotation in Java?

The first step toward creating a custom annotation is to declare it using the @interface keyword:. public @interface JsonSerializable { } The next step is to add meta-annotations to specify the scope and the target of our custom annotation: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.Type) public @interface JsonSerializable { }


1 Answers

It looks like the same problem I was having. Based on what you've provided, it looks like you're using Spring AOP on an object that is not created by Spring (presumably Jersey is what creates your controller class instances) and therefore the advice is not being applied. Eclipse says its being advised, but that would only be for instances of your controller that are created by Spring. The ones you are actually using, the ones created by Jersey (or whatever provider you are using), are not advised.

I spent a lot of time trying to get Jersey to use Spring to create controller instances (including a couple of plugins that claimed to be able to do just that), but never could get it to work. I was able to get it working by using AspectJ directly instead of Spring AOP. It wasn't very hard, with exception of figuring out how to get spring to inject into the aspect.

Added:

The annotations remain the same. You just need to remove the AOP stuff from spring.xml, and create an aop.xml file in META-INF. Here's what mine looks like:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
    <aspects>
        <aspect name="com.ancestry.academy.api.ServiceApiAspect" />
        <aspect name="com.ancestry.academy.manager.ManagerApiAspect" />
    </aspects>
</aspectj>

You can keep Spring AOP for classes that are spring loaded. You only have to do this for classes that are created outside of spring (like controllers for jax).

There's one little piece of black magic that I don't really understand. If you need to use spring to inject into your aspect itself, then you need to declare it in spring.xml like this:

<bean id="serviceApiAspect" class="com.ancestry.academy.api.ServiceApiAspect" factory-method="aspectOf" />

<bean id="managerApiAspect" class="com.ancestry.academy.manager.ManagerApiAspect" factory-method="aspectOf" />

Note that my aspects do NOT have a method named aspectOf. Apparently that's added by AspectJ. That was pretty much all there was to it.

like image 58
Robert Wille Avatar answered Oct 28 '22 20:10

Robert Wille