springspring-boottomcat9embedded-tomcat

Error while starting app with repositories - IllegalAccessError: class com.acme.$Proxy53 cannot access its superinterface com.acme.UserRepository


I am trying to run a standalone MVC + JPA application with embedded tomcat

But the application fails with the following error, tho I dont see any obvious reason for this failure. I believe this has something to do with the way Tomcat deals with class loaders

I am not sure how spring boot gets around this problem

Appreciate if anyone can shed light on why this error is thrown

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepositoryImpl': Initialization of bean failed; nested exception is java.lang.IllegalAccessError: class com.acme.$Proxy53 cannot access its superinterface com.acme.UserRepository
    ... 36 more
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:584)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:846)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:863)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546)
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:400)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:291)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:103)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4661)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5131)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1382)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1372)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
    at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:907)
    at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:831)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1382)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1372)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
    at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:907)
    at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:262)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.StandardService.startInternal(StandardService.java:423)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:933)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.startup.Tomcat.start(Tomcat.java:398)
    at com.acme.EmbeddedTomcatWebAppWithoutBoot.main(EmbeddedTomcatWebAppWithoutBoot.java:65)
Caused by: java.lang.IllegalAccessError: class com.acme.$Proxy53 cannot access its superinterface com.acme.UserRepository
    at java.lang.reflect.Proxy.defineClass0(Native Method)
    at java.lang.reflect.Proxy.access$300(Proxy.java:228)
    at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:642)
    at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:557)
    at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:230)
    at java.lang.reflect.WeakCache.get(WeakCache.java:127)
    at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:419)
    at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:719)
    at org.springframework.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:123)
    at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:473)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:352)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:301)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:434)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1749)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:576)
    ... 36 more

Sources used

EmbeddedTomcatWebAppWithoutBoot.java

package com.acme;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.repository.config.RepositoryConfiguration;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.SharedEntityManagerCreator;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Properties;

import static java.lang.String.valueOf;

public class EmbeddedTomcatWebAppWithoutBoot {
  private static final int PORT = 8080;

  public static void main(String[] args) throws LifecycleException {
    String appBase = ".";
    Tomcat tomcat = new Tomcat();
    tomcat.setBaseDir(createTempDir());
    tomcat.setPort(PORT);
    tomcat.getHost().setAppBase(appBase);
    tomcat.addWebapp("", ".");
    tomcat.getConnector(); // Trigger the creation of the default connector
    tomcat.start();
    ClassLoader classLoader = findContext(tomcat).getLoader().getClassLoader();
    Thread.currentThread().setContextClassLoader(classLoader);
    tomcat.getServer().await();
  }

  private static Context findContext(Tomcat tomcat) {
    for (Container child : tomcat.getHost().findChildren()) {
      if (child instanceof Context) {
        return (Context) child;
      }
    }
    throw new IllegalStateException("The host does not contain a Context");
  }

  // based on AbstractEmbeddedServletContainerFactory
  private static String createTempDir() {
    try {
      File tempDir = File.createTempFile("tomcat.", "." + PORT);
      tempDir.delete();
      tempDir.mkdir();
      tempDir.deleteOnExit();
      return tempDir.getAbsolutePath();
    } catch (IOException ex) {
      throw new RuntimeException(
          "Unable to create tempDir. java.io.tmpdir is set to " + System.getProperty("java.io.tmpdir"),
          ex
      );
    }
  }
}


class MainWebAppInitializer implements WebApplicationInitializer {
  @Override
  public void onStartup(ServletContext servletContext) {
    AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
    rootContext.register(DataAccessConfiguration.class, UserRepositoryImpl.class);
    servletContext.addListener(new ContextLoaderListener(rootContext));

    AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
    servletAppContext.register(WebMvcConfiguration.class);
    DispatcherServlet dispatcherServlet = new DispatcherServlet(servletAppContext);
    ServletRegistration.Dynamic servletRegistration = servletContext.addServlet("dispatcher", dispatcherServlet);
    servletRegistration.setLoadOnStartup(1);
    servletRegistration.addMapping("/");
  }
}


@EnableWebMvc
@Configuration
@Import(UserResource.class)
class WebMvcConfiguration {}


@RestController
class UserResource {

  private final UserRepository userRepository;

  public UserResource(UserRepository userRepository) {
    this.userRepository = userRepository;
  }

  @GetMapping
  ResponseEntity<List<User>> get() {
    return ResponseEntity.ok().body(userRepository.findAllUsers());
  }
}


@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
class UserRequest {
  private String name;
  private String authorityName;
}


@Configuration
@EnableTransactionManagement
class DataAccessConfiguration  {
  @Bean
  DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.H2)
        .setName("sample")
        .build();
  }

  @Bean
  LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
    LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();

    factoryBean.setDataSource(dataSource);

    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    factoryBean.setJpaVendorAdapter(vendorAdapter);
    factoryBean.setPackagesToScan(EmbeddedTomcatWebAppWithoutBoot.class.getPackage().getName());

    // set som extra properties hibernate
    Properties jpaProperties = new Properties();
    jpaProperties.setProperty("hibernate.show_sql", "true");
    jpaProperties.setProperty("hibernate.format_sql", "true");
    jpaProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
    jpaProperties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
    factoryBean.setJpaProperties(jpaProperties);

    return factoryBean;
  }

  @Bean
  JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    return new JpaTransactionManager(entityManagerFactory);
  }

  @Bean
  EntityManager entityManager(EntityManagerFactory entityManagerFactory) {
    return SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory);
  }

  @Bean
  PersistenceExceptionTranslationPostProcessor postProcessor() {
    return new PersistenceExceptionTranslationPostProcessor();
  }
}


