Bean type not found when using Spring aspect and java config

3.5k views Asked by At

I added an aspect to a working Spring+Wicket application to log thrown exceptions, and now I get the following error whenever I load the Report page:

ERROR | 2013-12-09 08:42:06,149 | qtp1559334851-16 | DefaultExceptionMapper:123 - Unexpected error occurred
org.apache.wicket.WicketRuntimeException: Can't instantiate page using constructor 'public org.jonblack.ReportPage() throws java.lang.Exception'. An exception has been thrown during construction!
    at org.apache.wicket.session.DefaultPageFactory.newPage(DefaultPageFactory.java:194)
    at org.apache.wicket.session.DefaultPageFactory.newPage(DefaultPageFactory.java:67)
    ...
Caused by: java.lang.IllegalStateException: bean of type [org.jonblack.ReportController] not found
    at org.apache.wicket.spring.injection.annot.AnnotProxyFieldValueFactory.getBeanNameOfClass(AnnotProxyFieldValueFactory.java:236)
    at org.apache.wicket.spring.injection.annot.AnnotProxyFieldValueFactory.getBeanName(AnnotProxyFieldValueFactory.java:179)
    ...

I'm using java configuration for the project, so there is no applicationContext.xml. All configuration is done via annotations, the bulk of which are in AppConfig.java.

All the code for my application is shown below. I've left out the HTML templates and HomePage as they aren't integral to the problem.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>org.jonblack</groupId>
  <artifactId>sw-p2</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
    <!-- TODO project name  -->
  <name>sw-p2</name>
  <description></description>
  <licenses>
    <license>
      <name>The Apache Software License, Version 2.0</name>
      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
      <distribution>repo</distribution>
    </license>
  </licenses>
  <properties>
    <aspectj.version>1.7.4</aspectj.version>
    <spring.version>3.2.4.RELEASE</spring.version>
    <wicket.version>6.11.0</wicket.version>
    <jetty.version>7.6.3.v20120416</jetty.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <!--  WICKET DEPENDENCIES -->
    <dependency>
      <groupId>org.apache.wicket</groupId>
      <artifactId>wicket-core</artifactId>
      <version>${wicket.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.wicket</groupId>
      <artifactId>wicket-extensions</artifactId>
      <version>${wicket.version}</version>
    </dependency>
    <dependency>
      <groupId>org.wicketstuff</groupId>
      <artifactId>wicketstuff-annotation</artifactId>
      <version>${wicket.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.wicket</groupId>
      <artifactId>wicket-spring</artifactId>
      <version>${wicket.version}</version>
    </dependency>
    <!-- Spring dependencies -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!-- aspectj -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>${aspectj.version}</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>${aspectj.version}</version>
    </dependency>
    <!-- LOGGING DEPENDENCIES - LOG4J -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.6.4</version>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.16</version>
    </dependency>

    <!--  JUNIT DEPENDENCY FOR TESTING -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>

    <!--  JETTY DEPENDENCIES FOR TESTING  -->
    <dependency>
      <groupId>org.eclipse.jetty.aggregate</groupId>
      <artifactId>jetty-all-server</artifactId>
      <version>${jetty.version}</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  <build>
    <resources>
      <resource>
        <filtering>false</filtering>
        <directory>src/main/resources</directory>
      </resource>
      <resource>
        <filtering>false</filtering>
        <directory>src/main/java</directory>
        <includes>
          <include>**</include>
        </includes>
        <excludes>
          <exclude>**/*.java</exclude>
        </excludes>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <filtering>false</filtering>
        <directory>src/test/resources</directory>
      </testResource>
      <testResource>
        <filtering>false</filtering>
        <directory>src/test/java</directory>
        <includes>
          <include>**</include>
        </includes>
        <excludes>
          <exclude>**/*.java</exclude>
        </excludes>
      </testResource>
    </testResources>
    <plugins>
      <plugin>
        <inherited>true</inherited>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.5.1</version>
        <configuration>
          <source>1.7</source>
          <target>1.7</target>
          <encoding>UTF-8</encoding>
          <showWarnings>true</showWarnings>
          <showDeprecation>true</showDeprecation>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>${jetty.version}</version>
        <configuration>
          <connectors>
            <connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
              <port>8080</port>
              <maxIdleTime>3600000</maxIdleTime>
            </connector>
            <connector implementation="org.eclipse.jetty.server.ssl.SslSocketConnector">
              <port>8443</port>
              <maxIdleTime>3600000</maxIdleTime>
              <keystore>${project.build.directory}/test-classes/keystore</keystore>
              <password>wicket</password>
              <keyPassword>wicket</keyPassword>
            </connector>
          </connectors>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-eclipse-plugin</artifactId>
        <version>2.9</version>
        <configuration>
          <downloadSources>true</downloadSources>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

AppConfig.java

package org.jonblack;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.Executor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan;
//import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@ComponentScan("org.jonblack")
@EnableAsync
@EnableAspectJAutoProxy()
public class AppConfig {
  private static final Logger log = LoggerFactory.getLogger(AppConfig.class);

  @Bean
  public ReportController reportController() {
    log.info("Getting ReportController");
    return new ReportController();
  }

  @Bean
  public ThreadPoolTaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
    pool.setCorePoolSize(5);
    pool.setMaxPoolSize(10);
    pool.setWaitForTasksToCompleteOnShutdown(true);
    return pool;
  }

  @Bean
  public ExceptionLoggingAspect exceptionLoggingAspect() {
    return new ExceptionLoggingAspect();
  }

  @Bean
  public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {
    AnnotationAwareAspectJAutoProxyCreator aop = new AnnotationAwareAspectJAutoProxyCreator();
    return aop;
  }
}

ExceptionLoggingAspect.java

package org.jonblack;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;

import org.springframework.stereotype.Component;


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

  @AfterThrowing(pointcut="execution(* org.jonblack.ReportController.getReportData(..))", throwing="ex")
  public void afterThrowing(Throwable ex) {
    LOG.error(ex.getMessage());
  }

}

