Finding app base for embedded tomcat in spring boot - java

I am using spring boot. I am developing a web application and i need to get the app base to generate a link for an email. For that i need to get the base URL. As the tomcat is embedded
request.getContextpath()
returns null. I need to get that localhost:8080 dynamically so that when I deploy this to server I don't have to change the code.

In a spring boot application you can change the server context in the application.properties with the properties server.context-path=your-path
in your code you can refer to this properties like below
#SpringBootApplication
#EnableConfigurationProperties(ServerProperties.class)
public class DemoApplication implements CommandLineRunner{
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Autowired
ServerProperties serverProperties;
#Override
public void run(String... strings) throws Exception {
System.out.println("serverProperties.getContextPath(): " + serverProperties.getContextPath());
}
}
The key point here is use #EnableConfigurationProperties(ServerProperties.class) on your #Configuration class and then use the injected fiels ServerProperties for consume the value.
I hope that this can help you...

Huh, that took me some time to get there but:
#SpringBootApplication
public class TestServiceApplication {
public static void main(String[] args) {
AnnotationConfigEmbeddedWebApplicationContext context = (AnnotationConfigEmbeddedWebApplicationContext)SpringApplication.run(TestServiceApplication.class, args);
TomcatEmbeddedServletContainer tomcatContainer = (TomcatEmbeddedServletContainer)context.getEmbeddedServletContainer();
String host = tomcatContainer.getTomcat().getHost().getName();
int port = tomcatContainer.getPort();
System.out.println("Host:port " + host + ":" + port);
}
}
Output: Host:port localhost:8080
Is that what You wanted?

Related

Eureka client looking for default url instead of custom url

I have eureka server that posted in tomcat server (localhost:80/eureka-server)
spring.application.name=eureka
server.port=80
server.servlet.context-path=/eureka-server
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.default-zone=http://localhost:80/eureka-server/eureka/
#SpringBootApplication
#EnableEurekaServer
public class EurekaServer extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class, args);
}
}
In my microservice i try to register it in eureka
spring.application.name=randomService
eureka.client.service-url.default-zone=http://localhost:80/eureka-server/eureka/
#SpringBootApplication
#EnableEurekaClient
public class RandomServiceApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(RandomServiceApplication.class, args);
}
}
but everytime microservice tries to connect with default eureka url (http://localhost:8761). What should i do?
So, i solved it in unusual way, in my microservice i changed application.properties
eureka.client.service-url.default-zone=...
to camelCase
eureka.client.serviceUrl.defaultZone=

Spring Boot can connect to Cassandra when running as Servlet but not as Command Line

Background: I'm trying to set up a code-based data migration system for our Cassandra database. I don't have a ton of experience with Java, but if this were a .NET project I'd set up the migrations as a different project under the same solution. However, based on guidance from other team members that are more experienced, it was recommended that I include the migrations in the same package as the rest of the application (which I'm fine with). It was also suggested that the easiest method would be to run the migrations via a web API endpoint (which I'm more skeptical of). In the interest of avoiding opening up a potential security vulnerability, I thought I'd take a shot at making a command-line utility to execute the migrations.
I have a Spring Boot web application with an entry point class that looks like this:
#Configuration
#SpringBootApplication
#EnableAutoConfiguration
#EnableCaching
#EnableScheduling
public class MyApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
new MyApplication().configure(new SpringApplicationBuilder(MyApplication.class)).run(args);
}
}
However, I'm trying to add the functionality to run a couple migration scripts that are packaged with this application via the command line (e.g. java -jar MyApplication.jar migrate), so I added the following class:
#Configuration
#SpringBootApplication
#EnableAutoConfiguration
public class MigrationRunner implements CommandLineRunner {
#Autowired
Session session;
#Override
public void run(String[] args)
{
MigrationResources mr = new MigrationResources();
mr.addMigration(...);
mr.addMigration(...);
MigrationEngine.withSession(session).migrate(mr);
}
}
And then updated my entry point class like this:
// annotations
public class MyApplication extends SpringBootServletInitializer {
private final static String MIGRATE_COMMAND = "migrate";
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
if (args.length > 0 && args[0].equalsIgnoreCase(MIGRATE_COMMAND)) {
new SpringApplicationBuilder()
.sources(MigrationRunner.class)
.run(Arrays.copyOfRange(args, 1, args.length));
} else {
new MyApplication().configure(new SpringApplicationBuilder(MyApplication.class)).run(args);
}
}
}
The problem is that when I execute this with the migrate arg, Spring throws this error:
Error creating bean with name 'migrationRunner': Unsatisfied dependency expressed through field 'session'
Error creating bean with name 'session' defined in class path resource [org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.class]: Invocation of init method failed
All host(s) tried for query failed (tried: server022/XX.YY.ZZ.022:9042 (com.datastax.driver.core.exceptions.TransportException: [server022/XX.YY.ZZ.022:9042] Connection has been closed), server022/XX.YY.ZZ.020:9042 (com.datastax.driver.core.exceptions.TransportException: [server020/XX.YY.ZZ.020:9042] Connection has been closed), server020/XX.YY.ZZ.021:9042 (com.datastax.driver.core.exceptions.TransportException: [server020/XX.YY.ZZ.021:9042] Connection has been closed))
Running it without the migrate arg still works fine. I suspect that Spring is simply not picking up the correct certificates for this Cassandra server, even though it appears to be getting all the other configuration properties (server name, keyspace, etc.)
Question: How can I make a Spring Boot servlet that also has a command-line mode and can connect to the configured Cassandra server in both modes?
All you need to do is,
#SpringBootApplication
public class MyApplication
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
You have over complicated the application. If you run the MyApplication.main that will run in port 8080 by default.
Bonus, If you need both to start from same class.
#SpringBootApplication
public class MigrationRunner implements CommandLineRunner {
#Autowired
Session session;
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
public void run(String[] args)
{
MigrationResources mr = new MigrationResources();
mr.addMigration(...);
mr.addMigration(...);
MigrationEngine.withSession(session).migrate(mr);
}
}