interface UserRepository {
  List<User> findAllUsers();
}

@Repository
@Transactional
class UserRepositoryImpl implements UserRepository {
  private final EntityManager entityManager;

  public UserRepositoryImpl(EntityManager entityManager) {
    this.entityManager = entityManager;
  }

  @Override
  public List<User> findAllUsers() {
    return entityManager.createQuery("from User", User.class)
        .getResultList();
  }
}

@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
class User {
  @Id
  @GeneratedValue
  private int id;
  private String name;
}

build.gradle

ext {
    set('spring-boot.version', '2.1.2.RELEASE')
    // copied from above
    set('spring.version', '5.1.4.RELEASE')
    // async logging
    set('lmax-disruptor.version', '3.4.2')
    // misc utils
    set('unexceptional.version', '1.0.0')
    set('mapstruct.version', '1.3.0.Final')
    // tooling
    set('jetbrains-annotations.version', '16.0.2')
}

group 'com.acme'
version '1.0.0-SNAPSHOT'

apply plugin: 'java-library'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
    jcenter()
    maven {
        url 'https://repo.spring.io/libs-milestone'
    }
}

configurations {
    springBom
    developmentOnly

    // we want to make sure the dependencies bom ia available everywhere so that the dependencies can be resolved across all configurations
    compileOnly.extendsFrom(springBom)
    annotationProcessor.extendsFrom(springBom)
    testAnnotationProcessor.extendsFrom(springBom)
    api.extendsFrom(springBom)
    implementation.extendsFrom(springBom)

    runtimeClasspath {
        extendsFrom developmentOnly
    }

    // lets inherit everything
    testCompileOnly.extendsFrom(compileOnly)
}

dependencies {
    //*** bill of materials
    springBom platform("org.springframework.boot:spring-boot-dependencies:${project.'spring-boot.version'}")

    implementation 'org.springframework:spring-context'
    implementation 'org.springframework.data:spring-data-jpa'
    implementation 'org.hibernate:hibernate-core'
    implementation 'com.h2database:h2'

    // web
    implementation 'org.springframework:spring-web'
    implementation 'org.springframework:spring-webmvc'

    // jackson for json serialization
    implementation 'com.fasterxml.jackson.core:jackson-databind'
    implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8' // new java 8 classes like stream, OPtional
    implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' // new java date & time api
    implementation 'com.fasterxml.jackson.module:jackson-module-parameter-names' // custom modules within jackson

    // embedded server
    implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'

    // log4j2
    implementation 'org.apache.logging.log4j:log4j-core'
    implementation 'org.apache.logging.log4j:log4j-slf4j-impl'
    implementation 'org.apache.logging.log4j:log4j-jcl'
    implementation 'org.apache.logging.log4j:log4j-jul'
    implementation "com.lmax:disruptor:${project.'lmax-disruptor.version'}"

    // unexceptional for making sure code is not super polluted
    implementation "io.earcam:io.earcam.unexceptional:${project.'unexceptional.version'}"

    // junit
    testImplementation 'org.junit.jupiter:junit-jupiter-api'
    testImplementation 'org.junit.jupiter:junit-jupiter-engine'
    testImplementation 'org.junit.jupiter:junit-jupiter-params'
    testImplementation 'org.mockito:mockito-junit-jupiter'

    // test misc
    testImplementation 'org.assertj:assertj-core'
    testImplementation 'org.hamcrest:hamcrest-library'
    testImplementation 'org.mockito:mockito-junit-jupiter'

    // NOTE: Due to a bug as of now mapstruct needs to go before lombok
    // https://github.com/mapstruct/mapstruct/issues/1581

    // maspstruct
    implementation "org.mapstruct:mapstruct:${project.'mapstruct.version'}"
    implementation "org.mapstruct:mapstruct:${project.'mapstruct.version'}"
    annotationProcessor "org.mapstruct:mapstruct-processor:${project.'mapstruct.version'}"
    testAnnotationProcessor "org.mapstruct:mapstruct-processor:${project.'mapstruct.version'}"

    // lombok
    compileOnly('org.projectlombok:lombok')
    annotationProcessor('org.projectlombok:lombok')
}

configurations {
    all {
        // we need to exclude logging to avoid keeping both slf4j & log4j2 both in the same location
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
        // `spring-boot-starter-test` is pulling older version of junit. Lets just ignore it
        exclude group: 'junit', module: 'junit'
    }
}

The complete runnable gradle project can be be found here


Solution

  • The creation of the proxy is failing because the interface that defines the proxy's API, UserRepository, is package-private, access to a package-private type is prohibited across class loaders and the class loader being used to create the proxy is not the same as the class loader that was used to load UserRepository.

    There are a few different ways that you can fix the problem, including:

    1. Make UserRepository public
    2. Update MainWebAppInitializer to call rootContext.setClassLoader(getClass().getClassLoader())

    The second option is closer to what Spring Boot does and ensures that the class loader that loaded MainWebAppInitializer (and UserRepository) is used for proxy creation.