This is my controller. It accepts a multipart/form-data request with two fields, form and file. The form field is a
MyObject, the file field is a MultipartFile. Both variables are annotated with #Valid, and accordingly, I would expect Spring to invoke the Validator class of each respective field. However, this only happens with MyObject, and not with MultipartFile.
#RequestMapping("/api")
#RestController
public class Controller {
private MyObjectRepository repo;
private MyObjectValidator myObjectValidator;
private FileValidator fileValidator;
#Autowired
public myObjectController(MyObjectRepository repo, MyObjectValidator myObjectValidator,
FileValidator fileValidator) {
this.repo = repo;
this.myObjectValidator = myObjectValidator;
this.fileValidator = fileValidator;
}
#InitBinder("form")
public void initMyObjectBinder(WebDataBinder binder) {
binder.setValidator(this.myObjectValidator);
}
#InitBinder("file")
public void initFileBinder(WebDataBinder binder) {
binder.setValidator(this.fileValidator);
}
#PostMapping("myObject")
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public MyObject createMyObject(#RequestPart("form") #Valid MyObject myObject,
#RequestPart("file") #Valid MultipartFile... file) {
return repo.save(myObject);
}
}
My MyObjectValidator is triggered, but my FileValidator is not triggered. Both classes implement the Spring Validator interface. MyObjectValidator.supports(Class<?> aClass) is called, whereas FileValidator.supports(Class<?> aClass) is never called. Apart from that, my Controller is functioning perfectly, and diligently saves objects to my repo.
What could be the issue here? I've read similar questions, and common mistakes are to not use an appropriate argument inside the #InitBinder annotation, or to set the #InitBinder methods to private instead of public, but neither of this applies to my case.
This ugly workaround does what it's supposed to, but it is un-Spring-like. I call my FileValidator manually inside the Controller.createMyObject method, instead of letting Spring call it automatically through the #Valid annotation.
#PostMapping("myObject")
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public MyObject createMyObject(#RequestPart("form") #Valid MyObject myObject,
#RequestPart("file") #Valid MultipartFile... file) {
if (fileValidator.supports(file.getClass())) {
Errors errors = new BeanPropertyBindingResult(file, "Uploaded file.");
fileValidator.validate(file,errors);
if (errors.hasErrors()) {
throw new BadRequestException();
}
}
return repo.save(myObject);
}
EDIT: I have included my Validator classes on request.
import org.springframework.validation.Validator;
public abstract class AbstractValidator implements Validator {
// One shared method here.
}
public class FileValidator extends AbstractValidator {
public boolean supports(Class<?> aClass) { // This method is never triggered.
boolean isSingleFile = MultipartFile.class.isAssignableFrom(aClass); // This line has a breakpoint, it is never triggered in the debugger.
boolean isFileArray = aClass.equals(MultipartFile[].class);
return (isSingleFile || isFileArray);
}
public void validate(Object o, Errors e) {
//Several validation methods go here.
}
public class MyObjectValidator extends AbstractValidator {
public boolean supports(Class<?> aClass) { // This method is triggered.
return (MyObject.class.equals(aClass)); // This line has a breakpoint, and it is always triggered in the debugger.
}
public void validate(Object o, Errors e) {
// Several validation methods go here.
}
EDIT: I made some changes to my code like NiVeR suggested, removing the varargs parameter and changing my FileValidator.supports(Class<?> aClass) accordingly, but the behavior is still the same.
In Controller.java:
#PostMapping("myObject")
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public MyObject createMyObject(#RequestPart("form") #Valid MyObject myObject, #RequestPart("file") #Valid MultipartFile file) {
return repo.save(myObject);
}
In FileValidator.java:
public boolean supports(Class<?> aClass) {
return MultipartFile.class.isAssignableFrom(aClass);
}
I believe that the problem is related to the variadic Multipart... parameter. In the supports method of the validator you are checking for array of Multipart but I suspect that's not the correct way. Just as a trial, I would make the Multipart a single object parameter (and change the validator accordingly) to test if it works in this way.
#Magnus I think you have to add annotation in all the Validator Class
eg.
#Component
public class MyObjectValidator extends AbstractValidator {
public boolean supports(Class<?> aClass) { //This method is triggered.
return (MyObject.class.equals(aClass));
}
public void validate(Object o, Errors e) {
//Several validation methods goes here.
}
}
Related
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.
I am not sure of using inheritance / interface implementation in particular situation.
In my simple Spring MVC application I have #Entity class TennisPlayer, which is inherited from abstract class Player (TennisPlayer adds some attributes).
Also I have class TennisPlayerForm, which is inherited from abstract class PlayerForm (TennisPlayerForm adds some attributes again).
User fills the form about tennis player in .jsp page and TennisPlayerForm object is used to represent filled values and then on the basis of this object is created TennisPlayer object and saved into database.
Creation of TennisPlayer object is responsibility of class TennisPlayerDbService. This class is implementation of interface PlayerService.
I have following #Controller, which handles requests:
#Controller
public class NewPlayerController {
#Resource(name="tennisPlayerService")
private PlayerService playerService;
//omitted RequestMethod.GET handler method
#RequestMapping(value = "/newplayer", method = RequestMethod.POST)
public String newplayer(Locale locale, #ModelAttribute("tennisPlayerForm") #Valid TennisPlayerForm tennisPlayerForm,
BindingResult result, RedirectAttributes redirectAttributes) {
playerService.createPlayer(tennisPlayerForm);
return "redirect:/allplayers";
}
}
Part of my source code looks like this:
public interface PlayerService {
public void createPlayer(PlayerForm playerForm);
}
#Service(value="tennisPlayerService")
public class TennisPlayerDbService implements PlayerService {
private TennisPlayerDAO dao;
#Autowired
public void setDao(TennisPlayerDAO dao) {
this.dao = dao;
}
#Override
public void createPlayer(PlayerForm playerForm) {
TennisPlayerForm tennisPlayerForm = null;
if (playerForm instanceof TennisPlayerForm) {
tennisPlayerForm = (TennisPlayerForm) playerForm;
}
else {
throw new IllegalArgumentException("Must be of type TennisPlayerForm.");
}
TennisPlayer player = new TennisPlayer();
player.setName(tennisPlayerForm.getName());
player.setSurname(tennisPlayerForm.getSurname());
player.setAge(tennisPlayerForm.getAge());
player.setRacket(tennisPlayerForm.getRacket());
player.setRanking(tennisPlayerForm.getRanking());
player.setSponsor(tennisPlayerForm.getSponsor());
player.setCoach(tennisPlayerForm.getCoach());
player.setClub(tennisPlayerForm.getClub());
dao.saveAndFlush(player);
}
}
Is it justified to use inheritance and interface implementations like this in this situation, when concrete implementation of PlayerService (TennisPlayerDbService) expects instance of particular class, although these potential classes have common parent?
Finally I solved my problem according to your comments and answers.
I deleted PlayerForm abstract class, TennisPlayerForm and mixed javax.validation and javax.persistence annotations in #Entity classes Player and Tennis Player.
Previously mentioned code now looks like this:
#Controller
public class NewPlayerController {
#Resource(name="tennisPlayerService")
private PlayerService<TennisPlayer> playerService;
//omitted RequestMethod.GET handler method
#RequestMapping(value = "/newplayer", method = RequestMethod.POST)
public String newplayer(Locale locale, #ModelAttribute("tennisPlayer") #Valid TennisPlayer tennisPlayer,
BindingResult result, RedirectAttributes redirectAttributes) {
if(result.hasErrors()) {
return "newplayer";
}
playerService.createPlayer(tennisPlayer);
MessageUtil.flash(locale, redirectAttributes, "success", "signup.success");
return "redirect:/allplayers";
}
}
public interface PlayerService<T extends Player> {
public void createPlayer(T player);
public List<T> getAllPlayers();
}
#Service(value="tennisPlayerService")
public class TennisPlayerDbService implements PlayerService<TennisPlayer> {
private TennisPlayerDAO dao;
#Autowired
public void setDao(TennisPlayerDAO dao) {
this.dao = dao;
}
#Override
public void createPlayer(TennisPlayer player) {
dao.saveAndFlush(player);
}
#Override
public List<TennisPlayer> getAllPlayers() {
return dao.findAll();
}
}
Normally your service does not need to know you are working with a form. Your form is purely created to be the model in the model-view-controller architecture of your webpage. (your jsp being the view and your controller being the c-part)
Are you also planning on using other types of players than a TennisPlayer? If not it all seems like premature optimisation and you should keep it as simple as possible.
I have Spring rest controller that provides operations on Project entity. All methods use same entity accessing code. I don't want to copy&paste #PathVariable parameters in all methods, so I've made something like this.
#RestController
#RequestMapping("/projects/{userName}/{projectName}")
public class ProjectController {
#Autowired
ProjectService projectService;
#Autowired
protected HttpServletRequest context;
protected Project project() {
// get {userName} and {projectName} path variables from request string
String[] split = context.getPathInfo().split("/");
return projectService.getProject(split[2], split[3]);
}
#RequestMapping(method = GET)
public Project get() {
return project();
}
#RequestMapping(method = GET, value = "/doSomething")
public void doSomething() {
Project project = project();
// do something with project
}
// more #RequestMapping methods using project()
}
Is it possible to autowire path variables into controller by annotation so I don't have to split request path and get parts of it from request string for project() method?
In order to do custom binding from request you've got to implement your own HandlerMethodArgumentResolver (it's a trivial example without checking if path variables actually exist and it's also global, so every time you will try to bind to Project class this argument resolver will be used):
class ProjectArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterType().equals(Project.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Map<String, String> uriTemplateVars = (Map<String, String>) webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return getProject(uriTemplateVars.get("userName"), uriTemplateVars.get("projectName"));
}
private Project getProject(String userName, String projectName) {
// replace with your custom Project loading logic
Project project = new Project(userName, projectName);
return project;
}
}
and register it using WebMvcConfigurerAdapter:
#Component
public class CustomWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new ProjectArgumentResolver());
}
}
In your controller you have to put Project as a method argument, but do not annotate it with #PathVariable:
#Controller
#RequestMapping("/projects/{userName}/{projectName}")
public class HomeController {
#RequestMapping(method = RequestMethod.GET)
public void index(Project project){
// do something
}
}
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.
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);