How to Use SimpleUrlHandlerMapping with SpringBoot - spring

I am using SpringBoot and want to configure SimpleUrlHandlerMapping bean for my custom mapping. For that follow are the piece of code that I wrote.
#Configuration
public class WebConfiguration {
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
System.out.println("creating SimpleUrlHandlerMapping ....");
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
simpleUrlHandlerMapping.setOrder(0);
Properties urlProperties = new Properties();
urlProperties.put("/index", "myController");
simpleUrlHandlerMapping.setMappings(urlProperties);
return simpleUrlHandlerMapping;
}
}
I also have one Controller with name myController and its code looks like this.
#Controller("myController")
public class MyController extends AbstractController {
#Override
protected ModelAndView handleRequestInternal(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
System.out.println("My Controller!");
return null;
}
}
Now as per code when http://localhost:7171//index is hit then it should print the My Controller message on console. But it not touch this code.
Because this is an SpringBoot application and on start it print this bean registration with myController.
Could someone help to resolve this issue and tell me whats wrong in this code.
Thanks in advance.

#Autowire Controller Bean in Configuration class and pass it through Properties
SimpleUrlHandlerMapping is the most flexible HandlerMapping implementation. It allows for direct and declarative mapping between either bean instances and URLs or between bean names and URLs.
Let’s map requests “/simpleUrlWelcome” and “/*/simpleUrlWelcome” to the “welcome” bean: here
#Configuration
public class WebConfiguration {
#Autowired
private indexController index;
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
System.out.println("creating SimpleUrlHandlerMapping ....");
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
simpleUrlHandlerMapping.setOrder(0);
Properties<String,Object> urlProperties = new Properties<>();
urlProperties.put("/index", index);
simpleUrlHandlerMapping.setMappings(urlProperties);
return simpleUrlHandlerMapping;
}
}
Controller
#Controller("index")
public class indexController extends AbstractController {
#Override
protected ModelAndView handleRequestInternal(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
System.out.println("My Controller index!");
return null;
}
}

Related

Is it possible to define a custom rest template?

I'm trying to define a common bean to be used for all my application so to add inside a logger and other logic. My idea would be:
public class MyRestTemplate extends RestTemplate{
Then:
#Configuration
public class RestTemplateConfig {
#Bean
public MyRestTemplate myRestTemplate(RestTemplateBuilder builder){
return (MyRestTemplate) builder.build(); //throws classcast exception!
}
}
What am I doing wrong? Is there another way? I want to be sure that people will have to use my customized class.
If you want some customizations in your restTemplate you could define a class that implements RestTemplateCustomizer and add a custom interceptor to it.
public class CustomRestTemplateCustomizer implements RestTemplateCustomizer {
#Override
public void customize(RestTemplate restTemplate) {
restTemplate.getInterceptors().add(new CustomClientHttpRequestInterceptor());
}
}
Then you have to define that custom interceptor for all the requests going out of this restTemplate with
public class CustomClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
// This is where you can do a lot of thing with this request like logging
return execution.execute(request, body);
}
}
And finally, just define a bean for the custom restTemplate you have written
#Bean
public CustomRestTemplateCustomizer customRestTemplateCustomizer() {
return new CustomRestTemplateCustomizer();
}
builder.build() returns a RestTemplate, not a MyRestTemplate.
If you change your code as shown below you would create a bean named myRestTemplate. Spring use the name of the method as bean name if you don't override it in the #Bean annotation.
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate myRestTemplate(RestTemplateBuilder builder){
return builder.build(); //throws classcast exception!
}
}
Please also see https://docs.spring.io/spring-boot/docs/1.5.x/reference/html/boot-features-restclient.html

Springboot exception handling when there is no controllers

I have a spring-boot application without any controller classes.
How can I write exception handlers for this application. Exception handler classes annotated with #ControllerAdvice doesn't work.
If you are developing web applications, ErrroController is available.
#Controller
#RequestMapping("${server.error.path:${error.path:/error}}")
public class MyErrorController implements ErrorController {
private final ErrorAttributes errorAttributes;
public MyErrorController(final ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
#Override
public String getErrorPath() {
return null;
}
#RequestMapping
public ResponseEntity<Map<String, Object>> error(final HttpServletRequest request) {
final WebRequest webRequest = new ServletWebRequest(request);
final Throwable th = errorAttributes.getError(webRequest);
// ...
// see also: BasicErrorController implementation
}
}

Spring Webflux ErrorHandling - #RestControllerAdvice with #ExceptionHandler or DefaultErrorAttributes?

In Spring Webflux what is the prefered way of Exception Handling?
#RestControllerAdvice comes from Spring MVC whereas DefaultErrorAttributes comes from Spring Webflux.
However, in Spring Webflux someone could use #RestControllerAdvice. What would be the advantages/disadvantages?
#RestControllerAdvice
#RestControllerAdvice
public class ControllerAdvice
{
#ExceptionHandler(Throwable.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Mono<Map<String, Object>> exceptions(Throwable e)
{
return Mono.just(Map.of("message", "bad"));
}
}
Extend DefaultErrorAttributes
#Component
public class ErrorAttributes extends DefaultErrorAttributes
{
#Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace)
{
var ex = getError(request);
var attributes = new LinkedHashMap<String, Object>();
attributes.put("status", HttpStatus.BAD_REQUEST.value());
attributes.put("message", "bad");
return attributes;
}
}
I want to stay in the reactive world, so I tend more towards DefaultErrorAttributes (which plays well with DefaultErrorWebExceptionHandler in Webflux). However, in #RestControllerAdvice I could also use Mono.just(...).
It is same. Like WebMvc.
#RestControllerAdvice
public class ControllerAdvice {
#ExceptionHandler(AnyException.class)
public Mono<EntityResponse<YourModel>> example(AnyException exception) {
return EntityResponse.fromObject(new YourModel()).status(HttpStatus.NOT_FOUND).build();
}
}
In Spring Webflux in case functional routes declaration, you can also implement your own ExceptionHandler instead of DefaultErrorWebExceptionHandler:
class SystemErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
return super.handle(exchange, throwable)
// debug, process
.contextWrite(...);
}
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
// for all routs
return route(all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> error = getErrorAttributes(request, ErrorAttributeOptions.of());
Throwable t = this.getError(request);
// map exception on response
return ServerResponse.status(status).body(...);
}
}
Then use your implementation of AbstractErrorWebExceptionHandler in the spring configuration with #AutoConfigureBefore(WebFluxAutoConfiguration.class)

Spring Boot - Database based request mapping

Using Spring Boot 2 I want to create a database based request mapping. I mean, instead of using hundreds of #RequestMapping annotations on the controllers I would like to store the mapping in a database table.
Each of the controllers implements an interface that has an execute method so I simply search for the relevant controller in the DB and call the execute method on it.
At the moment I have a CustomController with a #RequestMapping("*") and this controller finds the real controller and calls the execute method. It works but it is not a good solution. For example at interceptor level the handler object is the CustomController and not the real controller.
Probably I should use the SimpleUrlHandlerMapping like this:
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
Map<String, Object> urlMap = new HashMap<>();
urlMap.put("/dashboard", __???__);
simpleUrlHandlerMapping.setUrlMap(urlMap);
return simpleUrlHandlerMapping;
}
But in this case I don't know how to fill the bean value in the urlMap. For example in case of "/dashboard" how to put the DashboardController.execute().
Maybe any better solution?
UPDATE 1
I have created a SimpleUrlHandlerMapping like this:
#Configuration
public class SimpleUrlHandlerMappingConfig {
#Autowired
private ApplicationContext context;
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
Map<String, Object> urlMap = new HashMap<>();
String path = "/dashboard";
String controllerName = "dashboardController";
Object myController = context.getBean(controllerName);
urlMap.put(path, myController);
simpleUrlHandlerMapping.setUrlMap(urlMap);
return simpleUrlHandlerMapping;
}
}
And a CustomHandlerAdapter as:
#Configuration
public class CustomHandlerAdapter implements HandlerAdapter {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public boolean supports(Object handler) {
logger.debug("Test handler: " + handler);
if (handler instanceof PageController) {
return true;
}
return false;
}
#Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.debug("Custom handle");
ModelAndView mv = new ModelAndView();
String viewName = ((PageController)handler).execute2(request, response);
mv.setViewName(viewName);
return mv;
}
#Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
But according to logs it seems that SimpleUrlHandlerMapping doesn't work correctly:
- DispatcherServlet with name 'dispatcherServlet' processing GET request for [/dashboard]
- Looking up handler method for path /dashboard
- Did not find handler method for [/dashboard]
- Matching patterns for request [/dashboard] are [/**]
- URI Template variables for request [/dashboard] are {}
- Mapping [/dashboard] to HandlerExecutionChain with handler [ResourceHttpRequestHandler [locations=[class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], ServletContext resource [/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver#4bc6044e]]] and 1 interceptor
- Test handler: ResourceHttpRequestHandler [locations=[class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], ServletContext resource [/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver#4bc6044e]]
- Last-Modified value for [/dashboard] is: -1
UPDATE 2
Thanks to #M. Deinum I have updated my code and have a working solution.
Please note that #EnableWebMvc was introduced and that can cause other side effects later.
The SimpleUrlHandlerMappingConfig:
#Configuration()
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleUrlHandlerMappingConfig {
#Autowired
private ApplicationContext context;
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
Map<String, Object> urlMap = new HashMap<>();
String path = "/dashboard";
String controllerName = "dashboardController";
Object myController = context.getBean(controllerName);
urlMap.put(path, myController);
simpleUrlHandlerMapping.setUrlMap(urlMap);
return simpleUrlHandlerMapping;
}
}
The CustomHandlerAdapter:
#Component
public class CustomHandlerAdapter implements HandlerAdapter {
#Override
public boolean supports(Object handler) {
if (handler instanceof PageController) {
return true;
}
return false;
}
#Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ModelAndView mv = new ModelAndView();
String viewName = ((PageController)handler).execute2(request, response);
mv.setViewName(viewName);
return mv;
}
#Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
And the WebConfig:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/style/**")
.addResourceLocations("classpath:" + "/static/style/");
registry.addResourceHandler("/js/**")
.addResourceLocations("classpath:" + "/static/js/");
}
}
If I understood correctly, you want to get rid of the simple actions (get/post/put/delete) -- and those only call the save/find/delete methods from the repository.
If that is the case I suggest using Spring Data REST
I post the final solution (thanks to M. Deinum) here maybe helping somebody else.
So I only created a HandlerMapping using the SimpleUrlHandlerMapping:
#Configuration()
public class SimpleUrlHandlerMappingConfig {
#Autowired
private ApplicationContext context;
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
Map<String, Object> urlMap = new HashMap<>();
String path = "/dashboard";
String controllerName = "dashboardController";
Object myController = context.getBean(controllerName);
urlMap.put(path, myController);
simpleUrlHandlerMapping.setUrlMap(urlMap);
simpleUrlHandlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
return simpleUrlHandlerMapping;
}
}
And a custom HandlerAdapter:
#Component
public class CustomHandlerAdapter implements HandlerAdapter {
#Override
public boolean supports(Object handler) {
if (handler instanceof PageController) {
return true;
}
return false;
}
#Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ModelAndView mv = new ModelAndView();
String viewName = ((PageController)handler).execute2(request, response);
mv.setViewName(viewName);
return mv;
}
#Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
Please note that this example demonstrates only the concept without proper error handling and real DB access.

Getting No bean resolver registered

After upgrading today from Spring boot 1.2.5 to 1.3.0 BUILD-SNAPSHOT Calling
#PreAuthorize fails:
example:
#PreAuthorize("#defaultSecurityService.canDoSomething(authentication.principal.id, #objId)")
Result doSomething(#P("objId")String objId);
where defaultSecurityService is defined as:
#Service
public class DefaultSecurityService implements SecurityService {
...
public boolean canDoSomething(String userId, String objId){
return true; //
}
}
Stack trace
Caused by: java.lang.IllegalArgumentException: Failed to evaluate expression '#oauth2.throwOnError(defaultSecurityService.canDoSomething(authentication.principal.id, #objId))'
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:14)
...
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1057E:(pos 8): No bean resolver registered in the context to resolve access to bean 'defaultSecurityService'
what i've tried:
make SecurityService extend [PermissionEvaluator][1] and register a bean
atApplication.java`
#Bean
#Lazy
public PermissionEvaluator permissionEvaluator(){
return securityService;
}`
But i'm still getting the same error
Reading the spring security 4.0.2 documentation didn't reveal any relevant material about breaking changes
This appears to be a bug in the newly added OAuth2AutoConfiguration. Specifically it brings in OAuth2MethodSecurityConfiguration which overrides the DefaultMethodSecurityExpressionHandler with a OAuth2MethodSecurityExpressionHandler that does not have a BeanResolver set.
If you are not using OAuth2, then the easiest solution is to remove Spring Security OAuth from your classpath.
Alternatively, you can exclude the OAuth2AutoConfiguration using the following if you use #SpringBootApplication:
#SpringBootApplication(exclude=OAuth2AutoConfiguration.class)
alternatively you can use the following if you leverage #AutoConfiguration directly:
#AutoConfiguration(exclude=OAuth2AutoConfiguration.class)
UPDATE
You can also use something like this:
public class DelegatingMethodSecurityExpressionHandler implements
MethodSecurityExpressionHandler {
private final MethodSecurityExpressionHandler delegate;
public DelegatingMethodSecurityExpressionHandler(
MethodSecurityExpressionHandler delegate) {
super();
this.delegate = delegate;
}
public Object filter(Object filterTarget, Expression filterExpression,
EvaluationContext ctx) {
return delegate.filter(filterTarget, filterExpression, ctx);
}
public ExpressionParser getExpressionParser() {
return delegate.getExpressionParser();
}
public EvaluationContext createEvaluationContext(
Authentication authentication, MethodInvocation invocation) {
return delegate.createEvaluationContext(authentication, invocation);
}
public void setReturnObject(Object returnObject, EvaluationContext ctx) {
delegate.setReturnObject(returnObject, ctx);
}
}
Then in your configuration use:
#Autowired(required = false)
List<AuthenticationTrustResolver> trustResolvers = new ArrayList<>();
#Autowired(required = false)
List<PermissionEvaluator> permissionEvaluators = new ArrayList<>();
#Bean
public MethodSecurityExpressionHandler securityExpressionHandler(ApplicationContext context) {
OAuth2MethodSecurityExpressionHandler delegate = new OAuth2MethodSecurityExpressionHandler();
delegate.setApplicationContext(context);
if(trustResolvers.size() == 1) {
delegate.setTrustResolver(trustResolvers.get(0));
}
if(permissionEvaluators.size() == 1) {
delegate.setPermissionEvaluator(permissionEvaluators.get(0));
}
return new DelegatingMethodSecurityExpressionHandler(delegate);
}
We have to wrap it in the DelegatingMethodSecurityExpressionHandler because Spring Boot's auto config will replace any subclass of DefaultMethodSecurityExpressionHandler with the broken configuration.
I had the same problem than you, my bean in charge of managing security on a REST controller wasn't found:
org.springframework.expression.spel.SpelEvaluationException: EL1057E:(pos 8): No bean resolver registered in the context to resolve access to bean 'communitySecurityAuthorizer
Rob's reply pointed me in the right direction (I thought I was doing it wrong, not that it was a bug in the standard Spring OAuth2).
I don't use springboot as I'm making a webapp and I found the answer that solved my problem here:
https://github.com/spring-projects/spring-security-oauth/issues/730#issuecomment-219480394
The problem comes in fact from the bean resolver which is null so here is the solution (retranscription of the link above):
Add a #Bean with OAuth2WebSecurityExpressionHandler that explicitly
sets the application context
#Bean
public OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler(ApplicationContext applicationContext) {
OAuth2WebSecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler();
expressionHandler.setApplicationContext(applicationContext);
return expressionHandler;
}
In the ResourceServerConfigurerAdapter, configure the resources and
pass in the Bean above.
#Autowired
private OAuth2WebSecurityExpressionHandler expressionHandler;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.expressionHandler(expressionHandler);
}
Hope this'll others !
As Almiriad has said, generate the OAuth2MethodSecurityExpressionHandler instance as a bean.
Instead do that:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
....
}
do this:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return getOAuth2MethodSecurityExpressionHandler();
}
#Bean
public OAuth2MethodSecurityExpressionHandler getOAuth2MethodSecurityExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
....
}
Hope this'll others !

Resources