Webflux EndpointConfiguration RouterFunctions.route error - spring

I am facing an issue with my code when using the RouterFunctions.route method. The code is failing to compile, not sure what I am doing wrong
error description :
Multiple markers at this line
- The method route(RequestPredicate, HandlerFunction<T>) in the type RouterFunctions is not
applicable for the arguments (RequestPredicate, handler::getById)
- The type CustomerProfileHandler does not define getById(ServerRequest) that is applicable here
- The method route(RequestPredicate, HandlerFunction<T>) in the type RouterFunctions is not
applicable for the arguments (RequestPredicate, handler::getById)
Endpoint Configuration Class
#Configuration
public class CustomerProfileEndpointConfiguration {
#Bean
public RouterFunction<ServerResponse> routes(CustomerProfileHandler handler) {
return RouterFunctions.route(i(GET("/customer/{customerId}")), handler::getById);
}
private static RequestPredicate i(RequestPredicate target) {
return new CaseInsensitiveRequestPredicate(target);
}
Handler Class
#Component
public class CustomerProfileHandler {
// <1>
private final CustomerProfileService profileService;
CustomerProfileHandler(CustomerProfileService profileService) {
this.profileService = profileService;
}
// <2>
Mono<ServerResponse> getById(ServerRequest r) {
return defaultReadResponse(this.profileService.get(id(r)));
}
private static Mono<ServerResponse> defaultReadResponse(Publisher<CustomerProfile> profiles) {
return ServerResponse
.ok()
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(profiles, CustomerProfile.class);
}
private static String id(ServerRequest r) {
return r.pathVariable("customerId");
}

Welcome to SO
your Mono<ServerResponse> getById(ServerRequest r) should be a public one
public Mono<ServerResponse> getById(ServerRequest r) {
return defaultReadResponse(this.profileService.get(id(r)));
}
reference doc:
https://github.com/spring-projects/spring-framework/blob/master/src/docs/asciidoc/web/webflux-functional.adoc

Related

How to test a try...finally method only been called once in SpringBoot?

I am following this article to implement a database read/write separation feature by calling different methods. However, I got the error:
Missing method call for verify(mock) here: verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY);
when doing the testing.
My test case is trying to verify DatabaseEnvironment.READONLY has been set once when using TransactionReadonlyAspect AOP annotation:
// TransactionReadonlyAspectTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {LoadServiceImpl.class, TransactionReadonlyAspect.class})
public class TransactionReadonlyAspectTest {
#Autowired
private TransactionReadonlyAspect transactionReadonlyAspect;
#MockBean
private LoadServiceImpl loadService;
#Test
public void testReadOnlyTransaction() throws Throwable {
ProceedingJoinPoint mockProceedingJoinPoint = mock(ProceedingJoinPoint.class);
Transactional mockTransactional = mock(Transactional.class);
DatabaseContextHolder spyDatabaseContextHolder = mock(DatabaseContextHolder.class);
when(mockTransactional.readOnly()).thenReturn(true);
when(loadService.findById(16)).thenReturn(null);
when(mockProceedingJoinPoint.proceed()).thenAnswer(invocation -> loadService.findById(16));
transactionReadonlyAspect.proceed(mockProceedingJoinPoint, mockTransactional);
verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY); // got the error: Missing method call for verify(mock)
verify(loadService, times(1)).findById(16);
assertEquals(DatabaseContextHolder.getEnvironment(), DatabaseEnvironment.UPDATABLE);
}
}
//TransactionReadonlyAspect.java
#Aspect
#Component
#Order(0)
#Slf4j
public class TransactionReadonlyAspect {
#Around("#annotation(transactional)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint,
org.springframework.transaction.annotation.Transactional transactional) throws Throwable {
try {
if (transactional.readOnly()) {
log.info("Inside method " + proceedingJoinPoint.getSignature());
DatabaseContextHolder.set(DatabaseEnvironment.READONLY);
}
return proceedingJoinPoint.proceed();
} finally {
DatabaseContextHolder.reset();
}
}
}
// DatabaseContextHolder.java
public class DatabaseContextHolder {
private static final ThreadLocal<DatabaseEnvironment> CONTEXT = new ThreadLocal<>();
public static void set(DatabaseEnvironment databaseEnvironment) {
CONTEXT.set(databaseEnvironment);
}
public static DatabaseEnvironment getEnvironment() {
DatabaseEnvironment context = CONTEXT.get();
System.out.println("context: " + context);
return CONTEXT.get();
}
public static void reset() {
CONTEXT.set(DatabaseEnvironment.UPDATABLE);
}
}
//DatabaseEnvironment.java
public enum DatabaseEnvironment {
UPDATABLE,READONLY
}
// LoadServiceImpl.java
#Service
public class LoadServiceImpl implements LoadService {
#Override
#Transactional(readOnly = true)
public LoadEntity findById(Integer Id) {
return this.loadDAO.findById(Id);
}
...
}
I just want to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) has been used once then in the TransactionReadonlyAspect finally block it will be reset to DatabaseEnvironment.UPDATABLE which make sense.
However, how to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) gets called once? Why does this error occur? Is there a better way to test TransactionReadonlyAspect?

