Spring won't inject a JPARepository bean - java

Dao
#Repository
public interface LoginDao extends JpaRepository<Login, Integer> {
Login findByLogin(String login);
}
Validator
#Component
public class PasswordChangeValidator implements Validator {
private LoginDao loginDao;
#Override
public boolean supports(Class<?> aClass) {
return PasswordChange.class.equals(aClass);
}
#Override
public void validate(Object o, Errors errors) {
PasswordChange passwordChange = (PasswordChange) o;
**// There is a null pointer here because loginDao is null**
Login login = loginDao.findByLogin(passwordChange.getLoginKey());
}
public LoginDao getLoginDao() {
return loginDao;
}
#Autowired
public void setLoginDao(LoginDao loginDao) {
**// There is a debug point on the next line and it's hit on server startup and I can
// see the parameter us non-null**
this.loginDao = loginDao;
}
}
Controller
#Controller
#RequestMapping("api")
public class PasswordController {
#Autowired
PasswordService passwordService;
#InitBinder("passwordChange")
public void initBinder(WebDataBinder webDataBinder, WebRequest webRequest) {
webDataBinder.setValidator(new PasswordChangeValidator());
}
#RequestMapping(value = "/passwordChange", method = RequestMethod.POST)
public #ResponseBody PasswordInfo passwordInfo(#RequestBody #Valid PasswordChange passwordChange)
throws PasswordChangeException {
return passwordService.changePassword(passwordChange.getLoginKey(), passwordChange.getOldPassword(), passwordChange.getNewPassword());
}
}
I have the Dao listed above. This same dao bean gets injected in an #Service annotated class but not in #Component annotated Validator class. Well, not exactly the upon server startup I can see that the setter method gets called, but when I try to use this variable in a method the variable shows as null.
Does anybody see a problem with my configuration ? Please note that the loginDao bean gets injected into a service class, so the Context configuration is good.

Well there's your problem
webDataBinder.setValidator(new PasswordChangeValidator());
Spring can only manage beans it created. Here, you're creating the instance. Instead inject your bean into the #Controller and use it.
#Inject
private PasswordChangeValidator passwordChangeValidator;
...
webDataBinder.setValidator(passwordChangeValidator);

Related

Autowired object populated using ThreadLocalTargetSource does not populate for every class

My Spring Boot application implements the TenantStore example for storing data in ThreadLocalTargetSource detailed in this link
#Bean(destroyMethod = "destroy")
public ThreadLocalTargetSource threadLocalTenantStore() {
ThreadLocalTargetSource result = new ThreadLocalTargetSource();
result.setTargetBeanName("tenantStore");
return result;
}
The working example allows for the TenantStore object to be set and injected by the Spring Framework. My version of the TenantFilter class described in that article sets the properties of the TenantStore object whenever a Servlet request is made
#Autowired
private TenantStore tenantStore;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
String token = (String) request.getAttribute(ACCESS_TOKEN_VALUE);
if (token != null) {
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
if (oAuth2AccessToken.getAdditionalInformation() != null) {
String tenantName = (String) oAuth2AccessToken.getAdditionalInformation().get("tenant");
storeTenantInThread(tenantName);
}
}
}
chain.doFilter(request, response);
} catch (ResourceNotFoundException e) {
log.error(e.getMessage());
} finally {
clearTenant();
}
}
private void storeTenantInThread(String tenantName) {
tenantStore.setName(tenantName);
}
private void clearTenant() {
tenantStore.clear();
}
I then have a number of services where TenantStore is autowired and in each of these services the TenantStore contains the information that was populated in the doFilter() method. Except for one class. For some reason the properties of the TenantStore in this class are still null. The name of the class affected is MyCacheService and the architecture is as follows:
#RestController
#RequestMapping("/here")
public class MyController {
#Autowired
private MyService myService
#GetMapping
public ResponseEntity myGetMethod(#RequestParam("text") String text) {
myService.myMethod(text);
return new ResponseEntity(Http.OK);
}
}
#Service
public class MyService {
#Autowired
private TenantStore tenantStore;
#Autowired
private MyOtherService myOtherService;
public void myMethod(String text) {
System.out.println(tenantStore.getName()); //works - prints name
myOtherService.myOtherMethod(text);
}
}
#Service
public class MyOtherService {
#Autowired
private TenantStore tenantStore;
#Autowired
private Map<String, MyComponent> myComponents;
public void myOtherMethod(String text) {
System.out.println(tenantStore.getName()); //works - prints name
MyComponent useThisComponent = myComponents.get("componentName");
useThisComponent.myComponentMethod(text);
}
}
#Component("componentName")
public class MyComponent {
#Autowired
private TenantStore tenantStore;
#Autowired
private MyCacheService myCacheService;
public void myComponentMethod(String text) {
System.out.println(tenantStore.getName()); //works - prints name
entityAliasCacheService.myCacheMethod(String text);
}
}
#Service
public class MyCacheService {
#Autowired
private TenantStore tenantStore;
public void myCacheMethod(String text) {
System.out.println(tenantStore.getName()); //DOES NOT WORK - tenantStore object is not null but the name property is
}
}
From what I can guess, for some reason the TenantStore in MyCacheService is being populated in a different thread, though I've no idea why.
I noticed similar behaviour. I fixed the issue by adding a bean dependancy
#Service
#DependsOn("proxiedThreadLocalTargetSource") // asks Spring to first load proxy bean
public class MyCacheService {
where proxiedThreadLocalTargetSource bean is defined like in the OP's example -
#Primary
#Bean(name = "proxiedThreadLocalTargetSource")
public ProxyFactoryBean proxiedThreadLocalTargetSource(ThreadLocalTargetSource threadLocalTargetSource) {
ProxyFactoryBean result = new ProxyFactoryBean();
result.setTargetSource(threadLocalTargetSource);
return result;
}
So, by adding the dependancy, Spring knows that it should load MyCacheService bean after the proxiedThreadLocalTargetSource. Without this dependancy, I noticed that TenantStore got injected instead of the proxy bean.
Getting instance of TenantStore from org.springframework.context.ApplicationContext
First implement ApplicationContextAware like as below
#Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext context() {
return context;
}
}
And your MyCacheService Will be like this:
public class MyCacheService {
public void myCacheMethod(String text) {
TenantStore tenantStore = ApplicationContextUtil.context().getBean(TenantStore.class);
System.out.println(tenantStore.getName());
}
}

