spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.68k stars 38.15k forks source link

JPMS: export to spring.core required #33373

Closed xenoterracide closed 2 months ago

xenoterracide commented 3 months ago

This is hopefully just a lack of understanding on how spring "cglib" support is supposed to work (in quotes because it's not cglib anymore right?) or how exactly a reflective access needs exports instead of just opens. Sorry if this isn't simply an understanding problem vs an issue which could be fixed to allow my module to work without adding an exports ... to spring...

// © Copyright 2024 Caleb Cushing
// SPDX-License-Identifier: AGPL-3.0-or-later

package com.xenoterracide.jpa.transaction;

import com.xenoterracide.jpa.annotation.TransactionScope;
import com.xenoterracide.jpa.util.Constants;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.jspecify.annotations.NonNull;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.support.SimpleTransactionScope;

/**
 * The type Transaction bean post processor.
 */
@Configuration
class TransactionBeanPostProcessor implements BeanFactoryPostProcessor {

  private static final ZoneId UTC = ZoneId.of("UTC");

  @Bean
  @TransactionScope
  Instant instantNow() {
    return Instant.now();
  }

  @Bean
  @TransactionScope
  ZonedDateTime zonedDateTimeNow(@NonNull Instant instantNow) {
    return instantNow.atZone(UTC);
  }

  /**
   * Offset date time now offset date time.
   *
   * @param zonedDateTimeNow the zoned date time now
   * @return the offset date time
   */
  @Bean
  @TransactionScope
  OffsetDateTime offsetDateTimeNow(@NonNull ZonedDateTime zonedDateTimeNow) {
    return zonedDateTimeNow.toOffsetDateTime();
  }

  @Override
  public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException {
    beanFactory.registerScope(Constants.TRANSACTION_SCOPE, new SimpleTransactionScope());
  }
}
import org.jspecify.annotations.NullMarked;

/**
 * JPA utilities.
 */
@NullMarked module com.xenoterracide.jpa {
  exports com.xenoterracide.jpa;
  exports com.xenoterracide.jpa.annotation;
  exports com.xenoterracide.jpa.util;
  opens com.xenoterracide.jpa to org.hibernate.orm.core, spring.core;
  opens com.xenoterracide.jpa.transaction to spring.core;

  requires java.base;
  requires org.apache.commons.lang3;
  requires spring.data.commons;
  requires spring.beans;
  requires spring.context;
  requires spring.tx;
  requires org.hibernate.orm.envers;

  requires static transitive org.jspecify;
  requires static com.xenoterracide.tools.java;
  requires static jakarta.annotation;
  requires transitive jakarta.persistence;
  requires transitive jakarta.validation;
  requires transitive com.xenoterracide.model;
}

Adding this fixes it.

  exports com.xenoterracide.jpa.transaction to spring.beans, spring.context;
    Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.xenoterracide.jpa.transaction.TransactionBeanPostProcessor$$SpringCGLIB$$0]: Is the constructor accessible?

        at spring.beans@6.1.11/org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.11.jar:?]
        at spring.beans@6.1.11/org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205) ~[spring-beans-6.1.11.jar:?]
        at spring.context@6.1.11/org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:202) ~[spring-context-6.1.11.jar:?]
        at spring.context@6.1.11/org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:789) ~[spring-context-6.1.11.jar:?]
        at spring.context@6.1.11/org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:607) ~[spring-context-6.1.11.jar:?]
        at spring.boot@3.3.2/org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.2.jar:?]
        at spring.boot@3.3.2/org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.2.jar:?]
        at spring.boot@3.3.2/org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.2.jar:?]
        at spring.boot.test@3.3.2/org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:137) ~[spring-boot-test-3.3.2.jar:?]
        at spring.core@6.1.11/org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) ~[spring-core-6.1.11.jar:?]
        at spring.core@6.1.11/org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[spring-core-6.1.11.jar:?]
        at spring.boot@3.3.2/org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1463) ~[spring-boot-3.3.2.jar:?]
        at spring.boot.test@3.3.2/org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:553) ~[spring-boot-test-3.3.2.jar:?]
        at spring.boot.test@3.3.2/org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:137) ~[spring-boot-test-3.3.2.jar:?]
        at spring.boot.test@3.3.2/org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:108) ~[spring-boot-test-3.3.2.jar:?]
        at spring.test@6.1.11/org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225) ~[spring-test-6.1.11.jar:?]
        at spring.test@6.1.11/org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152) ~[spring-test-6.1.11.jar:?]
        ... 130 more
    Caused by: java.lang.IllegalAccessException: class org.springframework.beans.BeanUtils (in module spring.beans) cannot access class com.xenoterracide.jpa.transaction.TransactionBeanPostProcessor$$SpringCGLIB$$0 (in module com.xenoterracide.jpa) because module com.xenoterracide.jpa does not export com.xenoterracide.jpa.transaction to module spring.beans

    Caused by: java.lang.IllegalAccessException: class org.springframework.beans.BeanUtils (in module spring.beans) cannot access class com.xenoterracide.jpa.transaction.TransactionBeanPostProcessor$$SpringCGLIB$$0 (in module com.xenoterracide.jpa) because module com.xenoterracide.jpa does not export com.xenoterracide.jpa.transaction to module spring.beans
        at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:394) ~[?:?]
        at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:714) ~[?:?]
        at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:495) ~[?:?]
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) ~[?:?]
        at spring.beans@6.1.11/org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:195) ~[spring-beans-6.1.11.jar:?]
        at spring.beans@6.1.11/org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:94) ~[spring-beans-6.1.11.jar:?]
        at spring.beans@6.1.11/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1331) ~[spring-beans-6.1.11.jar:?]
        at spring.beans@6.1.11/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222) ~[spring-beans-6.1.11.jar:?]
        at spring.beans@6.1.11/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562) ~[spring-beans-6.1.11.jar:?]
        at spring.beans@6.1.11/org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.11.jar:?]
        at spring.beans@6.1.11/org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.11.jar:?]
        at spring.beans@6.1.11/org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.11.jar:?]
        at spring.beans@6.1.11/org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.11.jar:?]
        at spring.beans@6.1.11/org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205) ~[spring-beans-6.1.11.jar:?]
        at spring.context@6.1.11/org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:202) ~[spring-context-6.1.11.jar:?]
        at spring.context@6.1.11/org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:789) ~[spring-context-6.
    1.11.jar:?]
        at spring.context@6.1.11/org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:607) ~[spring-context-6.1.11.jar:?]
        at spring.boot@3.3.2/org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.2.jar:?]
        at spring.boot@3.3.2/org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.2.jar:?]
        at spring.boot@3.3.2/org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.2.jar:?]
        at spring.boot.test@3.3.2/org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:137) ~[spring-boot-test-3.3.2.jar:?]
        at spring.core@6.1.11/org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) ~[spring-core-6.1.11.jar:?]
        at spring.core@6.1.11/org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[spring-core-6.1.11.jar:?]
        at spring.boot@3.3.2/org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1463) ~[spring-boot-3.3.2.jar:?]
        at spring.boot.test@3.3.2/org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:553) ~[spring-boot-test-3.3.2.jar:?]
        at spring.boot.test@3.3.2/org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:137) ~[spring-boot-test-3.3.2.jar:?]
        at spring.boot.test@3.3.2/org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:108) ~[spring-boot-test-3.3.2.jar:?]
        at spring.test@6.1.11/org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225) ~[spring-test-6.1.11.jar:?]
        at spring.test@6.1.11/org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152) ~[spring-test-6.1.11.jar:?]
        ... 130 more
 Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionBeanPostProcessor' defined in URL [jar:file:///home/xeno/IdeaProjects/spring-app-commons/module/jpa/build/libs/jpa.jar!/com/xenoterracide/jpa/transaction/TransactionBeanPostProcessor.class]: class org.springframework.context.annotation.ConfigurationClassEnhancer$BeanFactoryAwareMethodInterceptor (in module spring.context) cannot access class com.xenoterracide.jpa.transaction.TransactionBeanPostProcessor$$SpringCGLIB$$0 (in module com.xenoterracide.jpa) because module com.xenoterracide.jpa does not export com.xenoterracide.jpa.transaction to module spring.context

        at spring.boot.test@3.3.2/org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:108) ~[spring-boot-test-3.3.2.jar:?]
        at spring.test@6.1.11/org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225) ~[spring-test-6.1.11.jar:?]
        at spring.test@6.1.11/org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152) ~[spring-test-6.1.11.jar:?]
        ... 130 more
    Caused by: java.lang.IllegalAccessException: class org.springframework.context.annotation.ConfigurationClassEnhancer$BeanFactoryAwareMethodInterceptor (in module spring.context) cannot access class com.xenoterracide.jpa.transaction.TransactionBeanPostProcessor$$SpringCGLIB$$0 (in module com.xenoterracide.jpa) because module com.xenoterracide.jpa does not export com.xenoterracide.jpa.transaction to module spring.context
        at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:394) ~[?:?]