Access microservice using Spring Boot

I have to implement my project as microservice arch. For that I am doing one sample app using Spring Boot of adding two no. I have three services. Here is my registration-server.yml.Similarly I have account-server.yml and user-service.yml. I want to call add() using UserService.java without RMI concept, since I am using Spring Boot. Also I don't want REST call since it will be costly for my project. How can I manually write code for lookup() in UserService so that it can call Adder?
#EnableAutoConfiguration
#EnableDiscoveryClient
public class AddService {
public static int add(int x,int y){
int z=x+y;
System.out.println("The sum of no. is "+z);
return z;
}
public static void main(String[] args) {
System.setProperty("spring.config.name", "add-service");
SpringApplication.run(AddService.class, args);
}
#SpringBootApplication
#EnableEurekaServer
public class RegistrationService {
public static void main(String[] args) {
// Tell server to look for registration.properties or registration.yml
System.setProperty("spring.config.name", "registration-service");
SpringApplication.run(RegistrationService.class, args);
}
#SpringBootApplication
#EnableDiscoveryClient
public class UserService {
public static void main(String[] args) {
System.setProperty("spring.config.name", "registration-service");
SpringApplication.run(UserService.class, args);
}
eureka:
instance:
hostname: localhost
client: # Not a client, don't register with yourself
registerWithEureka: false
fetchRegistry: false
server:
port: 1111 # HTTP (Tomcat) port
I'd say that the Spring Cloud guide on this is the best starting point.
But in short, since you're using Spring Cloud (i.e. #EnableDiscoveryClient), I'd personally use Spring Cloud's feign client support to carry out the call. This will do the actual discovery service (eureka) lookup and HTTP calls for you.
Firstly you'll need the #EnableFeignClients annotation on your config class, and the following dependency (assuming Maven):
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
Then within your user service project, you can add the following interface:
#FeignClient("add-service")
public interface AddServiceClient {
#RequestMapping(method = RequestMethod.POST, value = "/add/{x}/{y}", consumes="application/json")
int addNumbers(#PathVariable("x") int x, #PathVariable("y") int y);
}
That's basically it really. You can then autowire AddServiceClient and use it:
#Autowired
private AddServiceClient addServiceClient;
void someMethod() {
addServiceClient.addNumbers(2, 4);
}
This assumes that you expose /add/{x}/{y} as a POST endpoint within your add service (e.g. via #RestController and #RequestMapping)
EDIT: Sorry, I just seen where you said REST would be costly. Why do you think that? :)

Overriding #ConfigurationProperties

I'm developing a Spring Integration/Boot application. I'm using a multi-document application.yml (src/main/resources/application.yml) to set defaults for several configuration classes (annotated with #ConfigurationProperties). Applicaiton.yml comes with defaults, and some of them need to be overridden, depending on the environment.
I'm open to either using Java system properties (-D...=...), Spring "properties" (--...=...), or preferably a yaml file located outside a Jar, in a directory.
Application.yml has 4 documents and each one corresponds to a different configuration class. Let's just focus on ServerConfig:
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(locations = "classpath:application.yml", prefix = "server")
public class ServerConfig {
private Integer port;
private String address;
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Application.yml:
server:
address: ::1
port: 8080
---
Notice how I have locations specified in the annotation. This loads application.yml and uses those values successfully, but I can't figure out how to override them (say -Dserver.port=7777 or --server.port=7777). If I remove locations = ..., then I can use `-Dserver.port=7777, but the defaults in application.yml are never loaded, so I have to specify every single value as a command line argument.
I've read through https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html numerous times, and I can't understand why I can't leave locations = 'application.yml' in the config annotations and selectively override with system properties.
Does anyone know how to do this?
Sigh. It was a problem with application startup -- caused by confusion on my part between Spring Integration and Spring Boot.
My Main method used to be:
#SpringBootApplication
public class Main {
public static void main(String... args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("org.fandingo.blah.blah");
ctx.registerShutdownHook();
}
My understanding is that's how you'd start an application that is Spring Integration only (assuming JavaConfig of course). The problem is that the YAML properties loading is a Spring Boot feature. Switching out the main method to use Spring Boot method fixed the issue:
#SpringBootApplication
public class Main {
public static void main(String... args) {
SpringApplication app = new SpringApplication(Main.class);
app.setRegisterShutdownHook(true);
app.run(args);
}
}
Spring is so goddamned complex and cryptic. Why Spring Integration and Boot can't naturally cooperate is beyond me.

Spring: How to set system properties in integration test when #ComponentScan is used?

Summary: Adding the #ComponentScan (or #SpringBootApplication) annotation to my application class changes the behaviour of SpringApplicationBuilder.properties() and breaks my integration test.
I am using a cut-down version of the Spring Boot sample:
spring-boot-sample-websocket-jetty
I have removed everything except what is required for the "echo" example (and I'm using Spring Boot 1.3.3).
I am left with the following SampleJettyWebSocketsApplication code:
#Configuration
#EnableAutoConfiguration
//#ComponentScan // --- If I uncomment this the test breaks ---
#EnableWebSocket
public class SampleJettyWebSocketsApplication
implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoWebSocketHandler(), "/echo").withSockJS();
}
#Bean
public EchoService echoService() {
return new DefaultEchoService("Did you say \"%s\"?");
}
#Bean
public WebSocketHandler echoWebSocketHandler() {
return new EchoWebSocketHandler(echoService());
}
public static void main(String[] args) {
SpringApplication.run(SampleJettyWebSocketsApplication.class, args);
}
}
And the following test class (code straight from the Spring Boot samples):
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(SampleJettyWebSocketsApplication.class)
#WebIntegrationTest({"server.port=0"})
#DirtiesContext
public class SampleWebSocketsApplicationTests {
private static Log logger = LogFactory.getLog(SampleWebSocketsApplicationTests.class);
#Value("${local.server.port}")
private int port = 1234;
#Test
public void echoEndpoint() throws Exception {
logger.info("Running the echoEndpoint test. Port: " + port + ". Path: /echo/websocket");
ConfigurableApplicationContext context = new SpringApplicationBuilder(
ClientConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
.properties("websocket.uri:ws://localhost:" + this.port
+ "/echo/websocket")
.run("--spring.main.web_environment=false");
long count = context.getBean(ClientConfiguration.class).latch.getCount();
AtomicReference<String> messagePayloadReference = context
.getBean(ClientConfiguration.class).messagePayload;
context.close();
assertThat(count).isEqualTo(0);
assertThat(messagePayloadReference.get())
.isEqualTo("Did you say \"Hello world!\"?");
}
#Configuration
static class ClientConfiguration implements CommandLineRunner {
#Value("${websocket.uri}")
private String webSocketUri;
private final CountDownLatch latch = new CountDownLatch(1);
private final AtomicReference<String> messagePayload = new AtomicReference<String>();
#Override
public void run(String... args) throws Exception {
logger.info("Waiting for response: latch=" + this.latch.getCount());
if (this.latch.await(10, TimeUnit.SECONDS)) {
logger.info("Got response: " + this.messagePayload.get());
}
else {
logger.info("Response not received: latch=" + this.latch.getCount());
}
}
#Bean
public WebSocketConnectionManager wsConnectionManager() {
logger.info("Setting up SimpleClientWebSocketHandler...");
WebSocketConnectionManager manager = new WebSocketConnectionManager(client(),
handler(), this.webSocketUri);
manager.setAutoStartup(true);
return manager;
}
#Bean
public StandardWebSocketClient client() {
return new StandardWebSocketClient();
}
#Bean
public SimpleClientWebSocketHandler handler() {
logger.info("Creating new SimpleClientWebSocketHandler using SimpleGreetingService...");
return new SimpleClientWebSocketHandler(greetingService(), this.latch,
this.messagePayload);
}
#Bean
public GreetingService greetingService() {
return new SimpleGreetingService();
}
}
}
Running the Application and the unit test as above all is fine but if I uncomment the #ComponentScan annotation on the application class the application still runs OK but the test breaks with the error:
Could not resolve placeholder 'websocket.uri' in string value "${websocket.uri}".
I have read at setting-the-run-time-properties-on-springapplicationbuilder that:
The properties you configure on SpringApplicationBuilder are made available in your application's Environment, not as system properties.
And in the #ComponentScan javadoc that:
If specific packages are not defined, scanning will occur from the package of the class that declares this annotation.
But I don't understand why the behaviour changes when the #ComponentScan annotation is added.
How can I set the System Property websocket.uri in the test when the application class is annotated with #ComponentScan (or #SpringBootApplication)?
(I aim to use #SpringBootApplication, which incorporates #ComponentScan, but I can't until I get this working.)
There are several ways to add a system properties.
Solution 1:
Add arguments for Test in format of -Dabc=xyz, that will add property abc to system properties.
Solution 2:
Just like floor 0.
Solution 3:
Just let spring-boot load the properties, such as classpath:bootstrap.yml, and you can specify whatever properties in there.
The annotation #ComponentScan will enable auto scanning based on current package or ComponentScan#basePackages. Which means SampleWebSocketsApplicationTests.ClientConfiguration will be scanned cause they have same base package samples.websocket.jetty.
However, SampleWebSocketsApplicationTests.ClientConfiguration should not be parsed by SpringJUnit4ClassRunner cause we need parse it in SampleWebSocketsApplicationTests#echoEndpoint manually. It's should only be parsed by ApplicationContext created in echoEndpoint().
What's more, #SpringBootApplication equals to use #Configuration and #EnableAutoConfiguration and #ComponentScan together, so comment out #ComponentScan or #SpringBootApplication will have same effect.
My suggestion is move class SampleWebSocketsApplicationTests into package samples.websocket.jettytest(different from samples.websocket.jetty) and enable #ComponentScan or #SpringBootApplication on SampleJettyWebSocketsApplication and try again. It should work.
Adding my thoughts on this (from whatever i could gather from your code):
-Try adding the property websocket.uri in you application properties or if your project contains src/test/resources/test.properties then add it into your test.properties file.#ComponentScan should pick it up.
-Else,you could just say :
public static void main(String[] args) {
System.setProperty("websocket.uri","<your uri>");
SpringApplication.run(SampleJettyWebSocketsApplication.class, args);
}
Hope it helps.

Categories