What's the best way to access your AOP proxy in spring? - spring

Due to Spring's implementation of AOP there are times when you want to call a method within the same class where you'd like the call to pass through your advice.
Here's a quick example
#Service
public class SomeDaoClass {
public int getSomeValue() {
// do some cache look up
Integer cached = ...;
if ( cached == null ) {
cached = txnGetSomeValue();
// store in cache
}
return cached;
}
#Transactional
public int txnGetSomeValue() {
// look up in database
}
}
Now ignore for a second that in spring I could use #Cached annotations for caching, the point of my example is that getSomeValue() is accessed by other beans passing through the spring proxy and running the associated advice. The internal call to txnGetSomeValue will not and in my example will miss having the advice we apply at the #Transactional point cut.
What's the best way to get access to your proxy so you can apply the advice?
My best approach works, but is clumsy. It heavily exposes the implementation. I figured this out years ago and have just stuck with this awkward code but would like to know what is the preferred method. My hacky approach looks like this.
public interface SomeDaoClass {
public int getSomeValue();
// sometimes I'll put these methods in another interface to hide them
// but keeping them in the primary interface for simplicity.
public int txnGetSomeValue();
}
#Service
public class SomeDaoClassImpl implements SomeDaoClass, BeanNameAware, ApplicationContextAware {
public int getSomeValue() {
// do some cache look up
Integer cached = ...;
if ( cached == null ) {
cached = proxy.txnGetSomeValue();
// store in cache
}
return cached;
}
#Transactional
public int txnGetSomeValue() {
// look up in database
}
SomeDaoClass proxy = this;
String beanName = null;
ApplicationContext ctx = null;
#Override
public void setBeanName(String name) {
beanName = name;
setProxy();
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ctx = applicationContext;
setProxy();
}
private void setProxy() {
if ( beanName == null || ctx == null )
return;
proxy = (BlockDao) ctx.getBean(beanName);
beanName = null;
ctx = null;
}
}
What I've done is add BeanNameAware and SpringContextAware so that I can look up my proxy object in Spring. Ugly I know. Any advice on a cleaner way to do this would be nice.