org.springframework:spring-core:6.1.11


------------------------------------------------------------
Gradle 8.9
------------------------------------------------------------

Build time:    2024-07-11 14:37:41 UTC
Revision:      d536ef36a19186ccc596d8817123e5445f30fef8

Kotlin:        1.9.23
Groovy:        3.0.21
Ant:           Apache Ant(TM) version 1.10.13 compiled on January 4 2023
Launcher JVM:  21.0.4 (Eclipse Adoptium 21.0.4+7-LTS)
Daemon JVM:    /home/xeno/.asdf/installs/java/temurin-21.0.4+7.0.LTS (no JDK specified, using current Java home)
OS:            Linux 6.6.41-1-MANJARO amd64
sbrannen commented 2 months ago

Hi @xenoterracide,

For starters, it's still CGLIB just rather our port of CGLIB which we now maintain.

Regarding the exception you've encountered, ConfigurationClassEnhancer$BeanFactoryAwareMethodInterceptor likely signals that there was an issue related to a dynamic subclass of your @Configuration class.

To avoid that, try @Configuration(proxyBeanMethods = false) and let us know if that solves your problem.

As a side note, we don't recommend that a @Configuration class implement BeanFactoryPostProcessor. Instead, a @Configuration class should contain a static @Bean method that returns an instance of your BeanFactoryPostProcessor.

xenoterracide commented 2 months ago

As a side note, we don't recommend that a @Configuration class implement BeanFactoryPostProcessor. Instead, a @Configuration class should contain a static @Bean method that returns an instance of your BeanFactoryPostProcessor.

for some reason I never considered that, or using statics...

For starters, it's still CGLIB just rather our port of CGLIB which we now maintain.

well now I wonder if bytebuddy has the same issue

To avoid that, try @Configuration(proxyBeanMethods = false) and let us know if that solves your problem.

It does, although I think I prefer the export because I can solve that in 1 place instead of N, or in theory at least less places (since I have N libraries). I have @SpringBootApplication(proxyBeanMethods = false) so I figured I wouldn't have to do it again. For some, unknown, reason I presumed that would be a default; like the component scanning package is for the whole app by default.

I still don't understand why the export helps here. Might be worth documenting these options.

xenoterracide commented 2 months ago

well now I wonder if bytebuddy has the same issue

and why I don't seem to have it with hibernate, which I would think has to accomplish something similar with its magic.

xenoterracide commented 2 months ago

I think this is just a duplicate of #32671 re-open if you disagree