#PropertySource(factory=...) breaks #SpringBootTest when loading META-INF/build-info.properties - java

I am trying to setup a custom #ConfigurationProperties class loaded from a HOCON syntax .conf file.
I have a Class annotated with #PropertySource(factory=TypesafePropertySourceFactory.class, value = "classpath:app.conf")
#Configuration
#ConfigurationProperties(value = "app.server")
#PropertySource(factory = TypesafePropertySourceFactory.class, value = "classpath:app.conf")
public class ServerProperties {
public int port;
}
and a simple test class:
#SpringBootTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SomeTest {
#Test
public void someCoolTest() {/* ... */}
// ...
}
When i run my junit test runner, i get the following error:
Caused by: com.typesafe.config.ConfigException$BadPath: path parameter: Invalid path 'spring.info.build.location:classpath:META-INF/build-info.properties': Token not allowed in path expression: ':' (you can double-quote this token if you really want it here)
at com.typesafe.config.impl.PathParser.parsePathExpression(PathParser.java:155) ~[config-1.4.0.jar:1.4.0]
at com.typesafe.config.impl.PathParser.parsePathExpression(PathParser.java:74) ~[config-1.4.0.jar:1.4.0]
at com.typesafe.config.impl.PathParser.parsePath(PathParser.java:61) ~[config-1.4.0.jar:1.4.0]
...
If i uncomment the #PropertySource line on the ServerProperties class, the tests proceed normally. It seems strange to me that my custom PropertySourceFactory gets in the way of the default .properties file resolution process.
PropertySource and Factory classes
// TypesafeConfigPropertySource.java
import com.typesafe.config.Config;
import org.springframework.core.env.PropertySource;
public class TypesafeConfigPropertySource extends PropertySource<Config> {
public TypesafeConfigPropertySource(String name, Config source) {
super(name, source);
}
#Override
public Object getProperty(String path) {
if (source.hasPath(path)) {
return source.getAnyRef(path);
}
return null;
}
}
// TypesafePropertySourceFactory.java
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.IOException;
import java.util.Objects;
public class TypesafePropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
Config config = ConfigFactory.load(Objects.requireNonNull(resource.getResource().getFilename())).resolve();
String safeName = name == null ? "typeSafe" : name;
return new TypesafeConfigPropertySource(safeName, config);
}
}
Am I missing something fundamental about configuring custom property resource factories, or is this a bug?
Versions
Spring boot 2.3.4
Junit Jupiter 5.6.2

Maybe you can also solve it with the use of a ContextInitializer as suggested in the answer here:
Spring Environment backed by Typesafe Config

TL;DR
Return null if you cannot process the path in your custom impl
public class TypesafeConfigPropertySource extends PropertySource<Config> {
// ...
#Override
public Object getProperty(String path) {
try {
if (source.hasPath(path)) {
return source.getAnyRef(path);
}
} catch(ConfigException.BadPath ignore) {
}
return null;
}
// ...
}
Explanation
I am making educated guesses, but functionally this appears supported by the way the code behaves
the most likely scenario here is the resolution order will consider our custom implementation before any default implementation. The method used in our implementation will error out with any path containing a ":" and "[" as the error occurs in the check for the path's existence.
I'm simply wrapping the BadPath exception in order to catch any problem and then returning null to signify no match.

Related

How to check Spring profile explicitly?