Configuration properties not autowired when used in request body?

I'm using a particular class (ClassA) in my controller as the request body, but within that class, my autowired ConfigurationProperties is null.
Controller:
#RestController
#RequestMapping(value = "/rest/v1/")
public class XyzController {
#Autowired
ServiceXyz serviceXyz;
#PostMapping(value = "/route")
public void route(#RequestBody ClassA classA) {
serviceXyz.methodAbc(classA);
}
}
ServiceXYZ:
#Service
public class ServiceXyz {
public boolean methodAbc(ClassA classA) {
return classA.methodA() && otherStuff();
}
}
ClassA.java:
#Component
public class ClassA {
#Autowired
ApplicationProperties applicationProperties;
public boolean methodA() {
return fieldA.equals(applicationProperties.someProperty());
}
}
ApplicationProperties.java:
#Component
#ConfigurationProperties(prefix="stuff")
public class ApplicationProperties {
// etc.
}
It's within ClassA.methodA that applicationProperties is null, even though everybody is marked with the correct annotations, and autowiring is working throughout the rest of the application.
Is it possible that this just doesn't work?
Autowiring works for objects from Spring context. In your request object of ClassA is parsed from JSON I think and is not taken from Spring context.
You'd better change your code to make ClassA as simple DTO and inject ApplicationProperties into your service class.
You can change your ClassA to this
public class ClassA {
public boolean methodA(ApplicationProperties applicationProperties) {
return fieldA.equals(applicationProperties.someProperty());
}
}
And your service to this:
#Service
public class ServiceXyz {
#Autowired
private ApplicationProperties applicationProperties;
public boolean methodAbc(ClassA classA) {
return classA.methodA(applicationProperties) && otherStuff();
}
}

Will I have any collisions coming from the #ControllerAdvice if I use it to populate a #ModelAttribute