ReportController.java

package org.jonblack;


import java.util.concurrent.Callable;
import java.util.concurrent.Future;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Controller;

@Controller
public class ReportController {
  private static final Logger log = LoggerFactory.getLogger(ReportController.class);

  @Autowired
  private ThreadPoolTaskExecutor taskExecutor;

  @Async
  public Future<String> getReportData() throws Exception {
    log.info("Entered into getReportData()");

    try {
      log.info("Throwing an exception");
      throw new Exception("Something went wrong");
    } catch(Exception ex) {
      log.error("In catch block: {}", ex.getMessage());
    }

    final Future<String> result = taskExecutor.submit(new Callable<String>() {
      @Override
      public String call() throws Exception {
        return "not a fancy result";
      }
    });

    log.info("Returning from getReportData()");
    return result;
  }
}

ReportPage.java

package org.jonblack;

import java.util.concurrent.Future;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.wicketstuff.annotation.mount.MountPath;


@MountPath("report")
public class ReportPage extends WebPage {
  private static final Logger log = LoggerFactory.getLogger(ReportPage.class);

  @SpringBean
  ReportController reportController;

  public ReportPage() throws Exception {
    log.info("Starting ReportPage");

    reportController.getReportData();
  }
}

WicketApplication.java

package org.jonblack;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
import org.wicketstuff.annotation.scan.AnnotatedMountScanner;


public class WicketApplication extends WebApplication
{
  @Override
  public Class<? extends WebPage> getHomePage()
  {
    return HomePage.class;
  }

  @Override
  public void init()
  {
    super.init();

    // Spring
    getComponentInstantiationListeners().add(
        new SpringComponentInjector(this));

    // Annotation-driven page mounting
    new AnnotatedMountScanner().scanPackage("org.jonblack").mount(this);
  }
}

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app 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"
  version="2.5">

  <display-name>sw-p2</display-name>

  <!-- Configuration loading -->
  <context-param>
    <param-name>contextClass</param-name>
    <param-value>
      org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    </param-value>
  </context-param>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>org.jonblack.AppConfig</param-value>
  </context-param>

  <!-- Spring -->
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>

  <!-- Wicket -->
  <filter>
    <filter-name>wicket.sw-p2</filter-name>
    <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
    <init-param>
      <param-name>applicationClassName</param-name>
      <param-value>org.jonblack.WicketApplication</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>wicket.sw-p2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

I've followed the Spring reference for Aspects but it's very thin on the ground when it comes to java configurations (and I do find the reference to be quite hard to follow).

I've also done the obligatory DuckDuckGo search and found only references to XML configurations.

On stackoverflow itself I have found this post which explains the same problem, but the cause in that instance was an incorrect annotation and incorrectly named applicationContext.xml file: neither of these helped resolve my situation. I tried using @Autowired instead of @SpringBean, and got the same NullPointerException. Given that the linked post resolved it without this change, I assume this isn't the right path to take.

1

There are 1 answers

3
M. Deinum On BEST ANSWER

The problem lies in your configuration.

@Bean
public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {
    AnnotationAwareAspectJAutoProxyCreator aop = new AnnotationAwareAspectJAutoProxyCreator();
    return aop;
 }

This bean leads to creation of a proxy of a proxy. The @EnableAspectJAutoProxy annotation already registers an AutoProxyCreator and due to the existence of multiple, different instances, this will lead to proxy duplication.

Next to that the additional declaration of your aspect might lead to 2 aspects being instantiated.

@Bean
public ExceptionLoggingAspect exceptionLoggingAspect() {
  return new ExceptionLoggingAspect();
}

Your @Aspect is also an @Component and as such will be detected by the @ComponentScan functionality.

Basically removing both beans should fix your problem.

public Future<String> getReportData() throws Exception {
    log.info("Entered into getReportData()");

    try {
      log.info("Throwing an exception");
      throw new Exception("Something went wrong");
    } catch(Exception ex) { // This catch blocks swallows the exception
      log.error("In catch block: {}", ex.getMessage()); 
    }
}

Next to your configuration problems, you also have a problem with your code. Basically your aspect is useless. You have a try/catch block in your code which swallows the Exception. This swallowing leads to a situation where your aspect never sees the exception and will never trigger. From your aspects point of view the exception never happened.

Either don't catch and simply let the Exception bubble up the stack or rethrow the Exception.