You could inject the proxy using #Resource
#Service
public class SomeDaoClassImpl implements SomeDaoClass {
#Resource
private SomeDaoClassImpl someDaoClassImpl;
public int getSomeValue() {
// do some cache look up
Integer cached = ...;
if ( cached == null ) {
cached = somDaoClassImpl.txnGetSomeValue();
// store in cache
}
return cached;
}
#Transactional
public int txnGetSomeValue() {
// look up in database
}
Note: #Autowired will not work.

Related

more than one 'primary' service instance suppliers found during load balancing (spring boot/cloud)

I'm currently updating from Spring boot 2.2.x to 2.6.x + legacy code, it's a big jump so there were multiple changes. I'm now running into a problem with load balancing through an api-gateway. I'll apologize in advance for the wall of code to come. I will put the point of failure at the bottom.
When I send in an API request, I get the following error:
more than one 'primary' bean found among candidates: [zookeeperDiscoveryClientServiceInstanceListSupplier, serviceInstanceListSupplier, retryAwareDiscoveryClientServiceInstanceListSupplier]
it seems that the zookeeperDiscovery and retryAware suppliers are loaded through the default serviceInsatnceListSupplier, which has #Primary over it. I thought would take precedence over the other ones. I assume I must be doing something wrong due changes in the newer version, here are the relevant code in question:
#Configuration
#LoadBalancerClients(defaultConfiguration = ClientConfiguration.class)
public class WebClientConfiguration {
#Bean
#Qualifier("microserviceWebClient")
#ConditionalOnMissingBean(name = "microserviceWebClient")
public WebClient microserviceWebClient(#Qualifier("microserviceWebClientBuilder") WebClient.Builder builder) {
return builder.build();
}
#Bean
#Qualifier("microserviceWebClientBuilder")
#ConditionalOnMissingBean(name = "microserviceWebClientBuilder")
#LoadBalanced
public WebClient.Builder microserviceWebClientBuilder() {
return WebClient.builder();
}
#Bean
#Primary
public ReactorLoadBalancerExchangeFilterFunction reactorLoadBalancerExchangeFilterFunction(
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
//the transformer is currently null, there wasn't a transformer before the upgrade
return new CustomExchangeFilterFunction(loadBalancerFactory, transformer);
}
}
There are also some Feign Client related configs here which I will omit, since it's not (or shouldn't be) playing a role in this problem:
public class ClientConfiguration {
/**
* The property key within the feign clients configuration context for the feign client name.
*/
public static final String FEIGN_CLIENT_NAME_PROPERTY = "feign.client.name";
public ClientConfiguration() {
}
//Creates a new BiPredicate for shouldClose. This will be used to determine if HTTP Connections should be automatically closed or not.
#Bean
#ConditionalOnMissingBean
public BiPredicate<Response, Type> shouldClose() {
return (Response response, Type type) -> {
if(type instanceof Class) {
Class<?> currentClass = (Class<?>) type;
return (null == AnnotationUtils.getAnnotation(currentClass, EnableResponseStream.class));
}
return true;
};
}
//Creates a Custom Decoder
#Bean
public Decoder createCustomDecoder(
ObjectFactory<HttpMessageConverters> converters, BiPredicate<Response, Type> shouldClose
) {
return new CustomDecoder(converters, shouldClose);
}
#Bean
#Qualifier("loadBalancerName")
public String loadBalancerName(PropertyResolver propertyResolver) {
String name = propertyResolver.getProperty(FEIGN_CLIENT_NAME_PROPERTY);
if(StringUtils.hasText(name)) {
// we are in a feign context
return name;
}
// we are in a LoadBalancerClientFactory context
name = propertyResolver.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
Assert.notNull(name, "Could not find a load balancer name within the configuration context!");
return name;
}
#Bean
public ReactorServiceInstanceLoadBalancer reactorServiceInstanceLoadBalancer(
BeanFactory beanFactory, #Qualifier("loadBalancerName") String loadBalancerName
) {
return new CustomRoundRobinLoadBalancer(
beanFactory.getBeanProvider(ServiceInstanceListSupplier.class),
loadBalancerName
);
}
#Bean
#Primary
public ServiceInstanceListSupplier serviceInstanceListSupplier(
#Qualifier(
"filter"
) Predicate<ServiceInstance> filter, DiscoveryClient discoveryClient, Environment environment, #Qualifier(
"loadBalancerName"
) String loadBalancerName
) {
// add service name to environment if necessary
if(environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME) == null) {
StandardEnvironment wrapped = new StandardEnvironment();
if(environment instanceof ConfigurableEnvironment) {
((ConfigurableEnvironment) environment).getPropertySources()
.forEach(s -> wrapped.getPropertySources().addLast(s));
}
Map<String, Object> additionalProperties = new HashMap<>();
additionalProperties.put(LoadBalancerClientFactory.PROPERTY_NAME, loadBalancerName);
wrapped.getPropertySources().addLast(new MapPropertySource(loadBalancerName, additionalProperties));
environment = wrapped;
}
return new FilteringInstanceListSupplier(filter, discoveryClient, environment);
}
}
There was a change in the ExchangeFilter constructor, but as far as I can tell, it accepts that empty transformer,I don't know if it's supposed to:
public class CustomExchangeFilterFunction extends ReactorLoadBalancerExchangeFilterFunction {
private static final ThreadLocal<ClientRequest> REQUEST_HOLDER = new ThreadLocal<>();
//I think it's wrong but I don't know what to do here
private static List<LoadBalancerClientRequestTransformer> transformersList;
private final Factory<ServiceInstance> loadBalancerFactory;
public CustomExchangeFilterFunction (Factory<ServiceInstance> loadBalancerFactory) {
this(loadBalancerFactory);
///according to docs, but I don't know where and if I need to use this
#Bean
public LoadBalancerClientRequestTransformer transformer() {
return new LoadBalancerClientRequestTransformer() {
#Override
public ClientRequest transformRequest(ClientRequest request, ServiceInstance instance) {
return ClientRequest.from(request)
.header(instance.getInstanceId())
.build();
}
};
}
public CustomExchangeFilterFunction (Factory<ServiceInstance> loadBalancerFactory, List<LoadBalancerClientRequestTransformer> transformersList) {
super(loadBalancerFactory, transformersList); //the changed constructor
this.loadBalancerFactory = loadBalancerFactory;;
}
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
// put the current request into the thread context - ugly, but couldn't find a better way to access the request within
// the choose method without reimplementing nearly everything
REQUEST_HOLDER.set(request);
try {
return super.filter(request, next);
} finally {
REQUEST_HOLDER.remove();
}
}
//used to be an override, but the function has changed
//code execution doesn't even get this far yet
protected Mono<Response<ServiceInstance>> choose(String serviceId) {
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerFactory.getInstance(serviceId);
if(loadBalancer == null) {
return Mono.just(new EmptyResponse());
}
ClientRequest request = REQUEST_HOLDER.get();
// this might be null, if the underlying implementation changed and this method is no longer executed in the same
// thread
// as the filter method
Assert.notNull(request, "request must not be null, underlying implementation seems to have changed");
return choose(loadBalancer, filter);
}
protected Mono<Response<ServiceInstance>> choose(
ReactiveLoadBalancer<ServiceInstance> loadBalancer,
Predicate<ServiceInstance> filter
) {
return Mono.from(loadBalancer.choose(new DefaultRequest<>(filter)));
}
}
There were pretty big changes in the CustomExchangeFilterFunction, but the current execution doesn't even get there. It fails here, in .getIfAvailable(...):
public class CustomRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final int DEFAULT_SEED_POSITION = 1000;
private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private final String serviceId;
private final int seedPosition;
private final AtomicInteger position;
private final Map<String, AtomicInteger> positionsForVersions = new HashMap<>();
public CustomRoundRobinLoadBalancer (
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId
) {
this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(DEFAULT_SEED_POSITION));
}
public CustomRoundRobinLoadBalancer (
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId,
int seedPosition
) {
Assert.notNull(serviceInstanceListSupplierProvider, "serviceInstanceListSupplierProvider must not be null");
Assert.notNull(serviceId, "serviceId must not be null");
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.serviceId = serviceId;
this.seedPosition = seedPosition;
this.position = new AtomicInteger(seedPosition);
}
#Override
// we have no choice but to use the raw type Request here, because this method overrides another one with this signature
public Mono<Response<ServiceInstance>> choose(#SuppressWarnings("rawtypes") Request request) {
//fails here!
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get().next().map((List<ServiceInstance> instances) -> getInstanceResponse(instances, request));
}
}
Edit: after some deeper stacktracing, it seems that it does go into the CustomFilterFunction and invokes the constructor with super(loadBalancerFactory, transformer)
I found the problem or a workaround. I was using #LoadBalancerClients because I thought it would just set the same config for all clients that way (even if I technically only have one atm). I changed it to ##LoadBalancerClient and it suddenly worked. I don't quite understand why this made a difference but it did!

