Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions spring-messaging/spring-messaging.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies {
testImplementation("org.skyscreamer:jsonassert")
testImplementation("org.xmlunit:xmlunit-assertj")
testImplementation("org.xmlunit:xmlunit-matchers")
testImplementation(project(":spring-core-test"))
testRuntimeOnly("com.sun.activation:jakarta.activation")
testRuntimeOnly("com.sun.xml.bind:jaxb-core")
testRuntimeOnly("com.sun.xml.bind:jaxb-impl")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.aot.hint.annotation.Reflective;

/**
* Annotation to declare a method on an RSocket service interface as an RSocket
* endpoint. The endpoint route is determined through the annotation attribute,
Expand Down Expand Up @@ -65,6 +67,7 @@
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Reflective(RSocketExchangeReflectiveProcessor.class)
public @interface RSocketExchange {

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.messaging.rsocket.service;

import java.util.HashSet;
import java.util.Set;

import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.ProxyHints;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

/**
* An AOT {@link BeanRegistrationAotProcessor} that detects the presence of
* {@link RSocketExchange @RSocketExchange} on methods and creates
* the required proxy hints.
* Based on {@code HttpExchangeBeanRegistrationAotProcessor}
*
* @author Sebastien Deleuze
* @author Olga Maciaszek-Sharma
* @since 6.0
* @see org.springframework.web.service.annotation.HttpExchangeBeanRegistrationAotProcessor
*/
class RSocketExchangeBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {

@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
Class<?> beanClass = registeredBean.getBeanClass();
Set<Class<?>> exchangeInterfaces = new HashSet<>();
for (Class<?> interfaceClass : ClassUtils.getAllInterfacesForClass(beanClass)) {
ReflectionUtils.doWithMethods(interfaceClass, method -> {
if (!exchangeInterfaces.contains(interfaceClass) &&
MergedAnnotations.from(method, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
.get(RSocketExchange.class).isPresent()) {
exchangeInterfaces.add(interfaceClass);
}
});
}
if (!exchangeInterfaces.isEmpty()) {
return new RSocketExchangeBeanRegistrationContribution(exchangeInterfaces);
}
return null;
}

private static class RSocketExchangeBeanRegistrationContribution implements BeanRegistrationAotContribution {

private final Set<Class<?>> rSocketExchangeInterfaces;

public RSocketExchangeBeanRegistrationContribution(Set<Class<?>> rSocketExchangeInterfaces) {
this.rSocketExchangeInterfaces = rSocketExchangeInterfaces;
}

@Override
public void applyTo(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode) {
ProxyHints proxyHints = generationContext.getRuntimeHints().proxies();
for (Class<?> httpExchangeInterface : this.rSocketExchangeInterfaces) {
proxyHints.registerJdkProxy(AopProxyUtils
.completeJdkProxyInterfaces(httpExchangeInterface));
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.messaging.rsocket.service;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.annotation.ReflectiveProcessor;
import org.springframework.core.MethodParameter;

/**
* A {@link ReflectiveProcessor} implementation for {@link RSocketExchange @RSocketExchange}
* annotated methods. In addition to registering reflection hints for invoking
* the annotated method, this implementation handles reflection-based
* binding for return types and parameters.
* Based on {@code HttpExchangeReflectiveProcessor}.
*
* @author Sebastien Deleuze
* @author Olga Maciaszek-Sharma
* @since 6.0
* @see org.springframework.web.service.annotation.HttpExchangeReflectiveProcessor
*/
public class RSocketExchangeReflectiveProcessor implements ReflectiveProcessor {

private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();

@Override
public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) {
if (element instanceof Method method) {
this.registerMethodHints(hints, method);
}
}

protected void registerMethodHints(ReflectionHints hints, Method method) {
hints.registerMethod(method, ExecutableMode.INVOKE);
for (Parameter parameter : method.getParameters()) {
// Also register non-annotated parameters to handle metadata
this.bindingRegistrar.registerReflectionHints(hints,
MethodParameter.forParameter(parameter).getGenericParameterType());
}
registerReturnTypeHints(hints, MethodParameter.forExecutable(method, -1));
}

protected void registerReturnTypeHints(ReflectionHints hints, MethodParameter returnTypeParameter) {
if (!void.class.equals(returnTypeParameter.getParameterType())) {
this.bindingRegistrar.registerReflectionHints(hints, returnTypeParameter
.getGenericParameterType());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.messaging.rsocket.service.RSocketExchangeBeanRegistrationAotProcessor
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.messaging.rsocket.service;


import org.junit.jupiter.api.Test;

import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.DecoratingProxy;
import org.springframework.lang.Nullable;
import org.springframework.messaging.handler.annotation.Payload;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

/**
* Tests for {@link RSocketExchangeBeanRegistrationAotProcessor}.
*
* @author Sebastien Deleuze
* @author Olga Maciaszek-Sharma
*/
class RSocketExchangeBeanRegistrationAotProcessorTests {

private final RSocketExchangeBeanRegistrationAotProcessor processor =
new RSocketExchangeBeanRegistrationAotProcessor();

private final GenerationContext generationContext = new TestGenerationContext();

@Test
void shouldProcessesAnnotatedInterface() {
process(AnnotatedInterface.class);
assertThat(RuntimeHintsPredicates.proxies().forInterfaces(AnnotatedInterface.class,
SpringProxy.class, Advised.class, DecoratingProxy.class))
.accepts(this.generationContext.getRuntimeHints());
}

@Test
void shouldSkipNonAnnotatedInterface() {
process(NonAnnotatedInterface.class);
assertThat(this.generationContext.getRuntimeHints().proxies().jdkProxyHints()).isEmpty();
}


void process(Class<?> beanClass) {
BeanRegistrationAotContribution contribution = createContribution(beanClass);
if (contribution != null) {
contribution.applyTo(this.generationContext, mock(BeanRegistrationCode.class));
}
}

@Nullable
private BeanRegistrationAotContribution createContribution(Class<?> beanClass) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition(beanClass.getName(), new RootBeanDefinition(beanClass));
return this.processor.processAheadOfTime(RegisteredBean.of(beanFactory, beanClass.getName()));

}

interface NonAnnotatedInterface {

void notExchange();
}

interface AnnotatedInterface {

@RSocketExchange
void exchange(@Payload String testPayload);
}

}


Loading