Skip to content

Commit

Permalink
ArC - register client proxies and subclasses for reflection if needed
Browse files Browse the repository at this point in the history
- introduce ReflectiveBeanClassBuildItem
- produce ReflectiveBeanClassBuildItem automatically for bean class
annotated with RegisterForReflection
- resolves #7507

(cherry picked from commit 5db6271)
  • Loading branch information
mkouba authored and gsmet committed Feb 7, 2022
1 parent 24c9730 commit 1988bd4
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,8 @@ public BeanContainerBuildItem generateResources(ArcConfig config, ArcRecorder re
BuildProducer<GeneratedClassBuildItem> generatedClass,
LiveReloadBuildItem liveReloadBuildItem,
BuildProducer<GeneratedResourceBuildItem> generatedResource,
BuildProducer<BytecodeTransformerBuildItem> bytecodeTransformer) throws Exception {
BuildProducer<BytecodeTransformerBuildItem> bytecodeTransformer,
List<ReflectiveBeanClassBuildItem> reflectiveBeanClasses) throws Exception {

for (ValidationErrorBuildItem validationError : validationErrors) {
for (Throwable error : validationError.getValues()) {
Expand All @@ -496,6 +497,8 @@ public BeanContainerBuildItem generateResources(ArcConfig config, ArcRecorder re
}

Consumer<BytecodeTransformer> bytecodeTransformerConsumer = new BytecodeTransformerConsumer(bytecodeTransformer);
Set<DotName> reflectiveBeanClassesNames = reflectiveBeanClasses.stream().map(ReflectiveBeanClassBuildItem::getClassName)
.collect(Collectors.toSet());

long start = System.currentTimeMillis();
List<ResourceOutput.Resource> resources = beanProcessor.generateResources(new ReflectionRegistration() {
Expand All @@ -509,6 +512,22 @@ public void registerField(FieldInfo fieldInfo) {
reflectiveFields.produce(new ReflectiveFieldBuildItem(fieldInfo));
}

@Override
public void registerClientProxy(DotName beanClassName, String clientProxyName) {
if (reflectiveBeanClassesNames.contains(beanClassName)) {
// Fields should never be registered for client proxies
reflectiveClasses.produce(new ReflectiveClassBuildItem(true, false, clientProxyName));
}
}

@Override
public void registerSubclass(DotName beanClassName, String subclassName) {
if (reflectiveBeanClassesNames.contains(beanClassName)) {
// Fields should never be registered for subclasses
reflectiveClasses.produce(new ReflectiveClassBuildItem(true, false, subclassName));
}
}

}, existingClasses.existingClasses, bytecodeTransformerConsumer,
config.shouldEnableBeanRemoval() && config.detectUnusedFalsePositives);
for (ResourceOutput.Resource resource : resources) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkus.arc.deployment;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;

import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;

/**
* This build item instructs ArC to produce a {@link ReflectiveClassBuildItem} for a client proxy and intercepred
* subclass generated for the given bean class.
*/
public final class ReflectiveBeanClassBuildItem extends MultiBuildItem {

private final DotName className;

public ReflectiveBeanClassBuildItem(ClassInfo classInfo) {
this(classInfo.name());
}

public ReflectiveBeanClassBuildItem(String className) {
this.className = DotName.createSimple(className);
}

public ReflectiveBeanClassBuildItem(DotName className) {
this.className = className;
}

public DotName getClassName() {
return className;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.quarkus.arc.deployment;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Type;

import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.runtime.annotations.RegisterForReflection;

public class ReflectiveBeanClassesProcessor {

@BuildStep
void implicitReflectiveBeanClasses(BuildProducer<ReflectiveBeanClassBuildItem> reflectiveBeanClasses,
BeanDiscoveryFinishedBuildItem beanDiscoveryFinished) {
DotName registerForReflection = DotName.createSimple(RegisterForReflection.class.getName());

for (BeanInfo classBean : beanDiscoveryFinished.beanStream().classBeans()) {
ClassInfo beanClass = classBean.getTarget().get().asClass();
AnnotationInstance annotation = beanClass.classAnnotation(registerForReflection);
if (annotation != null) {
Type[] targets = annotation.value("targets") != null ? annotation.value("targets").asClassArray()
: new Type[] {};
String[] classNames = annotation.value("classNames") != null ? annotation.value("classNames").asStringArray()
: new String[] {};
if (targets.length == 0 && classNames.length == 0) {
reflectiveBeanClasses.produce(new ReflectiveBeanClassBuildItem(beanClass));
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ public static Builder builder() {
private final boolean generateSources;
private final boolean allowMocking;
private final boolean transformUnproxyableClasses;
private final boolean failOnInterceptedPrivateMethod;
private final List<Function<BeanInfo, Consumer<BytecodeCreator>>> suppressConditionGenerators;

// This predicate is used to filter annotations for InjectionPoint metadata
Expand All @@ -87,7 +86,6 @@ private BeanProcessor(Builder builder) {
this.generateSources = builder.generateSources;
this.allowMocking = builder.allowMocking;
this.transformUnproxyableClasses = builder.transformUnproxyableClasses;
this.failOnInterceptedPrivateMethod = builder.failOnInterceptedPrivateMethod;
this.suppressConditionGenerators = builder.suppressConditionGenerators;

// Initialize all build processors
Expand Down Expand Up @@ -198,13 +196,29 @@ public List<Resource> generateResources(ReflectionRegistration reflectionRegistr
if (SpecialType.BEAN.equals(resource.getSpecialType())) {
if (bean.getScope().isNormal()) {
// Generate client proxy
resources.addAll(
clientProxyGenerator.generate(bean, resource.getFullyQualifiedName(),
bytecodeTransformerConsumer, transformUnproxyableClasses));
Collection<Resource> proxyResources = clientProxyGenerator.generate(bean,
resource.getFullyQualifiedName(),
bytecodeTransformerConsumer, transformUnproxyableClasses);
if (bean.isClassBean()) {
for (Resource r : proxyResources) {
if (r.getSpecialType() == SpecialType.CLIENT_PROXY) {
reflectionRegistration.registerClientProxy(bean.getBeanClass(), r.getFullyQualifiedName());
break;
}
}
}
resources.addAll(proxyResources);
}
if (bean.isSubclassRequired()) {
resources.addAll(
subclassGenerator.generate(bean, resource.getFullyQualifiedName()));
Collection<Resource> subclassResources = subclassGenerator.generate(bean,
resource.getFullyQualifiedName());
for (Resource r : subclassResources) {
if (r.getSpecialType() == SpecialType.SUBCLASS) {
reflectionRegistration.registerSubclass(bean.getBeanClass(), r.getFullyQualifiedName());
break;
}
}
resources.addAll(subclassResources);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.quarkus.arc.impl.Mockable;
import io.quarkus.arc.processor.BeanGenerator.ProviderType;
import io.quarkus.arc.processor.ResourceOutput.Resource;
import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.DescriptorUtils;
Expand Down Expand Up @@ -77,9 +78,6 @@ public ClientProxyGenerator(Predicate<DotName> applicationClassPredicate, boolea
Collection<Resource> generate(BeanInfo bean, String beanClassName,
Consumer<BytecodeTransformer> bytecodeTransformerConsumer, boolean transformUnproxyableClasses) {

ResourceClassOutput classOutput = new ResourceClassOutput(applicationClassPredicate.test(bean.getBeanClass()),
generateSources);

ProviderType providerType = new ProviderType(bean.getProviderType());
ClassInfo providerClass = getClassByName(bean.getDeployment().getBeanArchiveIndex(), providerType.name());
String baseName = getBaseName(bean, beanClassName);
Expand All @@ -89,6 +87,9 @@ Collection<Resource> generate(BeanInfo bean, String beanClassName,
return Collections.emptyList();
}

ResourceClassOutput classOutput = new ResourceClassOutput(applicationClassPredicate.test(bean.getBeanClass()),
name -> name.equals(generatedName) ? SpecialType.CLIENT_PROXY : null, generateSources);

// Foo_ClientProxy extends Foo implements ClientProxy
List<String> interfaces = new ArrayList<>();
String superClass = Object.class.getName();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.arc.processor;

import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.MethodInfo;

Expand All @@ -9,15 +10,31 @@ public interface ReflectionRegistration {

void registerField(FieldInfo fieldInfo);

/**
* Register the client proxy for the given bean class if it's needed.
*
* @param beanClassName
* @param clientProxyName
*/
default void registerClientProxy(DotName beanClassName, String clientProxyName) {
}

/**
* Register the intercepted subclass for the given bean class if it's needed.
*
* @param beanClassName
* @param subclassName
*/
default void registerSubclass(DotName beanClassName, String subclassName) {
}

ReflectionRegistration NOOP = new ReflectionRegistration() {
@Override
public void registerMethod(MethodInfo methodInfo) {

}

@Override
public void registerField(FieldInfo fieldInfo) {

}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,16 @@ default String getFullyQualifiedName() {
enum Type {
JAVA_CLASS,
JAVA_SOURCE,
SERVICE_PROVIDER,
SERVICE_PROVIDER
}

enum SpecialType {
BEAN,
INTERCEPTOR_BEAN,
DECORATOR_BEAN,
OBSERVER;
OBSERVER,
CLIENT_PROXY,
SUBCLASS
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.quarkus.arc.processor.BeanInfo.InterceptionInfo;
import io.quarkus.arc.processor.Methods.MethodKey;
import io.quarkus.arc.processor.ResourceOutput.Resource;
import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType;
import io.quarkus.gizmo.AssignableResultHandle;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.CatchBlockCreator;
Expand Down Expand Up @@ -99,9 +100,6 @@ public SubclassGenerator(AnnotationLiteralProcessor annotationLiterals, Predicat

Collection<Resource> generate(BeanInfo bean, String beanClassName) {

ResourceClassOutput classOutput = new ResourceClassOutput(applicationClassPredicate.test(bean.getBeanClass()),
generateSources);

Type providerType = bean.getProviderType();
ClassInfo providerClass = getClassByName(bean.getDeployment().getBeanArchiveIndex(), providerType.name());
String providerTypeName = providerClass.name().toString();
Expand All @@ -111,6 +109,10 @@ Collection<Resource> generate(BeanInfo bean, String beanClassName) {
return Collections.emptyList();
}

ResourceClassOutput classOutput = new ResourceClassOutput(applicationClassPredicate.test(bean.getBeanClass()),
name -> name.equals(generatedName) ? SpecialType.SUBCLASS : null,
generateSources);

// Foo_Subclass extends Foo implements Subclass
ClassCreator subclass = ClassCreator.builder().classOutput(classOutput).className(generatedName)
.superClass(providerTypeName).interfaces(Subclass.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.it.arc;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;

import io.quarkus.arc.Lock;
import io.quarkus.arc.Unremovable;
import io.quarkus.runtime.annotations.RegisterForReflection;

@RegisterForReflection
@Unremovable
@Lock
@ApplicationScoped
public class IntercepredNormalScopedFoo {

private int val;

public int ping() {
return val;
}

@PostConstruct
void init() {
val = 42;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.quarkus.it.arc;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import io.quarkus.arc.ClientProxy;

@Path("/reflective-bean")
public class ReflectiveBeanEndpoint {

@Inject
IntercepredNormalScopedFoo foo;

@Path("proxy")
@GET
public int proxyPing() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
return callPingViaReflection(foo);
}

@Path("subclass")
@GET
public int subclassPing() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
IntercepredNormalScopedFoo subclass = (IntercepredNormalScopedFoo) ((ClientProxy) foo).arc_contextualInstance();
return callPingViaReflection(subclass);
}

private int callPingViaReflection(IntercepredNormalScopedFoo foo) throws NoSuchMethodException, SecurityException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method pingMethod = foo.getClass().getDeclaredMethod("ping");
return (int) pingMethod.invoke(foo);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.it.main;

import io.quarkus.test.junit.QuarkusIntegrationTest;

@QuarkusIntegrationTest
class ReflectiveBeanITCase extends ReflectiveBeanTest {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.it.main;

import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;

@QuarkusTest
class ReflectiveBeanTest {

@Test
public void testReflectiveAccess() {
RestAssured.when().get("/reflective-bean/proxy").then()
.body(is("42"));
RestAssured.when().get("/reflective-bean/subclass").then()
.body(is("42"));
}

}

0 comments on commit 1988bd4

Please sign in to comment.