#ControllerAdvice being called for all Controllers, not just basePackageClasses [duplicate] - java

This question already has answers here:
How to target specific handlers with a #ControllerAdvice #ModelAttribute?
(2 answers)
Closed 5 years ago.
I cannot provide the actual code since it is on another system but the issue I am seeing in my Spring 4.2.6 Web Release is the use of basePackageClasses not actually mapping to the controller.
Let's say I have 3 Controllers: AController.class, BController.class, and CController.class.
For each I have created a ControllerAdvice: AControllerAdvice.class,BControllerAdvice.class & CControllerAdvice.class.
I use the annotation as follows:
#ControllerAdvice(basePackageClasses = AController.class)
public class AControllerAdvice{
#ModelAttribute
public void addModelInformation(Model model){
//controller specific model information for header and footer added here
}
}
What I am experiencing is that when I call a rest method in AController.class, I have verified that via logging, that the 'addModelInformation' method is being called from all three ControllerAdvice's and thus the last one called by Spring's ordering sets the header and footer with the net effect that the header and footer remain static.
Via documentation Spring should support and my understanding is that only the ControllerAdvice for the rest method residing within that mapped controller should be called. So I have to conclude that I have set up something wrong. I am using JavaConfig and look forward to hearing from you guys!

You should use assignableTypes:
#ControllerAdvice(assignableTypes = AController.class)
public class AControllerAdvice {
#ModelAttribute
public void addModelInformation(Model model){
//controller specific model information for header and footer added here
}
}

You should pass the config classes to #ControllerAdvice's basePackageClasses not controller classes.
Assume you have Configuration named with ConfigClass
#Configuration
public class ConfigClass {
#Bean
public AController controllerA() {
....
}
... // your controllers to be weaved
}
Then you should setup advice with
#ControllerAdvice(basePackageClasses = ConfigClass.class)

Related

How to enforce Aspect implementation is loaded when its corresponding annotation is used?

