Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring 5.2 with cglib proxies: An illegal reflective access operation has occurred #26919

Closed
orange-buffalo opened this issue May 10, 2021 · 3 comments
Labels
for: external-project Needs a fix in external project

Comments

@orange-buffalo
Copy link

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:
    @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).

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label May 10, 2021
@jhoeller
Copy link
Contributor

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.

@jhoeller jhoeller self-assigned this May 10, 2021
@orange-buffalo
Copy link
Author

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.

@rstoyanchev rstoyanchev added the in: core Issues in core modules (aop, beans, core, context, expression) label Nov 10, 2021
@orange-buffalo
Copy link
Author

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

@sbrannen sbrannen added for: external-project Needs a fix in external project and removed status: waiting-for-triage An issue we've not yet triaged or decided on in: core Issues in core modules (aop, beans, core, context, expression) labels Jan 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: external-project Needs a fix in external project
Projects
None yet
Development

No branches or pull requests

5 participants