I'm trying to implement a custom validator for my model classes that autowires a custom bean of mine (declared via #Component).
In this, I followed the Spring documentation on that topic. My AuthenticationFacade object is implemented according to this tutorial.
When running my tests, however, the autowired attribute in the Validator object is always null. Why is that?
Here are the relevant parts of my code:
My custom bean, AuthenticationFacadeImpl.java
#Component
public class AuthenticationFacadeImpl implements AuthenticationFacade {
boolean hasAnyRole(Collection<String> roles) {
// checks currently logged in user roles
}
}
My custom constraint, HasAnyRoleConstraint.java
#Constraint(validatedBy = HasAnyRoleConstraintValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD})
public #interface HasAnyRole {
String[] value();
String message() default "{HasAnyRole}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
My custom validator, HasAnyRoleConstraintValidator.java
#Component
public class HasAnyRoleConstraintValidator implements ConstraintValidator<HasAnyRole, Object> {
#Autowired
AuthenticationFacade authenticationFacade;
private String[] roles;
#Override
public void initialize(HasAnyRole hasAnyRole) {
this.roles = hasAnyRole.value();
}
#Override
public boolean isValid(Object target, ConstraintValidatorContext constraintValidatorContext) {
return target == null || authenticationFacade.hasAnyRole(Arrays.asList(this.roles));
}
}
The model class, Article.java
#Entity
public class Article {
// ...
#HasAnyRole({"EDITOR", "ADMIN"})
private String title;
// ...
}
The service object, ArticleServiceImpl.java
#Service
public class ArticleServiceImpl implements ArticleService {
#Autowired
private ArticleRepository articleRepository;
#Autowired
private AuthenticationFacade authenticationFacade;
#Autowired
private Validator validator;
#Override
#PreAuthorize("hasAnyRole('ADMIN', 'EDITOR')")
public boolean createArticle(Article article, Errors errors) {
articleRepository.save(article);
return true;
}
The Errors object that gets fed into the createArticle method is intended to come from the Spring controller, which gets fed a model object with the #Valid annotation.
The repository, ArticleRepository.java, uses Spring Data JPA's JpaRepository
public interface ArticleRepository extends JpaRepository<Article, Long> {
}
I solved this for now by ditching Dependency Injection for the Validator class, instead instantiating an instance of AuthenticationFacadeImpl in the constructor.
Would still be interesting, though how to combine the use of #Valid in the Controllers with custom validators + #Autowired attributes in the Model without explicitely calling the Validator in the code...
If your validator is instantiated outside the Spring context, then you can use Spring’s AOP #Configurable magic to register it in context and get autowiring work. All what you need is to annotate HasAnyRoleConstraintValidator with #Configurable and enable compile time, or load time aspects weaving.
Related
I'm using Spring Boot 2.2.4 and Hibernate 5.4.10. I cannot understand why the validator objects are created twice: the first time with Spring Boot context and the second time without it. I tried javax.persistance.validation.mode=none - nothing changed. Main problem is that objects created with Spring context are ignored.
Annotation:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {CustomForBaseEntity.class, CustomForDto.class})
public #interface Custom {
String message() default "{Your license plate has the wrong format. "
+ "Please don't use special symbols.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Base class:
abstract class CustomValidator<T> implements ConstraintValidator<LicensePlateValidation, T> {
private Pattern dependency;
#Autowired
public void setSpringDependency(#Value("${path}") final String dependency) {
this.dependency = Pattern.compile(dependency);
}
}
Both derived classes looks same, so there is only one:
#Component
public class CustomForBaseEntity extends LicensePlateValidator<Vehicle> {
#Override
public boolean isValid(Vehicle vehicle, ConstraintValidatorContext constraintValidatorContext) {
String licensePlate = vehicle.getLicensePlate();
return checkLicensePlate(licensePlate, constraintValidatorContext);
}
}
As mentioned in Spring Boot documentation, I'm created bean of LocalValidatorFactoryBean:
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
config class
#ComponentScan(basePackages = {"validator"})
class AppConfiguration { ... }
annotation class
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueLoginValidator.class)
public #interface UniqueLogin {
String message() default "{com.dolszewski.blog.UniqueLogin.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
validator class
#Component
class UniqueLoginValidator implements ConstraintValidator<UniqueLogin, String> {
private UserRepository userRepository;
public UniqueLoginValidator(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void initialize(UniqueLogin constraint) {
}
public boolean isValid(String login, ConstraintValidatorContext context) {
return login != null && !userRepository.findByLogin(login).isPresent();
}
}
I have a class with property #UniqueLogin String login, I also use other annotations like #Size and #Max, the last 2 works, but my custom annotation does not work.
Can you please help to understand why spring do not call custom validator?
It worked for me to create inside src/main/resources/META-INF/services a file named javax.validation.ConstraintValidator with a list new line separated of all qualified name of custom constraint validators you created.
This way, Spring will automatically register the custom validator.
This file will be automatically checked from Spring and included into built artifact.
Be careful of annotation configuration after applying this solution. You should annotate with #Constraint(validatedBy = { }) to prevent double validator initialization.
I'm trying to make a custom validation (checking if an email is already present in the database). For single class my annotation is working fine but I need to make this validation work for two objects implementing common interface. I have User interface and Visitor and Exhibitor classes which are implementing it.
Here is my annotation:
#Documented
#Constraint(validatedBy = UniqueEmailValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface UniqueEmail {
String message() default "Email is already existing!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Here is Validator class
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
#Autowired
private ApplicationContext applicationContext;
private UserService userService;
#Override
public void initialize(UniqueEmail uniqueEmail) {
}
#Override
public boolean isValid(String email, ConstraintValidatorContext constraintValidatorContext) {
return !userService.isEmailPresent(email);
}
}
UserService is a common interface of VisitorService and ExhibitorService
public interface UserService {
boolean isEmailPresent(String email);
}
And it's implementation...
#Service
public class VisitorService implements UserService {
#Autowired
VisitorDao visitorDao;
#Override
public boolean isEmailPresent(String email) {
try {
return !visitorDao.findAllByEmail(email).isEmpty();
} catch (NullPointerException e) {
return false;
}
}
}
Currently I'm getting NullPointerException
java.lang.NullPointerException: null
at pl.com.sremski.testapp.validators.UniqueEmailValidator.isValid(UniqueEmailValidator.java:24) ~[classes/:na]
at pl.com.sremski.testapp.validators.UniqueEmailValidator.isValid(UniqueEmailValidator.java:10)
Any ideas what's the reason? I was trying to debug and UserService is null... but I'm trying to add a new visitor so it should use VisitorService. Please help.
I managed to solve my problem by specifing exact implementation in annotation's parameter
#UniqueEmail(service = VisitorService.class)
and then creating it's instance in
#Override
public void initialize(UniqueEmail uniqueEmail) {
}
However I had to separate all Spring-based validation from Hibernate's entity to make it work.
If you don't want to have two different annotations with different #Qualifier() marked beans, you can consider to choose that bean in runtime using application context. But injecting whole Spring context to your business logic is considered as a very bad practice:
1) Mixing infrastructure (your WHOLE infrastructure) with business processes makes your code hard to understand and decouple.
2) It is hard to unit test this, need to mock the context object instead of your services.
But after using Google Guice DI I found myself in using Provider<Service> pattern, because injecting spring into spring is OK. So you can create a class like:
#Service
class UserServiceProvider<T extends UserService> implements Provider<T> {
#Autowired private ApplicationContext context;
public UserService get(Class<T> exactServiceType) {
return (UserService) context.getBean(exactServiceType);
}
}
Maybe there is a much better "Spring way" to do this, but this code is easy to understand and maintain. Works lile a Scope.Prototype bean, but a bit more flexible.
Google Guice Provider tutorial
EDIT:
Spring has a similar interface FactoryBean<T> and it has an enhanced handling, because DI will inject not the factory, but what factory provides.
But the one problem is you can't do it with some condition.
Simple example
Good morning,
I have defined a custom annotation that I want to use to mark some classes as Auditable
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Auditable {
}
And have declared an aspect to handle all classes marked with this annotation. I am using AspectJ with Eclipse
#Aspect
public class AuditableClassAspect extends AbstractAuditableAspect {
#Autowired
private ApplicationContext applicationContext;
// Makes the bean auditable
#DeclareMixin(value="#Auditable *")
public Auditable auditableBeanFactory(Object instance) {
// Use a bean delegator as a mixin object
Auditable mixin = applicationContext.getBean(PersistentDelegate.class);
mixin.setObject(instance);
return mixin;
}
#Pointcut("get(* (#Auditable *).*) && this(object)")
public void executionGet(Object object) {}
#Pointcut("set(* (#Auditable *).*) && this(object)")
public void executionSet(Object object) {}
}
Then I have marked a class:
#Auditable
public class Report implements Serializable {
private static final long serialVersionUID = 4746372287860486647L;
private String companyName;
private Date reportingDate;
I am using AspectJ with Spring, and have defined the Aspect in applicationContext.xml
<bean class="com.xxx.aop.AuditableClassAspect" factory-method="aspectOf" />
My issue is that there is no matching happening. The Aspect doesn't "detect" the Report class as an Auditable class.
In eclipse it doesn't show any matching. At run time I have an exception when I am casting my report in an Auditable interface.
Do you have any idea what there is no match?
For information, if in my code I write
#DeclareMixin(value="com.xxx.Report")
Then I have a matching and the Aspect works.
Is there something missing with the annotation?
Thanks
Gilles
Because I have some generics which need to be passed to the newly produced object, I am creating a producer. But while producer works, the EntityManager is not injected because producer creates an instance with operator new instead of using CDI.
How can I produce an object with CDI support?
The code:
Qualifier:
#Qualifier
#Retention(RUNTIME)
#Target(
{ FIELD, TYPE, METHOD })
public #interface Multiselector
{
Class<? extends Dbo> clazz();
}
Producer:
#SessionScoped
public class MultiselectorProducer implements Serializable
{
#Produces
#Multiselector(clazz = SpecialDbo.class)
public MultiselectorService<SpecialDbo> produce()
{
return new MultiselectorService<SpecialDbo>(SpecialDbo.class);
}
}
Service class:
#Stateful
#LocalBean
public class MultiselectorService<T extends Dbo> implements Serializable
{
#Inject
private EntityManager em;
private List<T> itemList;
public MultiselectorService()
{
}
public MultiselectorService(Class<? extends Dbo> clazz)
{
itemList = em.createQuery("some Sql String", clazz);
}
....
}
NOTE: The EntityManager is a custom crud service which is otherwise injected correctly
Any improvement suggestions over the code are welcome. Thanks!
You have mixed a lot of unrelated things:
Your service MultiselectorService is an EJB, and you cannot produce it with a producer. EJB is registered once application is created and then depending on the scope it creates instances.
You have a method public void MultiselectorService(Class<? extends Dbo> clazz) with the name similar to constructor, it against convention.
Assume that you have fixed that method to be a constructor, but then line 'itemList = em.createQuery("some Sql String", clazz);' will fail with NPE. Because em will be initialized only after bean creation. there are two ways to do it:
Inject entity manager into constructor (this is against EJB spec, if you will still use EJB)
Execute initialization operation in method with annotation '#PostConstruct'
Do you have a producer for EntityManager ? e.g. you cannot just inject entity manager, you need to provide as a resource for EJB, with annotation #PersistenceContext
I understand what you try to achieve. The main problem is that manually created beans are not managed by container, this means that interceptors and decorators won't apply (e.g. PostConstruct and Transactional annotation will not work). Check here. So far the best way to achieve this is:
public interface SpecialDboMultiselectorService extends MultiselectorService {
}
#Stateless
public class SpecialDboMultiselectorServiceImpl extends MultiselectorServiceImpl<SpecialDbo> implements SpecialDboMultiselectorService
{
public SpecialDboMultiselectorServiceImpl() {
super(SpecialDbo.class);
}
}
public class MultiselectorServiceImpl<T extends Dbo> implements MultiselectorService {
#Inject
private EntityManager em;
private Class<? extends Dbo> clazz;
private List<T> itemList;
public MultiselectorService(Class<? extends Dbo> clazz) {
this.clazz = clazz
}
#PostConstruct
public void init() {
itemList = em.createQuery("some Sql String", clazz);
}
}
and inject SpecialDboMultiselectorService.