Is there a better way to configure interceptors using JavaConfig? - java

I am porting XML Spring configuration to JavaConfig.
My bean definition with interceptor looks like:
#Autowired
private MyService myServiceImpl;
#Bean
MyService myService() {
final ProxyFactoryBean proxy = new ProxyFactoryBean();
final Class<?>[] proxyInterfaces = { MyService.class };
proxy.setProxyInterfaces(proxyInterfaces);
proxy.setTarget(this.myServiceImpl);
final String[] interceptorNames = { "myInterceptor" };
proxy.setInterceptorNames(interceptorNames);
return (MyService) proxy.getObject();
}
, where "myInterceptor" name is not validated at compile-time.
Is there a better way to configure interceptors using JavaConfig?

A better way to configure interceptors:
#Configuration
public class MyServiceConfig {
#Autowired
private BeanFactory beanFactory;
#Autowired
private IMyService myService;
#Autowired
private MyInterceptor myInterceptor;
#Bean
public IMyService myServiceIntercepted() {
final Class<?>[] proxyInterfaces = { IMyService.class };
final Advice[] advices = { this.myInterceptor };
return createProxy(proxyInterfaces, this.myService, this.beanFactory,
advices);
}
<T> T createProxy(final Class<?>[] proxyInterfaces, final T target,
final BeanFactory beanFactory) {
final ProxyFactoryBean proxy =
createProxyReturnFactoryBean(proxyInterfaces, target, beanFactory);
return (T) proxy.getObject();
}
<T> ProxyFactoryBean createProxyReturnFactoryBean(
final Class<?>[] proxyInterfaces, final T target, final BeanFactory beanFactory) {
final ProxyFactoryBean proxy = new ProxyFactoryBean();
proxy.setBeanFactory(beanFactory);
if (proxyInterfaces != null) {
proxy.setProxyInterfaces(proxyInterfaces);
}
proxy.setTarget(target);
return proxy;
}
<T> T createProxy(final Class<?>[] proxyInterfaces, final T target,
final BeanFactory beanFactory, final Advice[] advices) {
final ProxyFactoryBean proxy =
createProxyReturnFactoryBean(proxyInterfaces, target, beanFactory);
for (final Advice advice : advices) {
proxy.addAdvice(advice);
}
return (T) proxy.getObject();
}
}

Related

Spring, injecting a list of beans with only specific beans included

In Spring, when I inject a list of beans, I only want to inject specific implementations of the interface, when used from different places. Is this possible to do? What would be the cleanest way to configure this? For example, I have the following validators:
public interface Validator {
Optional<Error> validate(MultipartFile file);
}
public class Validator1 implements Validator {
public Optional<Error> validate(MultipartFile file) {
// do stuff
}
}
public class Validator2 implements Validator {
public Optional<Error> validate(MultipartFile file) {
// do stuff
}
}
public class Validator3 implements Validator {
public Optional<Error> validate(MultipartFile file) {
// do stuff
}
}
And then I have a validation service which looks similar to this:
public class ValidationService {
#Autowired
private List<Validator> validators;
public List<Error> validate(MultipartFile file) {
List<Error> errors = new ArrayList<>();
validators.forEach(v -> {
Optional<Error> error = v.validate(file);
if (error.isPresent()) {
errors.add(error.get());
}
});
return errors;
}
}
And then I have some services, which use the ValidationService, e.g:
public class Service1 {
#Autowired
private ValidationService validationService;
public void doStuff(MultipartFile file) {
...
validationService.validate(file);
...
}
}
public class Service2 {
#Autowired
private ValidationService validationService;
public void doStuff(MultipartFile file) {
...
validationService.validate(file);
...
}
}
When Service1 calls validate, I only want Validator1 and Validator2 to have been injected into the ValidatorService.
When Service2 calls validate, I only want Validator2 and Validator3 to have been injected into the ValidatorService.
Hope I have explained this clearly enough. Thanks in advance for any help offered.
Create the bean like this with #Qualifier annotations --
#Qualifier("validator1")
public class Validator1 implements Validator {
public Optional<Error> validate(MultipartFile file) {
// do stuff
}
}
#Qualifier("validator2")
public class Validator2 implements Validator {
public Optional<Error> validate(MultipartFile file) {
// do stuff
}
}
#Qualifier("validator3")
public class Validator3 implements Validator {
public Optional<Error> validate(MultipartFile file) {
// do stuff
}
}
and inject it like this ---
#Autowired("validator1")
private ValidationService validationService;
Update
You can also create a bean collection for all the validators like this -
#Bean("validators")
public List<Validator> validatorList(Validator1 validator1, Validator2 validator2, Validator3 validator3) {
return Arrays.asList(validator1, validator2, validator3);
}
and the inject the list bean as --
#Autowired("validators")
private List<Validator> validators;
Check this page fore a detailed example - https://www.baeldung.com/spring-injecting-collections
This is likely not the best way to do this.
Here is how I would do it,
based on my current understanding of Spring.
Summary:
Create a bean method for each collection of implementations.
In your case, create a bean method for a List<Validator> that contains Validator1 and Validator2 and create a second List<Validator> that contains Validator2 and Validator3.
Inject the desired List using #Qualifier.
The code should be something like this:
#Configuration
public class ValidatorLists
{
private void getAndAddBean(
final ApplicationContext applicationContext,
final List<Validator> list,
final String beanName)
{
final Validator bean;
bean = applicationContext.getBean(beanName);
if (bean != null)
{
list.add(bean);
}
}
#Bean("ValidatorList1")
public List<Validator> validatorList1(final ApplicationContext applicationContext)
{
Validator bean;
final List<Validator> returnValue = new LinkedList<>();
getAndAddBean(applicationContext, returnValue, "ValidatorImpl1");
getAndAddBean(applicationContext, returnValue, "ValidatorImpl2");
return returnValue;
}
#Bean("ValidatorList2")
public List<Validator> validatorList2(final ApplicationContext applicationContext)
{
Validator bean;
final List<Validator> returnValue = new LinkedList<>();
getAndAddBean(applicationContext, returnValue, "ValidatorImpl2");
getAndAddBean(applicationContext, returnValue, "ValidatorImpl3");
return returnValue;
}
}
Then reference the list by qualifier.
#Autowired
#Qualifier("ValidatorList1")
private List<Validator> validators;