Spring boot cache not working in #PostConstruct

I'm building a "class cache", with classes I want to call later.
The main goal is that I don't want scan the context every time that a class instance is needed.
# Model / Repository classes
#Getter
#RequiredArgsConstructor
public class Block implements Serializable {
private final String className;
private final Set<String> classCandidates = new HashSet<>();
public boolean addCandidate(final String classCandidate) {
return this.classCandidates.add(classCandidate);
}
}
#Slf4j
#Component
#CacheConfig(cacheNames = ConstantsCache.CACHE_BLOCK)
public class BlockRepository {
#Cacheable(key = "#className")
public Block findByInputClass(final String className) {
log.info("---> Loading classes for class '{}'", className);
val block = new Block(className);
findCandidates(block);
return block;
}
}
First to evaluate the cache, I've put the cache method #Autowired in a #RestController, wich works fine. The cache is populated when I call the rest method.
#RestController
public class Controller {
#Autowired
BlockRepository blockRepository;
#RequestMapping("/findByInputClass")
public Block findByInputClass(#RequestParam("className") final String className) {
return blockRepository.findByInputClass(className);
}
}
After doing that, I've moved the #Autowired object to a #Service, creating a method to self-populate the cache. But this does not work. The cache is not populated when the #PostConstructor method is called.
#Slf4j
#Component
public class BlockCacheService {
#Autowired
BlockRepository blockRepository;
#PostConstruct
private void postConstruct() {
log.info("*** {} PostConstruct called.", this.getClass().getTypeName());
val block = blockRepository.findByInputClass(ConstantsGenerics.BLOCK_PARENT_CLASS);
final Set<String> inputClasses = getInputFromCandidates(block.getClassCandidates());
appendClassesToCache(inputClasses);
}
private void appendClassesToCache(final Set<String> inputClasses) {
for (val inputClass : inputClasses) {
blockRepository.findByInputClass(inputClass);
}
}
}
How can I properly populate the cache using a service or component, that must start with the application.
Thanks in advance.
EDIT:
I've found a possible solution here: https://stackoverflow.com/a/28311225/1703546
Than I've changed the #Service code to put the cache manually instead of use the #Cacheable magic abstraction.
The class now is like this.
#Slf4j
#Component
public class BlockCacheService {
#Autowired
CacheManager cacheManager;
#Autowired
BlockRepository blockRepository;
#PostConstruct
private void postConstruct() {
log.info("*** {} PostConstruct called.", this.getClass().getTypeName());
val block = blockRepository.findByInputClass(ConstantsGenerics.BLOCK_PARENT_CLASS);
final Set<String> inputClasses = getInputFromCandidates(block.getClassCandidates());
appendClassesToCache(inputClasses);
}
private void appendClassesToCache(final Set<String> inputClasses) {
for (val inputClass : inputClasses) {
val block = blockRepository.findByInputClass(inputClass);
cacheManager.getCache(ConstantsCache.CACHE_BLOCK).put(block.getClassName(), block);
}
}
}
Now the cache is populated correctly, but the question is, this is the best solution?
Thanks.
You can't use an aspect in #PostConstruct as it may not have been created yet (and that is documented by the way).
One possible way to make that work is to implement SmartInitializingBean instead as it gives a callback when all singletons have been fully initialized (including their aspect. Changing that on your original service should work.
Having said that, this code of yours has an impact on the startup time. Why don't you let your cache to be filled lazily instead?

conditional #Autowired?

I have a HsqldbReconciler (for "work" with a HSQLDB database) which I autowired, like:
#Autowired
HsqldbReconciler hsqldbReconciler;
In Future there will be a OracleReconciler, MssqlReconciler, etc. I will need to use them accordingly to the type of connection a user has chosen.
How should I implement this? Usually I would have a kind of factory, which returns only the needed Reconciler. The only way in spring, I can currently imagine, is to Autowire an instance of each Reconciler, then use one of them in the code. Is there a better way?
make a Factory Class that will contain all your beans, e.g
#Component
class Factory{
#Autowired HsqldbReconciler hsqldb;
#Autowired OracleReconciler oracle;
#Autowired MssqlReconciler mssql;
public Object getInstance(String type){
switch(type){
case "mssql" : return mssql;
case "oracle" : return oracle;
// and so on
default : return null;
}
}
}
Now use this Factory as follows
class SomeClass{
#Autowired private Factory factory;
public Object someMethod(){
Object reconciler = factory.getInstance("mssql");
((MssqlReconciler)reconciler).someMethod();
}
}
Define them in your Config with the same name, but different conditions:
#Bean(name = "dbReconciler")
#Conditional(HsqldbReconcilerEnabled.class)
public ReconcilerBase getHsqldbReconciler() {
return new HsqldbReconciler();
}
#Bean(name = "dbReconciler")
#Conditional(OracleReconcilerEnabled.class)
public ReconcilerBase getOracleReconciler() {
return new OracleReconciler();
}
#Bean(name = "dbReconciler")
#Conditional(MssqlReconcilerEnabled.class)
public ReconcilerBase getMssqlReconciler() {
return new MssqlReconciler();
}
create conditions reading from app.properties:
HsqldbReconciler.enabled=true
OracleReconciler.enabled=false
MssqlReconciler.enabled=false
like this:
public class HsqldbReconcilerEnabled implements Condition {
private static final String PROP_ENABLED = "HsqldbReconciler.enabled";
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String property = context.getEnvironment().getProperty(PROP_ENABLED);
return Boolean.parseBoolean(property);
}
}
// etc...
use like:
#Autowired
#Qualifier("dbReconciler")
ReconcilerBase dbReconsiler;
ensure you're not enabling multiple beans at the same time.