This is the main controller for the web entrypoint
#Controller
#RequestMapping("/webapp")
public class WebAppController {
#RequestMapping(value = "/home/{authKey}",method = RequestMethod.GET)
String index(#ModelAttribute MyMeta myMeta, Model model){
System.out.println("Token: "+myMeta.getAccessToken());
return "index";
}
#RequestMapping(value = "/config/{authKey}",method = RequestMethod.GET)
String config(#ModelAttribute MyMeta myMeta, Model model){
return "configure";
}
}
Now if you look at the interceptor you can see how I am creating the #ModelAttribute, and see the implementation
#Component
#ControllerAdvice
public class SessionInterceptor implements AsyncHandlerInterceptor {
MyMeta myMeta;
...
#ModelAttribute
public MyMeta getTest() {
return this.myMeta;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
...
// parse the key from the request
...
MetaMagicKey metaMagicKey = metaMagicKeyRepo.findKeyByMagicKey(key);
// do work here query my DB and build stuff
...
// assign the queried data built into object
this.myMeta = metaMagicKey.getId().getMyMeta();
return true;
}
My question is, I do not know the true inter-workings of Springboot so I am worried if too many people execute this I might have some object swapping, or some kind of collision? There really isn't a clean way to do this and all of the research I've done is torn between using HttpServletRequest#setAttribute() and using #ModelAttribute, I like the route I chose above as it's VERY easy to implement in my methods.
Springboot 1.4.2 - Java 8
EDIT:
What I ended up trying is this, based on several pages I've read.
I created a new component:
#Component
#RequestScope
public class HWRequest implements Serializable {
private MyMeta myMeta;
public MyMeta getMyMeta() {
return myMeta;
}
public void setMyMeta(MyMeta myMeta) {
this.myMeta = myMeta;
}
}
And then My Config class
#Configuration
public class AppConfig extends WebMvcConfigurerAdapter {
UserSessionInterceptor userSessionInterceptor;
#Autowired
public AppConfig(UserSessionInterceptor userSessionInterceptor) {
this.userSessionInterceptor = userSessionInterceptor;
}
#Bean
#RequestScope
public HWRequest hwRequest() {
return new HWRequest();
}
#Bean
public UserSessionInterceptor createUserSessionInterceptor() {
return userSessionInterceptor;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(createUserSessionInterceptor()).addPathPatterns("/user/**");
}
}
And here is the interceptor I modified
#Component
#ControllerAdvice
public class SessionInterceptor implements AsyncHandlerInterceptor {
#Resource
HWRequest hwRequest;
...
#ModelAttribute
public HWRequest getTest() {
return this.hwRequest;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
...
// parse the key from the request
...
MetaMagicKey metaMagicKey = metaMagicKeyRepo.findKeyByMagicKey(key);
// do work here query my DB and build stuff
...
// assign the queried data built into object
this.hwRequest.setMyMeta(metaMagicKey.getId().getMyMeta());
return true;
}
And of course the modified controller to fit my needs
#Controller
#RequestMapping("/user")
public class WebAppUserController {
#RequestMapping(value = "/home/{authKey}",method = RequestMethod.GET)
String index(#ModelAttribute HWRequest request, Model model){
return "index";
}
#RequestMapping(value = "/config/{authKey}",method = RequestMethod.GET)
String config(#ModelAttribute HWRequest request, Model model){
return "configure";
}
}
Based on all of the documentation I've read this should work, but maybe I am missing something as the interceptor is STILL a singleton. Maybe I am missing something?
myMeta variable represents state in singleton bean. Of course it is not thread-safe and various users will get collisions. Do not ever store any of your application state in singleton beans.
If you want to store some state per request, use Spring's request scope. That means creating separate bean just for storing state annotated with #RequestScope annotation
Reaction on EDIT:
This bean registration can be deleted as it is already registered into Spring IoC container with #Component annotation:
#Bean
#RequestScope
public HWRequest hwRequest() {
return new HWRequest();
}
Another piece that is not needed in your AppConfig is autowiring UserSessionInterceptor bean and registering it as bean again. Delete that. As that bean is being autowired it obviously already is in IoC container, so no need to register it again.
Another confusing piece is workd session in naming. As you are dealing with #RequestScope instead of #SessionScope I would advise to change naming of your class to request (e.g. RequestInterceptor). Session vs Request are very different beasts.
Otherwise it looks like it can work and should be thread safe.

Spring MVC validator annotation + custom validation

I'm working on spring mvc application, where I should aplly validation based on Spring MVC validator. I first step for that I added annotation for class and setup controller and it works fine. And now I need to implement custom validator for perform complex logic, but i want to use existing annotation and just add additional checking.
My User class:
public class User
{
#NotEmpty
private String name;
#NotEmpty
private String login; // should be unique
}
My validator:
#Component
public class UserValidator implements Validator
{
#Autowired
private UserDAO userDAO;
#Override
public boolean supports(Class<?> clazz)
{
return User.class.equals(clazz) || UsersForm.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors)
{
/*
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "NotEmpty.user");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "login", "NotEmpty.user");
*/
User user = (User) target;
if (userDAO.getUserByLogin(user.getLogin()) != null) {
errors.rejectValue("login", "NonUniq.user");
}
}
}
My controller:
#Controller
public class UserController
{
#Autowired
private UserValidator validator;
#InitBinder
protected void initBinder(final WebDataBinder binder)
{
binder.setValidator(validator);
}
#RequestMapping(value = "/save")
public ModelAndView save(#Valid #ModelAttribute("user") final User user,
BindingResult result) throws Exception
{
if (result.hasErrors())
{
// handle error
} else
{
//save user
}
}
}
So, Is it possible to use custom validator and annotation together? And if yes how?
I know this is a kind of old question but, for googlers...
you should use addValidators instead of setValidator. Like following:
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.addValidators(yourCustomValidator, anotherValidatorOfYours);
}
PS: addValidators accepts multiple parameters (ellipsis)
if you checkout the source of org.springframework.validation.DataBinder you will see:
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
....
public void setValidator(Validator validator) {
assertValidators(validator);
this.validators.clear();
this.validators.add(validator);
}
public void addValidators(Validator... validators) {
assertValidators(validators);
this.validators.addAll(Arrays.asList(validators));
}
....
}
as you see setValidator clears existing (default) validator so #Valid annotation won't work as expected.
If I correctly understand your problem, as soon as you use you custom validator, default validation for #NotEmpty annotation no longer occurs. That is common when using spring : if you override a functionnality given by default, you have to call it explicitely.
You have to generate a LocalValidatorFactoryBean and inject it with your message source (if any). Then you inject that basic validator in you custom validator and delegate annotation validation to it.
Using java configuration it could look like :
#Configuration
public class ValidatorConfig {
#Autowired
private MessageSource messageSource;
#Bean
public Validator basicValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource);
return validator;
}
}
Then you modify UserValidator to use it :
#Component
public class UserValidator implements Validator
{
#Autowired
#Qualifier("basicValidator")
private Validator basicValidator;
#Autowired
private UserDAO userDAO;
// ...
#Override
public void validate(Object target, Errors errors)
{
basicValidator.validate(target, errors);
// eventually stop if any errors
// if (errors.hasErrors()) { return; }
User user = (User) target;
if (userDAO.getUserByLogin(user.getLogin()) != null) {
errors.rejectValue("login", "NonUniq.user");
}
}
}
Well for me you have to delete the
#InitBinder
protected void initBinder(final WebDataBinder binder)
{
binder.setValidator(validator);
}
Leave the
#Valid #ModelAttribute("user") final User user,
BindingResult result
And after in the function make
validator.validate(user,result)
This way you will use the validation basic with the #Valid and after you will put make the more complex validation.
Because with the initBinder you are setting the validation with your complex logic and putting a way the basic logic.
Maybe is wrong, i use always the #Valid without any validator.