Create Bean for interface

I'm trying to create system like #Repository.
I have lots of interfaces like:
#Client(uri = "http://example.com", username = "httpBasicUsername", password = "httpBasicPassword")
public interface RequestRepository {
#Request(method = Method.POST, uri = "/mono")
Mono<Response> ex1(Object body);
#Request(method = Method.POST, uri = "/flux")
Flux<Response> ex2(Object body);
}
Right now, I'm creating bean with using this function:
#Bean
public RequestRepository requestRepository(WebClient.Builder builder) {
return (RequestRepository) Proxy.newProxyInstance(
RequestRepository.class.getClassLoader(),
new Class[]{RequestRepository.class},
new MyDynamicInvocationHandler(builder)
);
}
But I have lots of these interfaces. For every new interface I need to create another bean function. But I don't want to do that.
Is there a way to say spring (spring boot) if there is #Client annotation then create bean like this etc?
I've solved with creating custom interface scanner.
For more details: https://stackoverflow.com/a/43651431/6841566
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Import({InterfaceScanner.class})
public #interface InterfaceScan {
String[] value() default {};
}
public class InterfaceScanner implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(InterfaceScan.class.getCanonicalName());
if (annotationAttributes != null) {
String[] basePackages = (String[]) annotationAttributes.get("value");
if (basePackages.length == 0)
basePackages = new String[]{((StandardAnnotationMetadata) metadata)
.getIntrospectedClass().getPackage().getName()};
ClassPathScanningCandidateComponentProvider provider =
new ClassPathScanningCandidateComponentProvider(false, environment) {
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata meta = beanDefinition.getMetadata();
return meta.isIndependent() && meta.isInterface();
}
};
provider.addIncludeFilter(new AnnotationTypeFilter(Client.class));
for (String basePackage : basePackages)
for (BeanDefinition beanDefinition : provider.findCandidateComponents(basePackage))
registry.registerBeanDefinition(
generateName(beanDefinition.getBeanClassName()),
getProxyBeanDefinition(beanDefinition.getBeanClassName()));
}
}
}
#InterfaceScan
#SpringBootApplication
public class ExampleApplication {
...
}

Spring Bean - Constructor with arguments