JUNIT 5: Inject spring components to custom TestTemplateInvocationContextProvider

Is there a way in JUnit Jupiter (JUnit 5) that makes it possible to inject Spring components into a TestTemplateInvocationContextProvider?
Yes, if you register your TestTemplateInvocationContextProvider as a bean in the Spring ApplicationContext loaded for your test class, you can then have the provider #Autowired into a field and registered as a JUnit Jupiter extension using #RegisterExtension. The trick is that you'll need to use the per-class test instance lifecycle mode in order for the provider to be registered early enough for JUnit Jupiter to use it.
The following is a modified version of TestTemplateDemo from the JUnit 5 User Guide.
The tests pass "as is", but you can remove the // from the #Bean declaration for the baz bean to see a test fail.
#SpringJUnitConfig
#TestInstance(Lifecycle.PER_CLASS)
class TestTemplateDemo {
#Autowired
#RegisterExtension
TestTemplateInvocationContextProvider testTemplateInvocationContextProvider;
#TestTemplate
void testTemplate(String parameter) {
assertTrue("foo".equals(parameter) || "bar".equals(parameter));
}
#Configuration
static class Config {
#Bean
String foo() {
return "foo";
}
#Bean
String bar() {
return "bar";
}
// #Bean
String baz() {
return "baz";
}
#Bean
TestTemplateInvocationContextProvider myTestTemplateInvocationContextProvider(
List<String> parameters) {
return new MyTestTemplateInvocationContextProvider(parameters);
}
}
public static class MyTestTemplateInvocationContextProvider
implements TestTemplateInvocationContextProvider {
private final List<String> parameters;
public MyTestTemplateInvocationContextProvider(List<String> parameters) {
this.parameters = parameters;
}
#Override
public boolean supportsTestTemplate(ExtensionContext context) {
return true;
}
#Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
ExtensionContext context) {
return this.parameters.stream().map(p -> invocationContext(p));
}
private TestTemplateInvocationContext invocationContext(String parameter) {
return new TestTemplateInvocationContext() {
#Override
public String getDisplayName(int invocationIndex) {
return parameter;
}
#Override
public List<Extension> getAdditionalExtensions() {
return Collections.singletonList(new ParameterResolver() {
#Override
public boolean supportsParameter(
ParameterContext parameterContext,
ExtensionContext extensionContext) {
return parameterContext.getParameter().getType().equals(
String.class);
}
#Override
public Object resolveParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return parameter;
}
});
}
};
}
}
}

Spring 4 - Spring retry 1.2.1.RELEASE #Recover not working

