To reproduce, create a minimal Spring-based application (Spring Framework 5.2) with cglib proxies enabled and run it with Java 9+, for instance: * Create a project using https://start.spring.io/ using Spring Boot 2.3.x (no additional dependencies required). * Add a proxied bean: java @Bean @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) public Object theObject() { return new Object(); } * Start the application.

The following warning appears:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.cglib.core.ReflectUtils (.../.m2/repository/org/springframework/spring-core/5.2.14.RELEASE/spring-core-5.2.14.RELEASE.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of org.springframework.cglib.core.ReflectUtils
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

Debugging illegal access provides the following stacktrace:

WARNING: Illegal reflective access by org.springframework.cglib.core.ReflectUtils (.../.m2/repository/org/springframework/spring-core/5.2.14.RELEASE/spring-core-5.2.14.RELEASE.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:533)
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
    at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108)
    at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
    at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134)
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
    at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572)
    at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419)
    at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57)
    at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:205)
    at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110)
    at org.springframework.aop.scope.ScopedProxyFactoryBean.setBeanFactory(ScopedProxyFactoryBean.java:117)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeAwareMethods(AbstractAutowireCapableBeanFactory.java:1821)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1786)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:878)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:755)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:402)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:312)

It looks like a regression of #22674 (as far as I understand, Spring Framework 5.1+ should be fully Java 9 compatible).

Comment From: jhoeller

There are certain operations that simply do not work without opening the module or generally allowing "illegal" access on JDK 9+. With CGLIB proxies, it's very dependent on the specific class that we need to create a proxy for: Depending on the original class loader of that class and the target class loader to create the proxy in, we may have to enforce ClassLoader.defineClass as the only API available for such a job... and that's unfortunately a protected method and therefore "illegal".

So it's not a regression, just a case where we have no other way of solving the specified problem. Asking the framework to create a proxy for java.lang.Object is such an impossible task ;-) but also other scenarios where the original class lives in a sealed class loader. In some scenarios, the only way out may be to rearrange your class loaders - or to switch to interface-based proxies.

Comment From: orange-buffalo

Thank you for the detailed response, Juergen.

The real-world application we face the issue in is a standard-structure Spring Boot application. To my understanding, the framework and application classes are loaded by the same application classloader, so theoretically we should not face the issue where multiple classloaders are involved.

We were able to trace the problem to a particular bean in the third-party library. It is defined as following:

@Bean("application")
@ConditionalOnMissingBean(name = "application")
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public Object application() {
    return FacesContext.getCurrentInstance().getExternalContext().getContext();
}

Note the return type of the factory method - java.lang.Object. To me it was to some extent a surprise, that framework is using java.lang.Object class as a target class when creating a proxy, instead of the actual bean type. As a consequence, we fall into the attempt to proxy java.lang.Object, which is impossible without breaking the encapsulation, as you mentioned. When we override the bean definition with explicit return type:

@Bean("application")
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public ServletContext application() {
    return (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
}

the problem for this particular bean gets resolved. Probably there are good reasons why framework uses the declared type instead of the actual one when creating proxies, although for the users it is not obvious...

We also discovered another interesting case where an An illegal reflective access operation warning is thrown. The library has a bean defined:

import javax.faces.context.FacesContext;

@Bean("facesContext")
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
@ConditionalOnMissingBean
public FacesContext facesContext() {
    return FacesContext.getCurrentInstance();
}

which also throws this warning. When debugging the org.springframework.cglib.core.ReflectUtils#defineClass(), we can see that the first attempt to define a class using method handles (Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matches section) fails with java.lang.IllegalArgumentException: Class not in same package as lookup class, and so the fallback to reflective defineClass is executed. I am struggling to interpret this exception, does it mean that the generated proxy has a different package to its target class?

If this is something Spring Team is interested to take a deeper look at, I could prepare a reproducer with this particular library involved.

Comment From: orange-buffalo

The library maintainers claim this to be resolved in the newer versions: https://github.com/joinfaces/joinfaces/issues/1326. I will close this issue.