Pass method argument in Aspect of custom annotation

I'm trying to use something similar to org.springframework.cache.annotation.Cacheable :
Custom annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CheckEntity {
String message() default "Check entity msg";
String key() default "";
}
Aspect:
#Component
#Aspect
public class CheckEntityAspect {
#Before("execution(* *.*(..)) && #annotation(checkEntity)")
public void checkEntity(JoinPoint joinPoint, CheckEntitty checkEntity) {
System.out.println("running entity check: " + joinPoint.getSignature().getName());
}
}
Service:
#Service
#Transactional
public class EntityServiceImpl implements EntityService {
#CheckEntity(key = "#id")
public Entity getEntity(Long id) {
return new Entity(id);
}
}
My IDE (IntelliJ) doesn't see anything special with the key = "#id" usage in contrast to similar usages for Cacheable where it's shown with different color than plain text. I'm mentioning the IDE part just as a hint in case it helps, it looks like the IDE is aware in advance about these annotations or it just realizes some connection which doesn't exist in my example.
The value in the checkEntity.key is '#id' instead of an expected number.
I tried using ExpressionParser but possibly not in the right way.
The only way to get parameter value inside the checkEntity annotation is by accessing the arguments array which is not what I want because this annotation could be used also in methods with more than one argument.
Any idea?
Adding another simpler way of doing it using Spring Expression. Refer below:
Your Annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CheckEntity {
String message() default "Check entity msg";
String keyPath() default "";
}
Your Service:
#Service
#Transactional
public class EntityServiceImpl implements EntityService {
#CheckEntity(keyPath = "[0]")
public Entity getEntity(Long id) {
return new Entity(id);
}
#CheckEntity(keyPath = "[1].otherId")
public Entity methodWithMoreThanOneArguments(String message, CustomClassForExample object) {
return new Entity(object.otherId);
}
}
class CustomClassForExample {
Long otherId;
}
Your Aspect:
#Component
#Aspect
public class CheckEntityAspect {
#Before("execution(* *.*(..)) && #annotation(checkEntity)")
public void checkEntity(JoinPoint joinPoint, CheckEntitty checkEntity) {
Object[] args = joinPoint.getArgs();
ExpressionParser elParser = new SpelExpressionParser();
Expression expression = elParser.parseExpression(checkEntity.keyPath());
Long id = (Long) expression.getValue(args);
// Do whatever you want to do with this id
// This works for both the service methods provided above and can be re-used for any number of similar methods
}
}
PS: I am adding this solution because I feel this is a simpler/clearner approach as compared to other answers and this might be helpful for someone.
Thanks to #StéphaneNicoll I managed to create a first version of a working solution:
The Aspect
#Component
#Aspect
public class CheckEntityAspect {
protected final Log logger = LogFactory.getLog(getClass());
private ExpressionEvaluator<Long> evaluator = new ExpressionEvaluator<>();
#Before("execution(* *.*(..)) && #annotation(checkEntity)")
public void checkEntity(JoinPoint joinPoint, CheckEntity checkEntity) {
Long result = getValue(joinPoint, checkEntity.key());
logger.info("result: " + result);
System.out.println("running entity check: " + joinPoint.getSignature().getName());
}
private Long getValue(JoinPoint joinPoint, String condition) {
return getValue(joinPoint.getTarget(), joinPoint.getArgs(),
joinPoint.getTarget().getClass(),
((MethodSignature) joinPoint.getSignature()).getMethod(), condition);
}
private Long getValue(Object object, Object[] args, Class clazz, Method method, String condition) {
if (args == null) {
return null;
}
EvaluationContext evaluationContext = evaluator.createEvaluationContext(object, clazz, method, args);
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, clazz);
return evaluator.condition(condition, methodKey, evaluationContext, Long.class);
}
}
The Expression Evaluator
public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {
// shared param discoverer since it caches data internally
private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
/**
* Create the suitable {#link EvaluationContext} for the specified event handling
* on the specified method.
*/
public EvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method, Object[] args) {
Method targetMethod = getTargetMethod(targetClass, method);
ExpressionRootObject root = new ExpressionRootObject(object, args);
return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
}
/**
* Specify if the condition defined by the specified expression matches.
*/
public T condition(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext, Class<T> clazz) {
return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext, clazz);
}
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
if (targetMethod == null) {
targetMethod = method;
}
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
}
The Root Object
public class ExpressionRootObject {
private final Object object;
private final Object[] args;
public ExpressionRootObject(Object object, Object[] args) {
this.object = object;
this.args = args;
}
public Object getObject() {
return object;
}
public Object[] getArgs() {
return args;
}
}
I think you probably misunderstand what the framework is supposed to do for you vs. what you have to do.
SpEL support has no way to be triggered automagically so that you can access the actual (resolved) value instead of the expression itself. Why? Because there is a context and as a developer you have to provide this context.
The support in Intellij is the same thing. Currently Jetbrains devs track the places where SpEL is used and mark them for SpEL support. We don't have any way to conduct the fact that the value is an actual SpEL expression (this is a raw java.lang.String on the annotation type after all).
As of 4.2, we have extracted some of the utilities that the cache abstraction uses internally. You may want to benefit from that stuff (typically CachedExpressionEvaluator and MethodBasedEvaluationContext).
The new #EventListener is using that stuff so you have more code you can look at as examples for the thing you're trying to do: EventExpressionEvaluator.
In summary, your custom interceptor needs to do something based on the #id value. This code snippet is an example of such processing and it does not depend on the cache abstraction at all.
Spring uses internally an ExpressionEvaluator to evaluate the Spring Expression Language in the key parameter (see CacheAspectSupport)
If you want to emulate the same behaviour, have a look at how CacheAspectSupport is doing it. Here is an snippet of the code:
private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
/**
* Compute the key for the given caching operation.
* #return the generated key, or {#code null} if none can be generated
*/
protected Object generateKey(Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey, evaluationContext);
}
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
private EvaluationContext createEvaluationContext(Object result) {
return evaluator.createEvaluationContext(
this.caches, this.metadata.method, this.args, this.target, this.metadata.targetClass, result);
}
I don't know which IDE you are using, but it must deal with the #Cacheable annotation in a different way than with the others in order to highlight the params.
Your annotation can be used with methods with more than 1 parameter, but that doesn't mean you can't use the arguments array. Here's a sollution:
First we have to find the index of the "id" parameter. This you can do like so:
private Integer getParameterIdx(ProceedingJoinPoint joinPoint, String paramName) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] parameterNames = methodSignature.getParameterNames();
for (int i = 0; i < parameterNames.length; i++) {
String parameterName = parameterNames[i];
if (paramName.equals(parameterName)) {
return i;
}
}
return -1;
}
where "paramName" = your "id" param
Next you can get the actual id value from the arguments like so:
Integer parameterIdx = getParameterIdx(joinPoint, "id");
Long id = joinPoint.getArgs()[parameterIdx];
Of course this assumes that you always name that parameter "id". One fix there could be to allow to specify the parameter name on the annotation, something like
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CheckEntity {
String message() default "Check entity msg";
String key() default "";
String paramName() default "id";
}