How to use session scoped bean properly

There are 3 classes in my Spring MVC app: a UserDetailsInterceptor class, an MyAdvice class and a UserDetails class (session scoped).
What I want to accomplish is simple:
UserDetailsInterceptor intercepts requests and set user's id in a session scoped UserDetails bean.
Later on, when the method in AOP advice class is called, retrieve user's id from the session scoped UserDetails bean.
Problem (also marked in the code below):
UserDetails object is null in MyAdvice class.
In UserDetailsInterceptor, userDetails.setUserID(request.getRemoteUser()); does nothing.
Code:
UserDetailsInterceptor class:
public class UserDetailsInterceptor extends HandlerInterceptorAdapter {
#Autowired
private UserDetails userDetails;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//set user ID, but next line doesn't do anything for some reason (e.g. `userID` is still null)
userDetails.setUserID(request.getRemoteUser());
return true;
}
}
MyAdvice class:
public class MyAdvice implements MethodInterceptor {
#Autowired
private UserDetails userDetails; //It's null
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//Print user ID
System.out.println(userDetails.getID());
return invocation.proceed();
}
}
UserDetails class:
public class UserDetails {
private String userID;
public void setUserID(String userID) {
this.userID= userID;
}
public String getUserID() {
return this.userID;
}
}
In dispatcher-servlet.xml:
<bean id="userDetails " class="package.UserDetails " scope="session">
<aop:scoped-proxy/>
</bean>
MyPointcutAdvisor class:
public class MyPointcutAdvisor implements PointcutAdvisor {
private MyPointcut pointcut = new MyPointcut();
private MyAdvice advice = new MyAdvice();
#Override
public Pointcut getPointcut() {
return this.pointcut;
}
#Override
public Advice getAdvice() {
return this.advice;
}
#Override
public boolean isPerInstance() {
return false;
}
}
Any ideas please? Thanks in advance.
Update:
By registering MyAdvice class, userDetails object in it is no longer null. However it is not the same object as the one in UserDetailsInterceptor. So the bean is not actually "session scoped"?
Answer:
The problem lies in following code:
private MyPointcut pointcut = new MyPointcut();
private MyAdvice advice = new MyAdvice();
Neither of them are managed by spring. As a result, things are being wired and not working the way we expected.
This
UserDetails object is null in MyAdvice class.
is not possible if the MyAdvice instance is managed by Spring. You must be instantiating it yourself instead of getting it from the context.
If Spring doesn't manage the object, it can't inject anything into #Autowired targets, so your field remains null.
If Spring was managing your object, a bean, and couldn't resolve the dependency, it would throw exceptions.

Categories

Resources