I see an error in the constructor which says
"String queueName" cannot be autowired.
I have AmazonSQSAsync component defined in another class but not
queueName. Why is the constructor trying to autowire the parameters
and how can I resolve this?
#Configuration
public class SqsQueueHealthIndicator extends AbstractHealthIndicator {
private final AmazonSQSAsync amazonSQSAsync;
private final String queueName;
public SqsQueueHealthIndicator(AmazonSQSAsync amazonSQSAsync, String queueName) {
this.amazonSQSAsync = amazonSQSAsync;
this.queueName = queueName;
}
#Override
protected void doHealthCheck(Health.Builder builder) {
try {
amazonSQSAsync.getQueueUrl(queueName);
builder.up();
} catch (QueueDoesNotExistException e) {
builder.down(e);
}
}
#Bean
SqsQueueHealthIndicator queueHealthIndicator(#Autowired AmazonSQSAsync amazonSQSAsync, #Value("${url}") String queueName) {
return new SqsQueueHealthIndicator(amazonSQSAsync, queueName);
}
#Bean
SqsQueueHealthIndicator deadLetterQueueHealthIndicator(#Autowired AmazonSQSAsync amazonSQSAsync, #Value("${dlqurl}") String deadLetterQueueName) {
return new SqsQueueHealthIndicator(amazonSQSAsync, deadLetterQueueName);
}
}
Because you're declaring it as final so its value must be initialized.
You did not provide default value so Spring understand its value will be injected.
So you have 2 options:
Remove the final modifier. Use #Value to inject your value from config file.
Create a bean type of String and inject it. You should name it:
#Bean("queueName")
public String getQueueName {return "xyz";}
And inject like:
#Autowire
#Qualifier("queueName")
private final String queueName;
In normal circumstance, the option 1 is the go-to.

Custom CacheResolver not working