I have a created an annotation that verifies whether certain security aspects are correct.
For example, #RequireClientCertificate, with an Aspect implementation RequireClientCertificateAspect that verifies whether the correct HTTP header is indeed passed in to the Spring REST controller.
This works totally fine, IF the RequireClientCertificateAspect is actually loaded, i.e. if its package is mentioned somewhere in #ComponentScan().
However, if someone forgets to add this package to #ComponentScan, or the aspect is moved to another package, or someone (accidentally) removes the package from #ComponentScan, the aspect bean isn't loaded, and the aspect is completely not applied.
I have this annotation in a common library, shared by several microservices, so it's easy for one of the microservices to accidentally get it wrong. In that case, no checking of the client certificate would be performed.
Question: How can I enforce that, if the #RequireClientCertificate annotation is used, its corresponding Aspect implementation is also loaded?
Simplified usage example:
#Controller
#RequestMapping(value = "/v1.0", produces = MediaType.APPLICATION_JSON_VALUE)
#RequireClientCertificate
public class SomeApiController {
#ResponseBody
#PostMapping("/get-token/")
public ResponseEntity<Token> getToken() {
return ResponseEntity.ok(...get token...);
}
}
Simplified version of the aspect:
#Aspect
#Component
public class RequireClientCertificateAspect {
#Around("execution(* (#RequireClientCertificate *).*(..))")
public Object requireClientCertificateAspectImplementation(ProceedingJoinPoint joinPoint) throws Throwable {
... verify request header ...
try {
return joinPoint.proceed();
finally {
... some other things I need to check ...
}
}
}
Things I've tried/considered:
I can detect 'usage' of the annotation by adding a static field with an initializer to the interface. For example:
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface RestFactoryGatewaySecurityContext {
static public final boolean dummy = SomeClass.checkAspectIsLoaded();
}
However, such initializers are called very early, and I don't think Spring DI is 'up and running' far enough at that stage that I could even reliably determine whether the aspect bean is loaded.
Another option is to use #Autowired to inject the RequireClientCertificateAspect bean on the main app class explicitly. If somehow the bean isn't on the component scan, this will prevent Spring from instantiating the app.
So that does work, but requires someone to explicitly add this 'dummy' autowire, which in itself is easy to forget, in addition to being a bit 'ugly'.
If you use spring boot you can create your own starter.
Create file META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.MyCustomConfiguration
Then just add any validation you want to your configuration
#Configuration
public class MyCustomConfiguration{
}
You can #Autowired your RequireClientCertificateAspect into it, which will cause error if it isn't defined.
You can create method with #PostConstruct and do any validation you want.
If you went so far as creating custom starter, you can just initialize your bewns there.
More about it you can read here

Spring MVC RequestMapping requires trailing slash

There seems to be weird behavior which I can't seem to pinpoint the reason for. When I access a particular url I get a 404 response while other urls that are handled by the same controller class works. I have to add a trailing / to the end of the url in order for the method to be called.
This method DOES NOT get called when accessing localhost:8080/newprofile
#RequestMapping(value="/newprofile", method=RequestMethod.GET)
public String newProfile(Model model, Principal principal) {
return "newprofile";
}
However, this one DOES get called when accessing localhost:8080/login
#GetMapping("/login")
public String login() {
return "login";
}
I have tried both GetMapping and RequestMapping but the methods are never called.
Both methods are contained in my controller class
#Controller
public class HomeResources {
//login
//new profile
}
There is a setting responsible for such behavior:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.html#setUseTrailingSlashMatch-boolean-
Just turn it off:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseTrailingSlashMatch(false);
}
}
Note that the default behaviour of setUseTrailingSlashMatch changed from true to false since 6.0 in order to support the deprecation of the property.
If you want this to be enabled you have to set it to true now. But as it is marked deprecated, probably best not to do it and instead follow the advice in this Spring Boot 3.0.0 M4 Release Notes
Developers should instead configure explicit redirects/rewrites through a proxy, a Servlet/web filter, or even declare the additional route explicitly on the controller handler (like #GetMapping("/some/greeting", "/some/greeting/") for more targeted cases.
Spring Boot 3+ TLDR
#Configuration
public class WebConfiguration implements WebMvcConfigurer {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseTrailingSlashMatch(true);
}
}
}
Be sure to use as above for Spring Boot 3+ (that uses Spring 6)
to have URL ending with '/' still being processed by mapping in Controllers without ending '/'.
Longer answer:
Note for the snippet the value is exactly true
and #EnableWebMvc is not used with Spring Boot
(as it would in fact disable autoconfiguration for web MVC)
That was exactly recommended in
https://github.com/spring-projects-experimental/spring-boot-migrator/issues/206
"Spring Boot 3.0.0 M4 Release Notes"
And because there are reasons why Spring became less forgiving,
( see "Deprecate trailing slash match and change default value from true to false"
https://github.com/spring-projects/spring-framework/issues/28552 )
I think we should better to think as bad habit to use URLs ending with '/', that now require extra attention.

Spring Boot accessing dao service from custom class [duplicate]

This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Role of new keyword in Spring Framework
(1 answer)
Closed 3 years ago.
I am trouble with accessing my service to save data in a class.
I have a class and wish to use my defined service that works well in my RestController by am struggling to access it in custom class.
I wish to do something like the following new MessageProcessor.processMessage(message) --> this should access service and save the data to database.
#Controller
public class MessageProcessor {
#Autowired
private IOTService iotService;
public MessageProcessor() {
}
public void processMessage(String message) {
iotService.addMessage(message);
}
}
It is being called like this in a random class.
The Autowired annotion is not working as i expected.
new MessageProcessor().processMessage("test");
I am new to Spring so any suggestions would be appreciated
First of all, you need to understand how the framework work. I am giving an example, may be it clears some of your doubts and logic.
The autowiring works in a way like, you have to follow a standard :
Make a domain class (Domain.class).
Make a controller (DomainController.class)(if there is need), mark it with annotation #Controller.
Make a service class,(DomainService.class), mark it with annotation #Service
In this way, you can use autowiring concept. If you want to use your service class method outside this class hierarchy, you have create its new instance with new keyword i.e. new DomainService()
The thing to be noted here autowiring works within a specific model not everywhere.

Why does Spring Boot not read the PathVariable that is defined on my interface resource?

I am using a custom code generator to transform a REST contract in to Java interfaces. Below find a sample of a generated resource Java interface.
#Generated(
date = "2018-01-30T11:56:25.156Z",
comments = "Specification filename: country.v1.json",
value = "GENERATOR"
)
#RequestMapping("/v1/countries")
public interface CountriesResource {
#RequestMapping(
method = RequestMethod.GET,
path = "/{id}",
produces = MediaType.APPLICATION_JSON_VALUE
)
#ResponseBody
LandGetResourceModel getEntity(#PathVariable("id") String id);
}
Additionally the generator creates an #RestController implementation a that interface for Spring to create a controller bean.
#RestController
public class CountriesResourceImpl implements CountriesResource {
#Override
public CountryGetResourceModel getEntity(String id) {
return new CountryGetResourceModel();
}
}
So far everything works fine. Spring creates the RestController bean and the HTTP calls are directed correctly to the corresponding Handler method (e.g. getEntity).
The problem is that for some reason Spring is not able to resolve path variables when they are defined on the interface. All calls containing a path variable are handled but the method parameter of any path variable is null. If I then add the PathVariable annotation to the implementation class Spring is able to resolve the corresponding value.
Is there a way to tell Spring to read the PathVariable from the interface method declaration like it does for the RequestMapping annotations?
Unfortunately, seems like it is a known issue. I've found the following opened Jira on the question. Some explanation can be found from this answer. Also, in the same question I took the previous answer I see this solution. However, I haven't tried it and it seems to be heavy.