I have created an adapterImpl class that will retry a method with an objA but if it throws an exception(hardcoded to throw) it will call recover method - which will again call the method with objB.
My problem is - The #Recover method is not called. I am not sure what I am doing wrong here.
Spring version - 4.3.5.RELEASE
Spring retry - 1.2.1.RELEASE
My Config class -
#Configuration
#EnableRetry
public class ConfigClass {
#Bean
public ClassTest beanA(){
ClassTest obj = new ClassTest();
obj.setProp(5);
return obj;
}
#Bean
public ClassTest beanB(){
ClassTest obj = new ClassTest();
obj.setProp(10);
return obj;
}
#Bean("adapterImpl")
public AdapterInterfaceImpl adapter(){
AdapterInterfaceImpl obj = new AdapterInterfaceImpl();
return obj;
}
}
My AdapterInterfaceImpl class -
public class AdapterInterfaceImpl implements AdapterInterface{
#Autowired
#Qualifier("beanA")
private ClassTest objA;
#Autowired
#Qualifier("beanB")
private ClassTest objB;
public ClassTest getObjA() {
return objA;
}
public void setObjA(ClassTest objA) {
this.objA = objA;
}
public ClassTest getObjB() {
return objB;
}
public void setObjB(ClassTest objB) {
this.objB = objB;
}
#Retryable(maxAttempts = 3, include = Exception.class, backoff = #Backoff(delay = 2000))
public int getValue(int val) throws Exception{
System.out.println("obj A get Value");
return getValue(objA,val);
}
public int getValue(ClassTest obj, int val) throws Exception{
System.out.println("get Value");
if(obj==objA){
throw new Exception("This is msg");
}
return obj.methodA(val);
}
#Recover
public int getValue(Exception e, int val){
System.out.println("Recover get Value");
try{
return getValue(objB,val);
}catch(Exception e1){
return 0;
}
}
My ClassTest class -
public class ClassTest {
private int prop;
public int getProp() {
return prop;
}
public void setProp(int prop) {
this.prop = prop;
}
public int methodA(int x){
return x+prop;
}
}
My class with main method -
public class App
{
public static void main( String[] args )
{
AbstractApplicationContext context = new
AnnotationConfigApplicationContext(ConfigClass.class);
AdapterInterface adapter = (AdapterInterface)
context.getBean("adapterImpl");
try {
System.out.println(adapter.getValue(3));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
My output is not showing any retry nor recovery -
A get Value
get Value
obj A get Value
get Value
obj A get Value
get Value
org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method; nested exception is java.lang.Exception: This is msg
Spring Retry uses AOP, internal calls (from getValue(int) to getValue(ClassTest, int)) won't go through the proxy.
You have to put the #Retryable on the method that is called externally so that the proxy can intercept the call and apply the retry logic.
There is a similar issue reported in https://github.com/spring-projects/spring-retry/issues/75
So #EnableRetry(proxyTargetClass=true) works as it is now able to find the recovery method in the implementation class.

RabbitMQ separate listeners by type

I have POJO which represents a message to Rabbit MQ. There is an integer which is responsible for the type of the message(whether it's update, remove, add and so on):
public class Message {
private String field1;
private String field2;
private Integer type;
...
<some other fields>
}
I have a consumer which accepts such messages in my spring boot app. So in order to handle each type separately, I have to add some switch/case construction in my code.
Are there any more clear solutions for such case?
You can use Spring Integration with a router...
Rabbit Inbound channel adapter -> router ->
Where the router routes to a different service activator (method) based on the type.
EDIT
Here's an example:
#SpringBootApplication
public class So47272336Application {
public static void main(String[] args) {
SpringApplication.run(So47272336Application.class, args);
}
#Bean
public ApplicationRunner runner(RabbitTemplate rabbitTemplate) {
return args -> {
rabbitTemplate.convertAndSend("my.queue", new Domain(1, "foo"));
rabbitTemplate.convertAndSend("my.queue", new Domain(2, "bar"));
rabbitTemplate.convertAndSend("my.queue", new Domain(3, "baz"));
};
}
#Bean
public Queue queue() {
return new Queue("my.queue");
}
#Bean
public IntegrationFlow flow(ConnectionFactory connectionFactory) {
return IntegrationFlows.from(Amqp.inboundAdapter(connectionFactory, "my.queue"))
.route("payload.type", r -> r
.subFlowMapping("1", f -> f.handle("bean", "add"))
.subFlowMapping("2", f -> f.handle("bean", "remove"))
.subFlowMapping("3", f -> f.handle("bean", "update")))
.get();
}
#Bean
public MyBean bean() {
return new MyBean();
}
public static class MyBean {
public void add(Domain object) {
System.out.println("Adding " + object);
}
public void remove(Domain object) {
System.out.println("Removing " + object);
}
public void update(Domain object) {
System.out.println("Updating " + object);
}
}
public static class Domain implements Serializable {
private final Integer type;
private final String info;
public Domain(Integer type, String info) {
this.type = type;
this.info = info;
}
public Integer getType() {
return this.type;
}
public String getInfo() {
return this.info;
}
#Override
public String toString() {
return "Domain [type=" + this.type + ", info=" + this.info + "]";
}
}
}

How can I adjust load balancing rule by feign in spring cloud

As I know, feign include ribbon's function, and I prove it in my code.
When I use feign, the default rule is Round Robin Rule.
But how can I change the rule in my feign client code, is ribbon the only way?
Here is my code below, so please help.
ConsumerApplication.java
#SpringBootApplication
#EnableDiscoveryClient
#EnableFeignClients
#EnableCircuitBreaker
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
UserFeignClient .java
#FeignClient(name = "cloud-provider", fallback = UserFeignClient.HystrixClientFallback.class)
public interface UserFeignClient {
#RequestMapping("/{id}")
BaseResponse findByIdFeign(#RequestParam("id") Long id);
#RequestMapping("/add")
BaseResponse addUserFeign(UserVo userVo);
#Component
class HystrixClientFallback implements UserFeignClient {
private static final Logger LOGGER = LoggerFactory.getLogger(HystrixClientFallback.class);
#Override
public BaseResponse findByIdFeign(#RequestParam("id") Long id) {
BaseResponse response = new BaseResponse();
response.setMessage("disable");
return response;
}
#Override
public BaseResponse addUserFeign(UserVo userVo) {
BaseResponse response = new BaseResponse();
response.setMessage("disable");
return response;
}
}
}
FeignController.java
#RestController
public class FeignController {
#Autowired
private UserFeignClient userFeignClient;
#GetMapping("feign/{id}")
public BaseResponse<Date> findByIdFeign(#PathVariable Long id) {
BaseResponse response = this.userFeignClient.findByIdFeign(id);
return response;
}
#GetMapping("feign/user/add")
public BaseResponse<Date> addUser() {
UserVo userVo = new UserVo();
userVo.setAge(19);
userVo.setId(12345L);
userVo.setUsername("nick name");
BaseResponse response = this.userFeignClient.addUserFeign(userVo);
return response;
}
}
From the documentation:
#RibbonClient(name = "cloud-provider", configuration = CloudProviderConfiguration.class)
public class ConsumerApplication {
/* ... */
}
class CloudProviderConfiguration {
#Bean
public IRule ribbonRule(IClientConfig config) {
return new RandomRule();
}
}

Resources