Wiring Repository interfaces in service layer dynamically

The Service class and my repository classes in my spring MVC set up are something like this -
public class ObjectServiceImpl implements ObjectService {
#Autowired
Temp1Repo temp1Repo;
#Autowired
Temp2Repo temp2Repo;
...
}
public interface Temp1Repo extends CrudRepository<Temp1, Integer> {
}
public interface Temp2Repo extends CrudRepository<Temp2, Integer> {
}
Now, in my service class, i am getting a object of a type Temp1, I have to call temp1Repo.save(). If I get an object of Temp2, I have to call temp2Repo.save() and so on...
How do i achieve this?
Seems fairly simple to just have an if statement:
if(object instanceof Temp1) {
temp1Repo.save((Temp1) object);
} else if(object instanceof Temp2) {
temp2Repo.save((Temp2) object);
}
Or perhaps you are looking for a more generic way?
I suppose that you want to regroup all repositories in one. Something like
#SuppressWarnings("rawtypes")
public class ObjectServiceImpl {
#Autowired
private CrudRepository[] repositories;
private Map<Class<?>, CrudRepository> repositoryMap = new HashMap<Class<?>, CrudRepository>();
#PostConstruct
public void init() {
for (CrudRepository r : repositories)
repositoryMap.put(getType(r), r);
}
private Class<?> getType(CrudRepository repository) {
Type[] types = repository.getClass().getGenericInterfaces();
for (Type t : types) {
if (t instanceof ParameterizedType)
return (Class<?>) ((ParameterizedType) t).getActualTypeArguments()[0];
}
throw new IllegalStateException("Check repositories...");
}
public void save(Object entity) {
repositoryMap.get(entity.getClass()).save(entity);
}
public <T> T get(Object id, Class<T> clazz) {
return repositoryMap.get(clazz).findOne(id);
}
....
}
Consider to use EntityManager directly, but could be useful anyway...
Following the code you wrote, Spring will rise an exception at startup time if any injection is missing.
What you want to do is a dynamic Module load, depending on a condition you omitted within your question.
You probably have to use XML configuration style and create a by condition spring context and load the correct one to be used.
Cheers

Resources