I am new to web app development. I am trying to expose the category ID:s so that I can implement "search by category" in my app.
But even after writing the below code I am not able to expose the ID:s.
I have seen some solutions, but they were of very little use to me because of the incompetence.
I am using Eclipse IDE.
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.persistence.metamodel.EntityType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.http.HttpMethod;
import com.kausar.ecommerce.entity.Product;
import com.kausar.ecommerce.entity.ProductCategory;
#Configuration
public class MyDataRestConfig implements RepositoryRestConfigurer {
private EntityManager entityManager;
#Autowired
public MyDataRestConfig(EntityManager theEntityManager) {
entityManager = theEntityManager;
}
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
HttpMethod[] theUnsupportedActions = {HttpMethod.PUT, HttpMethod.POST, HttpMethod.DELETE};
// disable HTTP methods for Product: PUT, POST and DELETE
config.getExposureConfiguration()
.forDomainType(Product.class)
.withItemExposure((metdata, httpMethods) -> httpMethods.disable(theUnsupportedActions))
.withCollectionExposure((metdata, httpMethods) -> httpMethods.disable(theUnsupportedActions));
// disable HTTP methods for ProductCategory: PUT, POST and DELETE
config.getExposureConfiguration()
.forDomainType(ProductCategory.class)
.withItemExposure((metdata, httpMethods) -> httpMethods.disable(theUnsupportedActions))
.withCollectionExposure((metdata, httpMethods) -> httpMethods.disable(theUnsupportedActions));
// call an internal helper method
exposeIds(config);
}
private void exposeIds(RepositoryRestConfiguration config) {
// expose entity ids
//get a list of all entity classes from the entity manager
Set<EntityType<?>> entities = entityManager.getMetamodel().getEntities();
//create an array of the entity types
List<Class> entityClasses = new ArrayList<>();
//get the entity types for the entities
for (EntityType tempEntityType : entities) {
entityClasses.add(tempEntityType.getJavaType());
}
//expose entity ids for the array of entity/domain types
Class[] domainTypes = entityClasses.toArray(new Class[0]);
config.exposeIdsFor(domainTypes);
}
}
just call the helper method and remove the other code :
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
exposeIds(config);
}
you need to override this method of RepositoryRestConfigurer interface :
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
//all code
}
it is perfectly working for me.....
CorsRegistry cors => add this as a second parameter in configureRepositoryRestConfiguration method
Related
I have really poor experience with ribbon/eureka so forgive me if this is a stupid question:
I have two different microservice both connected to a discovery server, the first one calls the second using a custom annotation that sends a request using rest template.
Custom annotation name is PreHasAuthority
Controller :
#PreHasAuthority(value="[0].getProject()+'.requirements.update'")
#PostMapping(CREATE_UPDATE_REQUIREMENT)
public ResponseEntity<?> createUpdateRequirement(#Valid #RequestBody RequirementDTO requirementDTO
, HttpServletRequest request, HttpServletResponse response) {
return requirementService.createUpdateRequirement(requirementDTO, request, response);
}
Annotation interface :
import java.lang.annotation.*;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface PreHasAuthority {
String value();
}
Annotation implementation:
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import netcomgroup.eu.service.AuthenticationService;
#Aspect
#Component
public class PreHasAuthorityServiceAspect {
#Autowired
private AuthenticationService authenticationService;
#Around(value = "#annotation(PreHasAuthority)")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
PreHasAuthority preHasAuthority = method.getAnnotation(PreHasAuthority.class);
Object[] args = joinPoint.getArgs();
String permission = preHasAuthority.value();
ExpressionParser elParser = new SpelExpressionParser();
Expression expression = elParser.parseExpression(permission);
String per = (String) expression.getValue(args);
String token =null;
for(Object o : args) {
if(o instanceof HttpServletRequest) {
HttpServletRequest request = (HttpServletRequest)o;
token=request.getHeader("X-Auth");
break;
}
}
if(token==null) {
throw new IllegalArgumentException("Token not found");
}
boolean hasPerm = authenticationService.checkPermission(per,token);
if(!hasPerm)
throw new Exception("Not Authorized");
}
}
My Ribbon configuration
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
public class RibbonConfiguration {
#Autowired
IClientConfig config;
#Bean
public IRule ribbonRule(IClientConfig config) {
return new RoundRobinRule();
}
}
Eureka config in application properties
#Eureka config
eureka.client.serviceUrl.defaultZone= http://${registry.host:localhost}:${registry.port:8761}/eureka/
eureka.client.healthcheck.enabled= true
eureka.instance.leaseRenewalIntervalInSeconds= 10
eureka.instance.leaseExpirationDurationInSeconds= 10
by calling the api from postman request is sendend correctly to the second microservice and i'm certain the return is "true".
After that the request stops before entering the createUpdateRequirement method and returns '1' as postman body response. No error of sort is provided.
My guess is that the problem resides within the custom annotation, cause when i remove the annotation the api call works perfectly, but i cannot understand the problem as it seems all setted up correctly to me.
Your #Around advice never calls joinPoint.proceed(). Hence, the intercepted target method will never be executed.
The second problem is that your advice method returns void, i.e. it will never match any method returning another type such as the ResponseEntity<?> createUpdateRequirement(..) method.
Besides, around is a reserved keyword in native AspectJ syntax. Even though it might work in annotation-driven syntax, you ought to rename your advice method to something else like aroundAdvice or interceptPreHasAuthority - whatever.
Please do read an AspectJ or Spring AOP tutorial, especially the Spring manual's AOP chapter. 😉
I have the following SecurityScheme definition using springdoc-openapi for java SpringBoot RESTful app:
#Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.components(new Components().addSecuritySchemes("bearer-jwt",
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")
.in(SecurityScheme.In.HEADER).name("Authorization")))
.info(new Info().title("App API").version("snapshot"));
}
Is it possible to apply it globally to all paths, without having to go and add #SecurityRequirement annotations to #Operation annotation everywhere in the code?
If it is, how to add exclusions to unsecured paths?
Yes, you can do it in the same place calling addSecurityItem:
#Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.components(new Components().addSecuritySchemes("bearer-jwt",
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")
.in(SecurityScheme.In.HEADER).name("Authorization")))
.info(new Info().title("App API").version("snapshot"))
.addSecurityItem(
new SecurityRequirement().addList("bearer-jwt", Arrays.asList("read", "write")));
}
Global security schema can be overridden by a different one with the #SecurityRequirements annotation. Including removing security schemas for an operation. For example, we can remove security for registration path.
#SecurityRequirements
#PostMapping("/registration")
public ResponseEntity post(#RequestBody #Valid Registration: registration) {
return registrationService.register(registration);
}
While still keeping security schemas for other APIs.
Old answer (Dec 20 '19):
Global security schema can be overridden by a different one with the #SecurityRequirements annotation. but it cannot be removed for unsecured paths. It is acctualy missing fueature in the springdoc-openapi, OpenAPI standard allows it. See disable global security for particular operation
There is a workaround though. The springdoc-openapi has a concept of an OpenApiCustomiser which can be used to intercept generated schema. Inside the customizer, an operation can be modified programmatically. To remove any inherited security, the field security needs to be set to an empty array. The logic may be based on any arbitrary rules e.g operation name. I used tags.
The customizer:
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import org.springdoc.api.OpenApiCustomiser;
import org.springframework.stereotype.Component;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
#Component
public class SecurityOverrideCustomizer implements OpenApiCustomiser {
public static final String UNSECURED = "security.open";
private static final List<Function<PathItem, Operation>> OPERATION_GETTERS = Arrays.asList(
PathItem::getGet, PathItem::getPost, PathItem::getDelete, PathItem::getHead,
PathItem::getOptions, PathItem::getPatch, PathItem::getPut);
#Override
public void customise(OpenAPI openApi) {
openApi.getPaths().forEach((path, item) -> getOperations(item).forEach(operation -> {
List<String> tags = operation.getTags();
if (tags != null && tags.contains(UNSECURED)) {
operation.setSecurity(Collections.emptyList());
operation.setTags(filterTags(tags));
}
}));
}
private static Stream<Operation> getOperations(PathItem pathItem) {
return OPERATION_GETTERS.stream()
.map(getter -> getter.apply(pathItem))
.filter(Objects::nonNull);
}
private static List<String> filterTags(List<String> tags) {
return tags.stream()
.filter(t -> !t.equals(UNSECURED))
.collect(Collectors.toList());
}
}
Now we can add #Tag(name = SecurityOverrideCustomizer.UNSECURED) to unsecured methods:
#Tag(name = SecurityOverrideCustomizer.UNSECURED)
#GetMapping("/open")
#ResponseBody
public String open() {
return "It works!";
}
Please bear in mind that it is just a workaround. Hopefully, the issue will be resolved in the next springdoc-openapi versions (at the time of writing it the current version is 1.2.18).
For a working example see springdoc-security-override-fix
Tested with v1.2.29 of springdoc-openapi: Its possible to disable security for particular Endpoint using: #SecurityRequirements
#GetMapping("/open")
#ResponseBody
#SecurityRequirements
public String open() {
return "It works!";
}
For older versions, for example tested with v1.2.28 using OperationCustomizer:
public static final String UNSECURED = "security.open";
#Bean
public OperationCustomizer customize() {
return (Operation operation, HandlerMethod handlerMethod) -> {
List<String> tags = operation.getTags();
if (tags != null && tags.contains(UNSECURED)) {
operation.setSecurity(Collections.emptyList());
operation.setTags(tags.stream()
.filter(t -> !t.equals(UNSECURED))
.collect(Collectors.toList()));
}
return operation;
};
}
According to Documentation of springdoc, for new versions you can do this
Add the #SecurityRequirement for the protected route like
#PostMapping(value = "/example")
#SecurityRequirement(name = "bearer-key")
public ResponseEntity<Object> exampleHandler() {
/// logic here
}
and then in your Security config class add
#Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.components(new Components()
.addSecuritySchemes("bearer-key",
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")));
}
Here is the link to the docs
Requirement: Read custom annotation details and generate report for all test classes of all suites.
Tried Solution:
Implemented custom listener using ITestListener. But don't see direct way to get custom annotation details used as part of test methods apart from below way.
#Override
public void onStart(ITestContext context) {
ITestNGMethod[] testNGMethods = context.getAllTestMethods();
for (ITestNGMethod testNgmethod : testNGMethods) {
Method[] methods = testNgmethod.getRealClass().getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(MyCustomAnnotation.class)) {
//Get required info
}
}
}
}
Inner loop triggers almost n*n(number of methods) times for each test class. I can control it by adding conditions.
As I'm new bee to TestNG framework, would like to know the better solution to achieve my requirement i.e. generating report by reading custom annotation details from all test methods from all suites.
Here's how you do it.
I am using the latest released version of TestNG as of today viz., 7.0.0-beta3 and using Java8 streams
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestNGMethod;
public class MyListener implements ITestListener {
#Override
public void onStart(ITestContext context) {
List<ITestNGMethod> methodsWithCustomAnnotation =
Arrays.stream(context.getAllTestMethods())
.filter(
iTestNGMethod ->
iTestNGMethod
.getConstructorOrMethod()
.getMethod()
.getAnnotation(MyCustomAnnotation.class)
!= null)
.collect(Collectors.toList());
}
#Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
#Target({METHOD, TYPE})
public static #interface MyCustomAnnotation {}
}
I've got Guice configuring Jersey, and it's working pretty well. Here's what I'm doing so far:
import com.google.common.collect.ImmutableMap;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.servlet.ServletScopes;
import com.company.app.ejb.lookup.EjbProvider;
import com.company.app.rest.exception.ConditionExceptionMapper;
import com.company.app.rest.exception.InvalidSortExceptionMapper;
import com.company.app.rest.exception.NotFoundExceptionMapper;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.guice.JerseyServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
/*
From http://randomizedsort.blogspot.com/2011/05/using-guice-ified-jersey-in-embedded.html
*/
public class GuiceServletConfig extends GuiceServletContextListener {
#Override
protected Injector getInjector() {
return Guice.createInjector(new JerseyServletModule() {
#Override
protected void configureServlets() {
install(new EjbProvider());
install(new JacksonJsonProviderProvider());
// Must configure at least one JAX-RS resource or the
// server will fail to start.
bind(RestApiVersion1.class).in(ServletScopes.REQUEST);
bind(RestApiVersion2.class).in(ServletScopes.REQUEST);
bind(ConditionExceptionMapper.class).in(Singleton.class);
bind(InvalidSortExceptionMapper.class).in(Singleton.class);
bind(NotFoundExceptionMapper.class).in(Singleton.class);
// bind(ResourceConfigClass.class).in(Singleton.class); this does not work :(
// Route all requests through GuiceContainer
serve("/*").with(GuiceContainer.class, ImmutableMap.of(JSONConfiguration.FEATURE_POJO_MAPPING, "true"));
}
});
}
}
I've got this ResourceConfigClass that is very simple, it looks like this:
import com.sun.jersey.api.core.PackagesResourceConfig;
import javax.ws.rs.core.MediaType;
import java.util.HashMap;
import java.util.Map;
public class ResourceConfigClass extends PackagesResourceConfig {
public ResourceConfigClass(String... packages) { //this constructor needs to be here, do not delete it or else the com.sun.jersey.config.property.packages param can't be passed in.
super(packages);
}
public ResourceConfigClass(Map<String, Object> props) { //this constructor needs to be here, do not delete it or else the com.sun.jersey.config.property.packages param can't be passed in.
super(props);
}
#Override
public Map<String, MediaType> getMediaTypeMappings() {
Map<String, MediaType> map = new HashMap<String, MediaType>();
map.put("xml", MediaType.APPLICATION_XML_TYPE);
map.put("json", MediaType.APPLICATION_JSON_TYPE);
return map;
}
}
But, I can't figure out how to configure Guice to use this ResourceConfigClass I've made. If I bind it, I get this error on startup:
#|2014-06-04T10:11:03.324-0700|SEVERE|glassfish3.1.2|javax.enterprise.system.tools.admin.org.glassfish.deployment.admin|_ThreadID=345;_ThreadName=Thread-2;|Exception while loading the app : java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: com.google.inject.CreationException: Guice creation errors:
1) Could not find a suitable constructor in com.company.app.rest.resource.ResourceConfigClass. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private.
at com.company.app.rest.resource.ResourceConfigClass.class(ResourceConfigClass.java:13)
at com.company.app.rest.resource.GuiceServletConfig$1.configureServlets(GuiceServletConfig.java:38)
Before I was using Guice, I could use this class by putting this in my web.xml:
<init-param>
<param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
<param-value>com.company.app.rest.resource.ResourceConfigClass</param-value>
</init-param>
I don't know how to set that property in the GuiceServletConfig, though. How do I set the media type mappings when I'm using jersey with guice?
I was able to do it this way:
serve("/*").with(GuiceContainer.class, ImmutableMap.of(
JSONConfiguration.FEATURE_POJO_MAPPING, "true",
"com.sun.jersey.config.property.packages", "com.fasterxml.jackson.jaxrs.json",
"com.sun.jersey.config.property.resourceConfigClass", "com.company.app.rest.resource.ResourceConfigClass"
));
But I found a more direct way to do this:
serve("/*").with(GuiceContainer.class, ImmutableMap.of(
JSONConfiguration.FEATURE_POJO_MAPPING, "true",
ResourceConfig.PROPERTY_MEDIA_TYPE_MAPPINGS, "xml:" + MediaType.APPLICATION_XML_TYPE + ",json:" + MediaType.APPLICATION_JSON_TYPE
));
Now I don't need the extra ResourceConfigClass class. This format was documented in com.sun.jersey.api.core.ResourceConfig#PROPERTY_MEDIA_TYPE_MAPPINGS
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.