I would like to check that my application runs only under given set of allowed profiles.
I would write
#Autowire
Environment environment;
#PostConstruct
public void checkAndReportProfiles() {
// checks
... environment.getActiveProfiles()
but the problem is that this runs after all beans were attempted to initialize and failed if profiles were incorrect.
I want to check profiles explicitly and fail with explicit message.
Is it possible to do this?
Option 1: you can create a bean which performes the checks for you.
#Component
public class TestBean {
public TestBean() {
throw new BeanInitializationException("error message");
}
}
to execute this before certain problematic beans you can annotate theese beans with #DependsOn
But now you have a bean with no purpose hanging around.
Option 2:
Perform the checks in a configration class.
To get access to the spring evironment or configuration properties, you can inject them via constructor.
#Configuration
public class TestConfiguration {
public TestConfiguration(Environment environment, #Value("${some.key}") String property) {
String[] profiles = environment.getActiveProfiles();
// perform tests here
throw new BeanInitializationException("error message");
}
}
Option 3: Create beans programatically and check before the bean is created.
#Configuration
public class TestConfiguration {
#Value("${some.key}")
String property;
private final Environment environment;
public TestConfiguration(Environment environment) {
this.environment = environment;
}
#Bean
public SomeBean someBean() {
if(whateverToTest) {
throw new BeanInitializationException("error message");
}
return new SomeBean();
}
}
Option 4: Perform test in the constructor of that particular bean. The constructor is called first.
#Component
public class SomeBean {
public SomeBean() {
if(whateverToTest) {
throw new BeanInitializationException("error message");
}
}
}
Option 3 is the cleanest solution in my opinion, but also the most difficult to implement. Especially if you are using constructor based injections.
You can try this.
Create a class for keeping the association profile/error
public enum ErrorProfile {
default("This is the error num 1"),
dev("This is the error num 2"),
test("This is the error num 3"),
prod("This is the error num 4");
private String profile;
private String message;
ErrorProfile(String profile, String message){
this.profile = profile;
this.profile = message;
}
}
Then create a profile checker that execute at the lunch of your spring project during the context loading.
Java provides a valueOf(String) method for all enum types.
Thus, we can always get an enum value based on the declared name. This means that, after we get the value of the profile we can retrieve the message code with:
ErrorProfile.valueOf("prod")
Then it will be enough to print it in the Logs, Exception or whatever you need.
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
#Slf4j
#Configuration
public class PropertiesLogger {
#Value("${spring.profiles.active}")
private String activeProfile;
#PostConstruct
public void printProperties() {
log.debug("Loading application-context profiles. Active profile found: {} [message: {}]",
activeProfile,
ErrorProfile.valueOf(activeProfile));
// and in this case (cause you want to rise an exception with that message)
throw new RuntimeException(ErrorProfile.valueOf(activeProfile))
}
}
To explicitly check the Spring profile, you can use the following approach:
1)Set the active profile via the --spring.profiles.active application launch option:java -jar myapp.jar --spring.profiles.active=production
2)Set the active profile via the environment:
export SPRING_PROFILES_ACTIVE=productionjava -jar myapp.jar
3)Set the active profile via an application.properties or application.yml file:
# application.properties spring.profiles.active=production
# application.yml spring: profiles: active: production

How to conditionally make a Spring Boot application terminate at startup based on a property

