Spring MVC - lookup validators automatically - java

Suppose I have a sample entity class like this:
public class Address {
...
}
and a corresponding validator:
#Component
public AddressValidator implements Validator {
#Override
public boolean supports(Class<?> entityClass) {
return entityClass.equals(Address.class);
}
#Override
public void validate(Object obj, Errors errors) {
...
}
}
When I use a controller like the following, everything works:
#RestController
#RequestMapping("/addresses")
public class AddressController {
#Autowired
private AddressValidator validator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
#RequestMapping(method=POST)
public Long addNewAddress(#Valid #RequestBody Address address) {
...
}
}
However, if I omit the validator registering part (i.e. the following), validation is not performed.
#Autowired
private AddressValidator validator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
Having to register validators manually seems pointless. Can I instruct Spring to look up validators automatically (similar to how controllers are looked up)?
It's a Spring Boot based application.

You can use my example from gist or below. The idea is to have a main CompositeValidator that will be a holder of all your Validator or SmartValidator instances.
It supports hints and can be also integrate with Hibernate Annotation Validator (LocalValidatorFactoryBean). And also it's possible to have more that one validator per specific Model.
CompositeValidator.java
#Component
public class CompositeValidator implements SmartValidator {
#Autowired
private List<Validator> validators = Collections.emptyList();
#PostConstruct
public void init() {
Collections.sort(validators, AnnotationAwareOrderComparator.INSTANCE);
}
#Override
public boolean supports(Class<?> clazz) {
for (Validator validator : validators) {
if (validator.supports(clazz)) {
return true;
}
}
return false;
}
#Override
public void validate(Object target, Errors errors) {
validate(target, errors, javax.validation.groups.Default.class);
}
#Override
public void validate(Object target, Errors errors, Object... validationHints) {
Class<?> clazz = target.getClass();
for (Validator validator : validators) {
if (validator.supports(clazz)) {
if (validator instanceof SmartValidator) {
((SmartValidator) validator).validate(target, errors, validationHints);
} else {
validator.validate(target, errors);
}
}
}
}
}
SomeController.java
#Controller
#RequestMapping("/my/resources")
public class SomeController {
#RequestMapping(method = RequestMethod.POST)
public Object save(
#Validated(javax.validation.groups.Default.class) // this interface descriptor (class) is used by default
#RequestBody MyResource myResource
) { return null; }
}
Java Config
#Configuration
public class WebConfig {
/** used for Annotation based validation, it can be created by spring automaticaly and you don't do it manualy */
// #Bean
// public Validator jsr303Validator() {
// LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
// // validator.setValidationMessageSource(...);
// return validator;
// }
#Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
return new WebMvcConfigurerAdapter() {
#Autowired
private CompositeValidator validator;
#Override
public Validator getValidator() {
return validator;
}
}
}
Or XML Config
<!-- used for Annotation based validation, it can be created by spring automaticaly and you don't do it manualy -->
<!--<bean id="jsr303Validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">-->
<!-- <property name="validationMessageSource" ref="messageSource"/>-->
<!--</bean>-->
<mvc:annotation-driven validator="compositeValidator">
//...
</mvc:annotation-driven>

You can configure global Validator.
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#validation-mvc
If you are using Java based spring configuration with WebMvcConfigurationSupport, you can override getValidator()
/**
* Override this method to provide a custom {#link Validator}.
*/
protected Validator getValidator() {
return null;
}
You may call setValidator(Validator) on the global WebBindingInitializer. This allows you to configure a Validator instance across all #Controller classes. This can be achieved easily by using the Spring MVC namespace:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven validator="globalValidator"/>
</beans>

I have not found a build in Spring solution for this, but here is what I do.
I declare my validator beans in the spring java configuration like so:
#Bean
public Validator studentValidator(){
return new StudentValidator();
}
#Bean
public Validator carValidator(){
return new CarValidator();
}
Then I have all controllers extends a base controller like so:
public abstract class BaseController <T> {
public BaseController(List<Validator> validators) {
super();
this.validators = validators;
}
//Register all validators
#InitBinder
protected void initBinder(WebDataBinder binder) {
validators.stream().forEach(v->binder.addValidators(v));
}
}
The concrete class of this controller gets the List injected via dependency injection, like so:
#Controller
public class StudentController extends BaseController<Student>{
#Inject
public StudentController(List<Validator> validators) {
super(validators);
}
}
The base Controller uses the #InitBinder call-back method to register all Validators.
I am surprised that spring doesn't automatically register all objects in class path that implement the Validator interface.

Related

Creating a pointcut for all the classes in package?

