1

我正在尝试使用嵌入式 tomcat 运行独立的 MVC + JPA 应用程序

但是应用程序失败并出现以下错误,但我没有看到任何明显的失败原因。我相信这与 Tomcat 处理类加载器的方式有关

我不确定弹簧靴如何解决这个问题

感谢是否有人可以阐明为什么会引发此错误

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

使用的来源

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'
    }
}

完整的可运行 gradle 项目可以在这里找到

4

1 回答 1

3

代理的创建失败,因为定义代理 API 的接口UserRepository是包私有的,跨类加载器禁止访问包私有类型,并且用于创建代理的类加载器与用于加载的类加载器UserRepository

有几种不同的方法可以解决问题,包括:

  1. UserRepository公开_
  2. 更新MainWebAppInitializer调用rootContext.setClassLoader(getClass().getClassLoader())

第二个选项更接近 Spring Boot 所做的,并确保加载MainWebAppInitializer(and UserRepository) 的类加载器用于创建代理。

于 2019-02-18T20:17:54.087 回答