I'm trying to start Spring application from main without Spring Boot, I managed to get working router functions, but can't make to work classic #Controllers.
I debugged and watched source of boot and built in configs, added RequestMappingHandlerMapping, and it registers my Controller, but anyway WebHandler can not see it.
I can see that spring discovered my #Controller as a bean so it is not a problem.
Can someone help me? Thanx.
When I run the application I get:
Apr 13, 2018 7:27:30 PM
org.springframework.context.support.AbstractApplicationContext
prepareRefresh INFO: Refreshing
org.springframework.context.annotation.AnnotationConfigApplicationContext#20e2cbe0:
startup date [Fri Apr 13 19:27:30 EEST 2018]; root of context
hierarchy Apr 13, 2018 7:27:31 PM
org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping$MappingRegistry
register INFO: Mapped "{[/test],methods=[GET]}" onto public
java.lang.String test.wboot.Controller.test() Apr 13, 2018 7:27:31 PM
org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping$MappingRegistry
register INFO: Mapped "{[/test],methods=[GET]}" onto public
java.lang.String test.wboot.Controller.test() [DEBUG] (main) Using
Console logging [DEBUG] (main) Default Epoll support : false [DEBUG]
(main) Default KQueue support : false [DEBUG] (main) Connecting new
channel:
AbstractBootstrap$PendingRegistrationPromise#4a3329b9(incomplete)
Press button to exit
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.start();
}
}
.
#Configuration
#ComponentScan("test.wboot")
public class AppConfig {
public static final int port = 9099;
#Bean
public NettyContext servak(ApplicationContext applicationContext) throws IOException {
// this handler works just perfect
// HttpHandler httpHandler = RouterFunctions.toHttpHandler(routingFunction());
// And this one can not see Controller
HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(applicationContext).build();
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
NettyContext localhost = HttpServer.create("localhost", port).newHandler(adapter).block();
System.out.println("Press button to exit");
System.in.read();
return localhost;
}
public RouterFunction<ServerResponse> routingFunction() {
return nest(path("/test"), route(GET(""), new HandlerFunction<ServerResponse>() {
public Mono<ServerResponse> handle(ServerRequest request) {
return ServerResponse.ok().syncBody("Hello yopta");
}
}));
}
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(ApplicationContext applicationContext) {
RequestMappingHandlerMapping requestMappingHandlerMapping = new RequestMappingHandlerMapping();
requestMappingHandlerMapping.setApplicationContext(applicationContext);
requestMappingHandlerMapping.afterPropertiesSet();
return requestMappingHandlerMapping;
}
#Bean
public WebHandler webHandler(ApplicationContext applicationContext) {
DispatcherHandler webHandler = new DispatcherHandler();
webHandler.setApplicationContext(applicationContext);
return webHandler;
}
}
.
#RestController
#RequestMapping("/test")
public class Controller {
#GetMapping
public String test() {
return "work";
}
}
Maven dependencies:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>io.projectreactor.ipc</groupId>
<artifactId>reactor-netty</artifactId>
<version>0.7.6.RELEASE</version>
</dependency>
</dependencies>
Why are you redifining everything?
Spring Framework provides an #EnableWebFlux annotation that does this and more, so you should just add that annotation on a #Configuration class.
You then just have to follow the instructions here to start your server using the WebHandler automatically created by the Framework; so for Reactor Netty, this would look like:
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create(host, port).newHandler(adapter).block();
Related
I am migrating an application to Spring boot 3.0, but I have a problem with the injection of a ProducerTemplate in one of my services (annotation #Service se Spring Boot).
I was using the Spring Boot starter from Camel
Current Spring Boot version is : 2.7.5
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>3.15.0</version>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-rabbitmq-starter</artifactId>
<version>3.15.0</version>
</dependency>
My ProducerTemplate is injected into the constructor of my service that extends RouteBuilder
#Service
public class MyServiceImpl extends RouteBuilder implements MyService {
private final ProducerTemplate producerTemplate;
#Autowired
public MyServiceImpl(ProducerTemplate producerTemplate) {
this.producerTemplate = producerTemplate;
}
At startup, the application crashes with this message :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.mycompany.ServiceImpl required a bean of type 'org.apache.camel.ProducerTemplate' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'org.apache.camel.ProducerTemplate' in your configuration.
It's not very clear to me how I should take it.
I guess I have to use the dependency
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>3.15.0</version>
</dependency>
instead of
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>3.15.0</version>
</dependency>
But I don't really know how I should instantiate the ProducerTemplate and the CamelContext ?
I tried without really believing something like this :
#Configuration
public class CamelConfig {
#Bean
public CamelContext getCamelContext() throws Exception {
CamelContext context = new DefaultCamelContext();
if (context.isStopped()) context.start();
return context;
}
#Bean
public ProducerTemplate getProducerTemplate() throws Exception
{
final ProducerTemplate template = getCamelContext().createProducerTemplate();
return template;
}
}
#Service
public class MyServiceImpl implements MyService {
private final ProducerTemplate producerTemplate;
private CamelContext context;
#Autowired
public MyServiceImpl(ProducerTemplate producerTemplate, CamelContext context) {
this.producerTemplate = producerTemplate;
this.context = context;
try {
this.context.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:update-legacy")
.to("bean:updateJdbc");
The app failed with the following error message :
Caused by: org.apache.camel.ResolveEndpointFailedException: Failed to resolve endpoint: bean://updateJdbc due to: No bean could be found in the registry for: updateJdbc
Tries to create an web application based on spring boot with web-socket support
Define public static main class annotated with #SpringBootApplication
#SpringBootApplication
public class Facade {
public static void main(String[] argv) throws IOException {
Map<String,Object> contextProperties = new HashMap<>();
contextProperties.put("server.port",p.getProperty("server.port","8091"));
SpringApplication app = new SpringApplication(Facade.class);
app.setDefaultProperties(contextProperties);
app.run(argv);
}
}
Add configuration class
#Configuration
#EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketTextHandler(), "/ws").setAllowedOrigins("*");
}
}
Add handler
#Component
public class SocketTextHandler extends TextWebSocketHandler {
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
String payload = message.getPayload();
session.sendMessage(new TextMessage("Hi " + payload + " how may we help you?"));
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
session.sendMessage(new TextMessage("Connected!"));
}
}
There are maven dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-coyote</artifactId>
<version>9.0.31</version>
</dependency>
spring boot version is 2.5.5
When I connect to socket using JavaScript
webSocket = new WebSocket(wsUrl);
I see in app logs
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.socket.server.HandshakeFailureException: Servlet request failed to upgrade to WebSocket: http://127.0.0.1:8091/ws; nested exception is javax.servlet.ServletException: java.lang.reflect.InvocationTargetException] with root cause
java.lang.ClassNotFoundException: org.apache.coyote.http11.upgrade.UpgradeInfo
The tomcat-coyote dependency is in pom.xlm
I used web-sockets in jersey implementation under tomcat9. Now I tries to migrate application to spring-boot, but confused with NoClassDef exception.
Could you please explain or suggest solution for this cause.
I am trying to enable loadtimeweaving without javaagent jar files of aspectweaver and spring-instrument. This what I have implemented to achieve the same But it's not working.
#ComponentScan("com.myapplication")
#EnableAspectJAutoProxy
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT)
public class AopConfig implements LoadTimeWeavingConfigurer {
#Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
/**
* Makes the aspect a Spring bean, eligible for receiving autowired components.
*/
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
}
A workaround I found was to hot-attach InstrumentationSavingAgent from spring-instrument instead of starting the agent via -javaagent command line parameter. But for that you need an Instrumentation instance. I just used the tiny helper library byte-buddy-helper (works independently of ByteBuddy, don't worry) which can do just that. Make sure that in Java 9+ JVMs the Attach API is activated if for some reason this is not working.
So get rid of implements LoadTimeWeavingConfigurer and the two factory methods in your configuration class and just do it like this:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.14</version>
</dependency>
#SpringBootApplication
public class Application {
public static void main(String[] args) {
Instrumentation instrumentation = ByteBuddyAgent.install();
InstrumentationSavingAgent.premain("", instrumentation);
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// ...
}
}
Feel free to ask follow-up questions if there is anything you do not understand.
Update: One more thing I noticed is that this only works for me with aspectjWeaving = ENABLED, not with AUTODETECT. And for one sample Spring bean I noticed that #Component did not work, probably because of some bootstrapping issue between Spring vs AspectJ. Hence, I replaced it by an explicit #Bean configuration, then it worked. Something like this:
#Configuration
#ComponentScan("com.spring.aspect.dynamicflow")
#EnableLoadTimeWeaving(aspectjWeaving = ENABLED)
public class ApplicationConfig {
#Bean
public JobProcess jobProcess() {
return new JobProcessImpl();
}
}
I'm creating a spring-rest-app.
This is my dispatcher config (I also have a root config that has DataSource bean)
#Configuration
#ComponentScan(basePackages= {"config", "cache", "dao", "entity", "exception", "rest", "service"})
#EnableWebMvc
public class DispatcherConfiguration {
#Bean
public KeyCache keyCache() {
return new KeyCacheImpl();
}
}
This is my webapp initializer
public class TinyURLKeyServiceInitializor implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext appcontext = new AnnotationConfigWebApplicationContext();
appcontext.register(ConfigurationClass.class);
servletContext.addListener(new ContextLoaderListener(appcontext));
AnnotationConfigWebApplicationContext dispatchercontext = new AnnotationConfigWebApplicationContext();
dispatchercontext.register(DispatcherConfiguration.class);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(dispatchercontext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
This is the controller
#Controller
#RequestMapping("/api/keyservice")
public class KeyServiceController {
#Autowired
private KeyService keyService;
#GetMapping(value="/key", produces="application/json")
#ResponseBody
public String getKey() {
return keyService.getKey();
}
}
When I startup the Web app and Send - GET http://localhost:7080/api/keyservice/key
I get the following in DEBUG LOG
[INFO] Completed initialization in 1006 ms
May 17, 2020 11:56:10 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-7080"]
[DEBUG] GET "/api/keyservice/key", parameters={}
[WARNING] No mapping for GET /api/keyservice/key
[DEBUG] Completed 404 NOT_FOUND
I've put #EnableMvc for registring MappingHandlers. But still they are not able to detect the mapping between endpoint and controller method.
I put a debug point in DispatcherServlet.getHandler and it returns null everytime. Has anyone ever faced similar problem?
Found the reason I was hitting -
http://localhost:7080/api/keyservice/key
My pom.xml had -
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>7080</port>
<path>/api/keyservice</path>
</configuration>
</plugin>
My Controller
#Controller
#RequestMapping("/api/keyservice")
public class KeyServiceController {
#Autowired
private KeyService keyService;
#GetMapping(value="/key", produces="application/json")
#ResponseBody
public String getKey() {
Since context path = /api/keyservice (due to setting in pom.xml), spring was trying to find a mapping for /key. Cleary there is no mapping for /key in my controller.
Removed the controller RequestMapping. And it worked.
I created a new sample and slipted the codes into client and server side.
The complete codes can be found here.
There are 3 version of server side.
server None Spring Boot app, using Spring Integration RSocket InboundGateway.
server-boot Reuse Spring RSocket autconfiguration, and created ServerRSocketConnecter through ServerRSocketMessageHanlder.
server-boot-messsagemapping Not use Spring Integration, just use Spring Boot RSocket autconfiguration, and #Controller and #MessageMapping.
There are 2 versions of client.
client, Sending messages using Spring Integration Rocket OutboundGateway.
client-requester Send messages using RSocketRequester, not use Spring Integration at all.
The client and server interaction mode is REQUEST_CHANNEL, and connect server via TCP/localhost:7000.
server
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-rsocket</artifactId>
</dependency>
The application class:
#Configuration
#ComponentScan
#IntegrationComponentScan
#EnableIntegration
public class DemoApplication {
public static void main(String[] args) throws IOException {
try (ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(DemoApplication.class)) {
System.out.println("Press any key to exit.");
System.in.read();
} finally {
System.out.println("Exited.");
}
}
#Bean
public ServerRSocketConnector serverRSocketConnector() {
return new ServerRSocketConnector("localhost", 7000);
}
#Bean
public IntegrationFlow rsocketUpperCaseFlow(ServerRSocketConnector serverRSocketConnector) {
return IntegrationFlows
.from(RSockets.inboundGateway("/uppercase")
.interactionModels(RSocketInteractionModel.requestChannel)
.rsocketConnector(serverRSocketConnector)
)
.<Flux<String>, Flux<String>>transform((flux) -> flux.map(String::toUpperCase))
.get();
}
}
server-boot
Dependencies in pom.xml.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-rsocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-rsocket</artifactId>
</dependency>
application.properties
spring.rsocket.server.port=7000
spring.rsocket.server.transport=tcp
Application class.
#SpringBootApplication
#EnableIntegration
public class DemoApplication {
public static void main(String[] args) throws IOException {
SpringApplication.run(DemoApplication.class, args);
}
// see PR: https://github.com/spring-projects/spring-boot/pull/18834
#Bean
ServerRSocketMessageHandler serverRSocketMessageHandler(RSocketStrategies rSocketStrategies) {
var handler = new ServerRSocketMessageHandler(true);
handler.setRSocketStrategies(rSocketStrategies);
return handler;
}
#Bean
public ServerRSocketConnector serverRSocketConnector(ServerRSocketMessageHandler serverRSocketMessageHandler) {
return new ServerRSocketConnector(serverRSocketMessageHandler);
}
#Bean
public IntegrationFlow rsocketUpperCaseFlow(ServerRSocketConnector serverRSocketConnector) {
return IntegrationFlows
.from(RSockets.inboundGateway("/uppercase")
.interactionModels(RSocketInteractionModel.requestChannel)
.rsocketConnector(serverRSocketConnector)
)
.<Flux<String>, Flux<String>>transform((flux) -> flux.map(String::toUpperCase))
.get();
}
}
server-boot-messagemapping
Dependencies in pom.xml.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-rsocket</artifactId>
</dependency>
The application.properties.
spring.rsocket.server.port=7000
spring.rsocket.server.transport=tcp
The applcition class.
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
#Controller
class UpperCaseHandler {
#MessageMapping("/uppercase")
public Flux<String> uppercase(Flux<String> input) {
return input.map(String::toUpperCase);
}
}
client
In the client, the dependencies in the pom.xml is like.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-rsocket</artifactId>
</dependency>
The application class:
#SpringBootApplication
#EnableIntegration
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public ClientRSocketConnector clientRSocketConnector() {
ClientRSocketConnector clientRSocketConnector = new ClientRSocketConnector("localhost", 7000);
clientRSocketConnector.setAutoStartup(false);
return clientRSocketConnector;
}
#Bean
public IntegrationFlow rsocketUpperCaseRequestFlow(ClientRSocketConnector clientRSocketConnector) {
return IntegrationFlows
.from(Function.class)
.handle(RSockets.outboundGateway("/uppercase")
.interactionModel((message) -> RSocketInteractionModel.requestChannel)
.expectedResponseType("T(java.lang.String)")
.clientRSocketConnector(clientRSocketConnector))
.get();
}
}
#RestController
class HelloController {
#Autowired()
#Lazy
#Qualifier("rsocketUpperCaseRequestFlow.gateway")
private Function<Flux<String>, Flux<String>> rsocketUpperCaseFlowFunction;
#GetMapping(value = "hello", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> uppercase() {
return rsocketUpperCaseFlowFunction.apply(Flux.just("a", "b", "c", "d"));
}
}
When running the client and server application, and try to access the http://localhost:8080/hello by curl.
When using server and server-boot which uses InboundGateway to handle messages, the output looks like this.
curl http://localhost:8080/hello
data:ABCD
When using server-boot-messagemapping, the output is woking as I expected:
data:A
data:B
data:C
data:D
client-requester
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-rsocket</artifactId>
</dependency>
The application class:
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
#RestController
class HelloController {
Mono<RSocketRequester> requesterMono;
public HelloController(RSocketRequester.Builder builder) {
this.requesterMono = builder.connectTcp("localhost", 7000);
}
#GetMapping(value = "hello", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> uppercase() {
return requesterMono.flatMapMany(
rSocketRequester -> rSocketRequester.route("/uppercase")
.data(Flux.just("a", "b", "c", "d"))
.retrieveFlux(String.class)
);
}
}
When running this client and the 3 servers, and try to access the http://localhost:8080/hello by curl.
When using server and server-boot which uses InboundGateway to handle messages, it throws a class cast exception.
When using server-boot-messagemapping, the output is woking as I expected:
data:A
data:B
data:C
data:D
I do not know where is the problem of the configuration of InboundGateway and OutboundGateway?
Thank you for such a detailed sample!
So, what I see. Both clients (plain RSocketRequester and Spring Integration) work well with plain RSocket server.
To make them working with Spring Integration server you have to do this changes:
The server side:
Add .requestElementType(ResolvableType.forClass(String.class)) into an RSockets.inboundGateway() definition, so it will know to what to convert an incoming payloads.
The client side:
.data(Flux.just("a\n", "b\n", "c\n", "d\n")).
Currently the server side of Spring Integration doesn't treat an incoming Flux as a stream of independent payloads. So, we try to connect all of them into a single value.
The new line delimiter is an indicator that we expect independent values. Spring Messaging on its side does exactly opposite: it checks for multi-value expected type and decode every element in the incoming Flux in its map() instead of an attempt for the whole Publisher decoding.
It's going to be kinda breaking change, but possibly need to consider to fix RSocketInboundGateway logic to be consistent with regular #MessageMapping for RSocket support. Feel free to raise a GH issue!