I have to create a configurable pointcut. Can anyone help for achieving the dynamic pointcut.
DynamicPointcut.class
public class DynamicPointcut extends DynamicMethodMatcherPointcut {
#Value("${custom.logging.basepackage}")
String basePackage;
#Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
#Override
public boolean matches(Class<?> clazz) {
List<Class<?>> classList = ClassFinder.find(basePackage);
return classList.stream().anyMatch(x -> x.equals(clazz));
}
};
}
#Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
if(args.length>0){
return true;
}
return false;
}
}
ConfigurableAdvisorConfig.class
#Configuration
public class ConfigurableAdvisorConfig {
#Autowired
private ProxyFactoryBean proxyFactoryBean;
#Autowired
DefaultPointcutAdvisor defaultPointcutAdvisor;
DynamicPointcut pointcut = new DynamicPointcut();
NonProductionLoggingAspect advice = new NonProductionLoggingAspect();
String[] advisor;
List<Advisor> advisorList = new ArrayList<Advisor>();
#Bean
public String[] defaultPointcutAdvisor(){
defaultPointcutAdvisor.setAdvice(new NonProductionLoggingAspect());
defaultPointcutAdvisor.setPointcut(new DynamicPointcut());
advisor = new String[]{"defaultPointcutAdvisor"};
return advisor;
}
#Bean
public ProxyFactoryBean proxyFactoryBean(){
proxyFactoryBean.setInterceptorNames(advisor);
return proxyFactoryBean;
}
}
You can wire up your aspect using the static aspectOf factory method (you can't see that method, it is added by the aspectj compiler)
<bean class="com.YourAspect" factory-method="aspectOf">
<property name="basePackage"
value="${custom.logging.basepackage}" />
Reference:
autowiring in aspect
Use AspectJ support in Spring for your scenario.
#Aspect
#Component
public class DaoAspect{
#Pointcut("within(com.xyz..dao.*)")
public void allDao(){};
#Before("allDao")
public void runAdvise(){
//some code
}
}
Define DaoAspect
Enable aspectJ support in spring by using #EnableAspectJAutoProxy
Ensure your aspect gets registered as bean via component scanning
There you go, this way you can advise all the classes in a
particular package

Spring REST validation on custom annotation

I'm trying to add some extra validation logic on my REST beans using annotations. This is just an example, but the point is that the annotation is to be used on multiple REST resource objects / DTO's.
I was hoping for a solution like this:
public class Entity {
#NotNull // JSR-303
private String name;
#Phone // Custom phonenumber that has to exist in a database
private String phoneNumber;
}
#Component
public class PhoneNumberValidator implements Validator { // Spring Validator
#Autowired
private PhoneRepository repository;
public boolean supports(Class<?> clazz) {
return true;
}
public void validate(Object target, Errors errors) {
Phone annotation = // find fields with annotations by iterating over target.getClass().getFields().getAnnotation
Object fieldValue = // how do i do this? I can easily get the annotation, but now I wish to do a call to repository checking if the field value exists.
}
}
Did you try JSR 303 bean validator implementations like hibernate validator
e.g. is available here http://www.codejava.net/frameworks/spring/spring-mvc-form-validation-example-with-bean-validation-api
Maven Module A:
public interface RestValidator<A extends Annotation, T> extends ConstraintValidator<A, T>
public interface PhoneValidator extends RestValidator<PhoneNumber, String>
#Target(FIELD)
#Retention(RUNTIME)
#Constraint(validatedBy = PhoneValidator.class) // This usually doesnt work since its a interface
public #interface PhoneNumber {
// JSR-303 required fields (payload, message, group)
}
public class Person {
#PhoneNumber
private String phoneNumber;
}
Maven Module B:
#Bean
LocalValidatorFactoryBean configurationPropertiesValidator(ApplicationContext context, AutowireCapableBeanFactory factory) {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setConstraintValidatorFactory(factory(context, factory));
return factoryBean;
}
private ConstraintValidatorFactory factory(final ApplicationContext context, final AutowireCapableBeanFactory factory) {
return new ConstraintValidatorFactory() {
#Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
if (RestValidator.class.isAssignableFrom(key)) {
return context.getBean(key);
} else {
return factory.createBean(key);
}
}
#Override
public void releaseInstance(ConstraintValidator<?, ?> instance) {
if (!(instance instanceof RestValidator<?, ?>)) {
factory.destroyBean(instance);
}
}
};
}
#Bean
WebMvcConfigurerAdapter webMvcConfigurerAdapter(final LocalValidatorFactoryBean validatorFactoryBean) {
return new WebMvcConfigurerAdapter() { // Adds the validator to MVC
#Override
public Validator getValidator() {
return validatorFactoryBean;
}
};
}
Then I have a #Component implementation of PhoneValidator that has a Scope = Prototype.
I hate this solution, and I think Spring SHOULD look up on Interface implementations by default, but I'm sure some people that are a lot smarter than me made the decision not to.

Spring4 How to use Service in Validator

public class BoardValidator implements Validator {
#Autowired
BoardService boardService;
#Override
public boolean supports(Class<?> clazz) {
return BoardVO.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
boardService.getAllList();
//TODO add
}
}
create Validator like this
Can use boardService in controller package
but in validator boardService is Null
how to use Service #Autowired in Validator class?

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.

Spring won't inject a JPARepository bean

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);

Categories

Resources