Why #Scheduled annotation doesn't work with #Transaction annotation. Spring Boot [duplicate]

This question already has answers here:
Spring3 's #Transactional #Scheduled not committed to DB?
(3 answers)
Closed 2 years ago.
I have a question:
Why when we annotate method with #Scheduled and #Transaction, transaction doesn't work?
I know that the #Scheduled call my class instead of proxy class that created by Spring, but can't understand this behavior.
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;
#Service
public class UserServiceImpl implements UserService {
#Override
#Scheduled(fixedRateString = "${somestring}",initialDelayString = "${anotherstring}")
#Transactional
public void doSomething() {
}
}
I have two solutions of this problem:
Call proxy from Scheduled method.
Implement ConcurrentTaskScheduler
and replace object of ScheduledMethodRunnable(that is with my class)
with object of ScheduledMethodRunnable with proxy.
But this solutions is very inconvenient.
Can you explaim me why #Scheduled works like this?
Thank you!
It happens because to process both annotations MAGIC is used.
I suppose there are several things happens:
UserServiceImpl is created.
#Scheduled annotation is processed and reference to bean is stored to invoke it at appropriate time.
#Transactional annotation is processed. It create proxy which store reference to original bean. Original bean is replaced to proxy in application context.
If step 2 and 3 passed in different order then you had no problem.
I don't know how to control order in which annotation is processed. I don't even sure it is possible at all.
There is basically two solution.
Use different kind of magic to process #Transaction. Default way is to create proxy object, but it is possible to instruct Spring to instrument current class.
Split this to two class each of them will have method with only one annotation.
Example:
#Service
public class UserServiceImpl implements UserService {
#Override
#Transactional
public void doSomething() {
}
}
#Service
public class UserServiceScheduler {
#Inject
private UserService service;
#Scheduled(fixedRateString = "${somestring}",initialDelayString = "${anotherstring}")
public void doSomething() {
service.doSomething();
}
}
I'm personally recommend second approach.
The Question is not private or public, the question is: How is it invoked and which AOP implementation you use!
If you use (default) Spring Proxy AOP, then all AOP functionality provided by Spring (like #Transational) will only be taken into account if the call goes through the proxy. -- This is normally the case if the annotated method is invoked from another bean.
This has two implications:
Because private methods must not be invoked from another bean (the exception is reflection), their #Transactional Annotation is not taken into account.
If the method is public, but it is invoked from the same bean, it will not be taken into account either (this statement is only correct if (default) Spring Proxy AOP is used).
you can also use the aspectJ mode, instead of the Spring Proxies, that will overcome the problem. And the AspectJ Transactional Aspects are woven even into private methods (checked for Spring 3.0).
refer: http://docs.spring.io/spring/docs/3.2.4.RELEASE/spring-framework-reference/html/aop.html#aop-proxying

Categories