I want to implement the following use case - my Spring Boot application should start only if a certain property in application.yaml is set:
myapp:
active: true
If the property is not set, the context initialization should fail with a message that the property is missing.
I found in this topic how to achieve it: Spring Boot - Detect and terminate if property not set? but the problem why I can't follow this approach is that it is possible that the context initialization fails before the bean, that checks this property, is loaded.
For example, if some other bean fails to load because another property is missing, the context initialization will fail at that point and my bean, that checks the desired property, won't be loaded. This is not OK for me because I want the myapp.active property to be checked first, before any other beans get loaded.
The reason why I want to have it that way is that a certain profile should be set when running the app - the application-[profile].yaml contains both myapp.active: true and some other mandatory properties that are required to load the context.
I want my app always to fail because of myapp.active not being true so that I can output a meaningful message telling that the profile is missing and that the app must be run with one of the profiles (from given list of profiles). Some guys that aren't developers are running the app so I want them to know why the app didn't run, otherwise they will think there is some bug in the app.
How can I achieve this? Is it possible somehow to read the property before the beans are being loaded? I would like to avoid setting #DependsOn on all beans (or doing the same through a BeanPostProcesser) and am seeking for a more elegant solution.
The application won't start if you use a condition-on-property. Fast enough?
#SpringBootApplication
#ConditionalOnProperty(name = "myapp.active")
public class FastFailWhenPropertyNotPresentApplication {
public static void main(String[] args) {
SpringApplication.run(FastFailWhenPropertyNotPresentApplication.class, args);
}
}
Basically #SpringBootApplication is just a #Configuration class.
You have an option matchIfMissing that you can use to specify if the condition should match if the property is not set. Defaults to false.
EDIT:
A better solution is to configure your property via a #ConfigurationProperties combined with #Validated, so you can use the javax.validation.constraints annotations.
package stackoverflow.demo;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
#Component
#ConfigurationProperties(prefix = "myapp")
#Validated
public class MyAppProperties {
#AssertTrue
#NotNull
private Boolean active;
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
}
note: you can leave out #ConditionalOnProperty(name = "myapp.active")
use #AssertTrue in combination with #NotNull because of #AssertTrueconsiders null elements as valid.
and spring-boot generates a nice error-message for free:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'myapp' to stackoverflow.demo.MyAppProperties failed:
Property: myapp.active
Value: false
Origin: class path resource [application.properties]:1:16
Reason: must be true
Action:
Update your application's configuration
EDIT (after updated question)
A faster way: your application won't start, nor an application-context will be loaded
package stackoverflow.demo;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
#SpringBootApplication
public class FastFailWhenPropertyNotPresentApplication {
static Boolean active;
static {
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("application.yaml"));
active = (Boolean) yaml.getObject().getOrDefault("myapp.active", false);
}
public static void main(String[] args) {
if (!active) {
System.err.println("your fail message");
} else {
SpringApplication.run(FastFailWhenPropertyNotPresentApplication.class, args);
}
}
}
EDIT
another solution that probably fits your needs best...
By listening to the ApplicationEnvironmentPreparedEvent
Event published when a {#link SpringApplication} is starting up and the
* {#link Environment} is first available for inspection and modification.
*
note: you cannot use a #EventListener, but you have add the Listener to the SpringApplication
package stackoverflow.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
#SpringBootApplication
public class FastFailWhenPropertyNotPresentApplication {
static class EnvironmentPrepared implements ApplicationListener<ApplicationEnvironmentPreparedEvent>{
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
Boolean active = event.getEnvironment().getProperty("myapp.active",Boolean.class,Boolean.FALSE);
if(!active) {
throw new RuntimeException("APPLICATION FAILED TO START: ACTIVE SHOULD BE TRUE ");
}
}
};
public static void main(String[] args) throws Exception {
SpringApplication springApplication = new SpringApplication(FastFailWhenPropertyNotPresentApplication.class);
springApplication.addListeners(new FastFailWhenPropertyNotPresentApplication.EnvironmentPrepared());
springApplication.run(args);
}
}
One option would be to create a bean which checks for the presence of the property, and throw an exception during bean creation if the property is not set.
#Component
public static EnsureApplicationActive {
#Value("${myapp.active}")
private Boolean active;
#PostConstruct
public void ensureApplicationActive() {
if (!Boolean.TRUE.equals(active)) {
throw new IllegalStateException("TODO: Meaningful message goes here");
}
}
}

Testing Dropwizard 'Application' class

I'm looking to test the Dropwizard 'Application' class without bootstrapping an entire Dropwizard server.
I'd essentially just like to ensure that the one bundle I'm registering is registered successfully.
All the routes I've been down so far result in NullPointer exceptions due to various other components not being setup correctly. Is there an easy path here?
public class SentimentApplication extends Application<SentimentConfiguration> {
public static void main(final String[] args) throws Exception {
new SentimentApplication().run(args);
}
#Override
public String getName() {
return "Sentiment";
}
#Override
public void initialize(final Bootstrap<SentimentConfiguration> bootstrap) {
bootstrap.setConfigurationSourceProvider(
new SubstitutingSourceProvider(bootstrap.getConfigurationSourceProvider(),
new EnvironmentVariableSubstitutor(false)
)
);
}
#Override
public void run(final SentimentConfiguration configuration,
final Environment environment) {
// TODO: implement application
}
}
You can register a simple command and call run method of your application with that command instead of server command. That way your application will be executed without running a server.
I wanted to do sth similar to what you want. (Considering ExampleApp as main Application class of my code) I wanted to write a test to make sure that there is no exception parsing the configuration. (Because KotlinModule() should have beed registered to environment.objectMaooer in initialize method of app, otherwise we would have had a runtime error.) I achieved it with sth similar to:
import io.dropwizard.cli.EnvironmentCommand
import io.dropwizard.setup.Bootstrap
import io.dropwizard.setup.Environment
import com.example.config.ExampleConfiguration
import net.sourceforge.argparse4j.inf.Namespace
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class DummyCommand(app: DummyApp) : EnvironmentCommand<ExampleConfiguration>(app, "dummy", "sample test cmd") {
var parsedConfig: ExampleConfiguration? = null
override fun run(environment: Environment, namespace: Namespace, configuration: ExampleConfiguration) {
parsedConfig = configuration
}
}
class DummyApp : ExampleApp() {
val cmd: DummyCommand by lazy { DummyCommand(this) }
override fun initialize(bootstrap: Bootstrap<ExampleConfiguration>) {
super.initialize(bootstrap)
bootstrap.addCommand(cmd)
}
}
class ExampleAppTest {
#Test
fun `Test ExampleConfiguration is parsed successfully`() {
val app = DummyApp()
app.run("dummy", javaClass.getResource("/example-app-test/test-config.yml").file)
val config = app.cmd.parsedConfig
assertNotNull(config)
assertEquals("foo", config.nginxUsername)
}
}

Guice assisted inject with several factory methods and null parameters

I have this interface and simple implementation:
public interface Data {
}
import java.nio.file.Path;
import javax.annotation.Nullable;
import javax.inject.Inject;
import com.google.inject.assistedinject.Assisted;
public class SimpleData implements Data {
#Inject
public SimpleData(#Assisted #Nullable Path path) {
}
}
I want to generate a Factory with different methods using guice.
import java.nio.file.Path;
import javax.annotation.Nullable;
public interface Factory {
Data create();
Data load(#Nullable Path path);
}
But the following module configuration:
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.assistedinject.FactoryModuleBuilder;
public class Main {
public static void main(String[] args) {
Injector injector = Guice.createInjector(
binder -> binder.install(
new FactoryModuleBuilder().implement(Data.class, SimpleData.class)
.build(Factory.class)));
Data data = injector.getInstance(Factory.class).create();
}
}
fails:
Exception in thread "main" com.google.inject.CreationException: Guice creation errors:
1) No implementation for java.nio.file.Path annotated with #com.google.inject.assistedinject.Assisted(value=) was bound.
while locating java.nio.file.Path annotated with #com.google.inject.assistedinject.Assisted(value=)
for parameter 0 at SimpleData.<init>(SimpleData.java:10)
at Factory.create(Factory.java:1)
at com.google.inject.assistedinject.FactoryProvider2.initialize(FactoryProvider2.java:539)
at com.google.inject.assistedinject.FactoryModuleBuilder$1.configure(FactoryModuleBuilder.java:335)
1 error
at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:435)
at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:175)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:109)
at com.google.inject.Guice.createInjector(Guice.java:95)
at com.google.inject.Guice.createInjector(Guice.java:72)
at com.google.inject.Guice.createInjector(Guice.java:62)
at Main.main(Main.java:9)
I solved my problem using the annotation #AssistedInject. Quote from the javadoc:
When used in tandem with FactoryModuleBuilder, constructors annotated with #AssistedInject indicate that multiple constructors can be injected, each with different parameters.
So i add the annotation and a constructor to the SimpleData class:
public class SimpleData implements Data {
#AssistedInject
public SimpleData(#Assisted Path path) {
}
#AssistedInject
public SimpleData() {
}
}
i removed the #Nullable annotation from the factory:
import java.nio.file.Path;
public interface Factory {
Data create();
Data load(Path path);
}
#Nullable does not mean that if you don't have a binding, then null will be injected. It only allows writing bindings to null. If you don't have a binding and there is no applicable JIT-binding, then injection will fail.
Your factory's create() method requires Guice to find an #Assisted Path binding, but it obviously can't find it since you've never created one, so it fails.
Honestly, I'm not sure if there is a clean way to implement such defaulting. Ideally you should mark Path with some binding annotation and add a default binding to null for it, but #Assisted already is a binding annotation, and it is not possible to have multiple binding annotations on a single injection point. You can try creating a binding for #Assisted Path:
binder.bind(Path.class).annotatedWith(Assisted.class).toInstance(null);
However, I'm not sure if it would work because Assisted can be special to Guice. And even if it will work, it is not very clean - there may be conflicts with other assisted factories accepting Paths.
I would have Guice implement some kind of internal factory interface, then expose something else. Like this:
interface InternalFactory {
Data load(#Nullable Path path);
}
public interface Factory {
Data load();
Data load(#Nullable Path path);
}
class FactoryImpl implements Factory {
#Inject InternalFactory internalFactory;
#Override
public Data load() {
return load(null); // Pass your defaults here
}
#Override
public Data load(#Nullable Path path) {
// Sadly you'll have to explicitly forward arguments here, but it's not
// too bad IMO
return internalFactory.load(path);
}
}
public class MyModule extends AbstractModule {
#Override
protected void configure() {
install(new FactoryModuleBuilder()
.implement(Data.class, SimpleData.class)
.build(InternalFactory.class));
bind(Factory).to(FactoryImpl.class);
}
}

How to implement #RequestMapping custom properties

As an example, take subdomain mapping.
This article: Managing multiple Domain and Sub Domain on Google App Engine for Same Application
recommends to resolve subdomain on Filter and assign variable to ServletRequest headers.
Then the mapping will look like this:
#RequestMapping(value = "/path", headers="subdomain=www")
public String subsiteIndexPage(Model model,HttpServletRequest request) { ... }
If we'd like to create custom #RequestMapping property, such as subdomain, eg. to create mapping like this:
#RequestMapping(value = "/some/action", subdomain = "www")
public String handlerFunction(){ ... }
we should override #RequestMapping #interface definition and override RequestMappingHandlerMapping protected methods, with our own implementation
(as stated on JIRA: "Allow custom request mapping conditions SPR-7812").
Is it right? Can anybody provide a hint, how to achieve this functionality?
Idea 1:
As suggested on original jira thread, is to create own implementation of RequestCondition
There is an project which uses this solution available on github: https://github.com/rstoyanchev/spring-mvc-31-demo/
And related SO question: Adding custom RequestCondition's in Spring mvc 3.1
Maybe mapping like #Subdomain("www") for both Type and Method, is possible solution?
Link to same question on forum.springsource.com
I've created solution based on referenced spring-mvc-31-demo
This solution can be used to map only single RequestCondition as of now. I've created two Issues to notify, this should be changed:
https://github.com/rstoyanchev/spring-mvc-31-demo/issues/5
https://jira.springsource.org/browse/SPR-9350
This solution uses custom #RequestCondition feature of Spring 3.1.1.RELEASE platform
USAGE
Example 1:
#Controller
#SubdomainMapping(value = "subdomain", tld = ".mydomain.com")
class MyController1 {
// Code here will be executed only on address match:
// subdomain.mydomain.com
}
Example 2:
#Controller
class MyController2 {
#RequestMapping("/index.html")
#SubdomainMapping("www")
public function index_www(Map<Object, String> map){
// on www.domain.com
// where ".domain.com" is defined in SubdomainMapping.java
}
#RequestMapping("/index.html")
#SubdomainMapping("custom")
public function index_custom(Map<Object, String> map){
// on custom.domain.com
// where ".domain.com" is defined in SubdomainMapping.java
}
}
We need three files
SubdomainMapping.java
SubdomainRequestCondition.java
SubdomainRequestMappingHandlerMapping.java
SubdomainMapping.java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface SubdomainMapping {
/**
* This param defines single or multiple subdomain
* Where the Method/Type is valid to be called
*/
String[] value() default {};
/**
* This param defines site domain and tld
* It's important to put the leading dot
* Not an array, so cannot be used for mapping multiple domains/tld
*/
String tld() default ".custom.tld";
}
SubdomainRequestCondition.java
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
public class SubdomainRequestCondition implements
RequestCondition<SubdomainRequestCondition> {
private final Set<String> subdomains;
private final String tld;
public SubdomainRequestCondition(String tld, String... subdomains) {
this(tld, Arrays.asList(subdomains));
}
public SubdomainRequestCondition(String tld, Collection<String> subdomains) {
this.subdomains = Collections.unmodifiableSet(new HashSet<String>(
subdomains));
this.tld = tld;
}
#Override
public SubdomainRequestCondition combine(SubdomainRequestCondition other) {
Set<String> allRoles = new LinkedHashSet<String>(this.subdomains);
allRoles.addAll(other.subdomains);
return new SubdomainRequestCondition(tld, allRoles);
}
#Override
public SubdomainRequestCondition getMatchingCondition(
HttpServletRequest request) {
try {
URL uri = new URL(request.getRequestURL().toString());
String[] parts = uri.getHost().split(this.tld);
if (parts.length == 1) {
for (String s : this.subdomains) {
if (s.equalsIgnoreCase(parts[0])) {
return this;
}
}
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
return null;
}
#Override
public int compareTo(SubdomainRequestCondition other,
HttpServletRequest request) {
return org.apache.commons.collections.CollectionUtils.removeAll(other.subdomains, this.subdomains).size();
}
}
SubdomainRequestMappingHandlerMapping.java
import java.lang.reflect.Method;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
public class CustomRequestMappingHandlerMapping extends
RequestMappingHandlerMapping {
#Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
SubdomainMapping typeAnnotation = AnnotationUtils.findAnnotation(
handlerType, SubdomainMapping.class);
return createCondition(typeAnnotation);
}
#Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
SubdomainMapping methodAnnotation = AnnotationUtils.findAnnotation(
method, SubdomainMapping.class);
return createCondition(methodAnnotation);
}
private RequestCondition<?> createCondition(SubdomainMapping accessMapping) {
return (accessMapping != null) ? new SubdomainRequestCondition(
accessMapping.tld(), accessMapping.value()) : null;
}
}
Instalation
IMPORTANT: So far, it is not possible to use this solution with XML element
<mvc:annotation-driven />, see JIRA https://jira.springsource.org/browse/SPR-9344 for explanation
You have to register custom MappingHandler bean, pointing at this custom implementation SubdomainRequestMappingHandlerMapping class
You have to set it's order to be lower than default RequestMappingHandlerMapping
OR
Replace the registered RequestMappingHandlerMapping (possibly on order=0)
For more wide explanation on implementing this solution, see the related github project
That's correct, but that would be too complicated. You'd better check the Host header, whether it contains a given subdomain.
But you should not really need this more than once or twice, so you can also do it manually in the method body. If you really need it in many places, it would be an odd requirement.

Categories

Resources