I have a Spring Boot project with a custom CacheResolver as I need to decide on runtime which cache I want to use, I don't have any compilation errors but, when I do some tests and place a break point at my custom CacheResolver it never steps into it.
This is my Configuration class for the Cache:
#Configuration
#EnableCaching(proxyTargetClass = true)
#PropertySource(CacheConfig.CLASSPATH_DEPLOY_CACHE_PROPERTIES_PROPERTIES)
public class CacheConfig extends CachingConfigurerSupport{
public static final String CLASSPATH_DEPLOY_CACHE_PROPERTIES_PROPERTIES = "classpath:/deploy/cache-properties.properties";
public static final String CACHEABLE_DOCUMENTS_PROPERTY = "cacheable.documents";
public static final String TTL_CACHEABLE_DOCUMENTS_PROPERTY = "ttl.cacheable.documents";
public static final String SIZED_CACHEABLE_DOCUMENTS_PROPERTY = "sized.cacheable.documents";
public static final String CACHE_NAME = "permanentCache";
public static final String TTL_CACHE = "ttlCache";
public static final String SIZED_CACHE = "sizedCache";
public static final String CACHEABLE_DOCUMENTS = "cacheableDocuments";
public static final String SIZED_CACHEABLE_DOCUMENTS = "sizedCacheableDocuments";
public static final int WEIGHT = 1000000;
public static final int TO_KBYTES = 1000;
#Inject
protected Environment environment;
//#Bean
#Override
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
GuavaCache sizedCache = new GuavaCache(SIZED_CACHE, CacheBuilder.newBuilder().maximumWeight(WEIGHT).weigher(
(key, storable) -> {
String json = ((Storable) storable).toJson();
return json.getBytes().length / TO_KBYTES;
}
).build());
GuavaCache permanentCache = new GuavaCache(CACHE_NAME,CacheBuilder.newBuilder().build());
//GuavaCache ttlCache = new GuavaCache(TTL_CACHE, CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).build());
cacheManager.setCaches(Arrays.asList(permanentCache,sizedCache));
return cacheManager;
}
#Bean(name = "wgstCacheResolver")
#Override
public CacheResolver cacheResolver(){
CacheResolver cacheResolver = new WgstCacheResolver(cacheManager(),cacheableDocuments(),sizedCacheableDocuments());
return cacheResolver;
}
#Bean(name = CACHEABLE_DOCUMENTS)
public List<String> cacheableDocuments(){
String[] cacheableDocuments = StringUtils.commaDelimitedListToStringArray(environment.getProperty(CACHEABLE_DOCUMENTS_PROPERTY));
return Arrays.asList(cacheableDocuments);
}
#Bean(name = SIZED_CACHEABLE_DOCUMENTS)
public List<String> sizedCacheableDocuments(){
String[] sizedCacheableDocuments = StringUtils.commaDelimitedListToStringArray(environment.getProperty(SIZED_CACHEABLE_DOCUMENTS_PROPERTY));
return Arrays.asList(sizedCacheableDocuments);
}
}
Here is my CacheResolver
public class WgstCacheResolver extends AbstractCacheResolver {
private final List<String> cacheableDocuments;
private final List<String> sizedCacheableDocuments;
public WgstCacheResolver(final CacheManager cacheManager,final List<String> cacheableDocuments, final List<String> sizedCacheableDocuments) {
super(cacheManager);
this.cacheableDocuments = cacheableDocuments;
this.sizedCacheableDocuments = sizedCacheableDocuments;
}
/**
* Resolves the cache(s) to be updated on runtime
* #param context
* #return*/
#Override
protected Collection<String> getCacheNames(final CacheOperationInvocationContext<?> context) {
final Collection<String> cacheNames = new ArrayList<>();
final AbstractDao dao = (AbstractDao)context.getTarget();
final String documentType = dao.getDocumentType().toString();
if (cacheableDocuments.contains(documentType)){
cacheNames.add("permanentCache");
}
if (sizedCacheableDocuments.contains(documentType)){
cacheNames.add("sizedCache");
}
return cacheNames;
}
}
And here my DAO where I use the cache:
#Component
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.DEFAULT)
#CacheConfig(cacheResolver = "wgstCacheResolver")
public class CacheableDao<T extends Storable> extends AbstractDao<T> {
private final Logger logger = LoggerFactory.getLogger(CacheableDao.class);
public CacheableDao(final Bucket bucket, final Class<T> typeParameterClass, final DocumentType documentType) {
super(bucket, typeParameterClass, documentType);
}
#Cacheable(key = "{#root.methodName, #root.target.generateFullKey(#key)}")
public T get(final String key) throws DatastoreAccessException, ObjectMappingException {
//do something
}
.
.
.
}
I have tried implementing CacheResolver instead of extending AbstractCacheResolver but it didn't make any difference.
Thank you.
Cache names need to be included at some point, just specifying the CacheResolver to use is not enough, the #Cacheable class needs to be aware of the available cache names, so I included them with the #CacheConfig annotation:
#CacheConfig(cacheNames = {WgstCacheConfig.PERMANENT_CACHE, WgstCacheConfig.SIZED_CACHE},
cacheResolver = WgstCacheConfig.WGST_CACHE_RESOLVER)
public class CacheableDao<T extends Storable> extends AbstractDao<T> {
One thing that I don't like is that I need to provide a null CacheManager, even if I'm not using it, otherwise I get the following error:
Caused by: java.lang.IllegalStateException: No CacheResolver specified, and no bean of type CacheManager found. Register a CacheManager bean or remove the #EnableCaching annotation from your configuration.
So I left it like this, and it works:
#Bean
public CacheManager cacheManager() {
return null;
}
#Bean(name = WGST_CACHE_RESOLVER)
public CacheResolver cacheResolver(){
CacheResolver cacheResolver = new WgstCacheResolver(cacheableDocuments(),sizedCacheableDocuments(),getPermanentCache(),
getSizedCache());
return cacheResolver;
}
Reran my tests, stepping through my custom CacheResolver and it is behaving as expected resolving to the correct cache(s)
My configuration class is not extending CachingConfigurerSupport anymore.
After a bit of back and forth (Sorry about that!) it turns out this is indeed a bug in Spring Framework.
I've created SPR-13081. Expect a fix for the next maintenance release (4.1.7.RELEASE). Thanks for the sample project!

Inject EntityManager within ConstraintValidator

Hello I want to inject EntityManager within ConstraintValidator
This is my codes
CoreConfiguration
#Configuration
public class CoreConfiguration {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
#Bean(name="validator")
public static LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
/* * ReloadableResourceBundleMessageSource messageSource = new
* ReloadableResourceBundleMessageSource();
* messageSource.setBasename("/WEB-INF/messages/validation");
* localValidatorFactoryBean.setValidationMessageSource(messageSource);*/
return localValidatorFactoryBean;
}
}
UniqueKeyValidator.java
In this class I try to inject the EntityManager, but it always gives null
#Component
public class UniqueKeyValidator implements
ConstraintValidator<Unique, Serializable> {
#PersistenceContext
private EntityManager entityManager;
private Class<?> entityClass;
private String uniqueField;
public void initialize(Unique unique) {
entityClass = unique.entity();
uniqueField = unique.property();
}
#Transactional
public boolean isValid(Serializable property,
ConstraintValidatorContext cvContext) {
String query = String.format("from %s where %s = :val ",entityClass.getName(), uniqueField);
List<?> list = entityManager.createQuery(query).setParameter("val", property).getResultList();
return list != null && list.size() > 0;
}
}
When I test the code, the EntityManager always gives null.
You created the UniqueKeyValidator using its constructor (new UniqueKeyValidator()) instead of injecting it (using Spring DI) into your test class.
The CoreConfiguration doesn't contain this validator anyway, so probably Spring doesn't scan it at all. All #Component classes must have a component:scan too.

Categories