2015-10-08 21 views
6

Ich versuche, etwas ähnliches zu org.springframework.cache.annotation.Cacheable verwenden:Pass Methode Argument in Richtung der benutzerdefinierte Anmerkung

Individuelle Anmerkung:

@Target(ElementType.METHOD) 
    @Retention(RetentionPolicy.RUNTIME) 
    @Documented 
    public @interface CheckEntity { 
     String message() default "Check entity msg"; 
     String key() default ""; 
    } 

Richtung:

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

Meine IDE (IntelliJ) sieht nichts Besonderes mit der Verwendung key = "#id" im Gegensatz zu ähnlichen Verwendungen für Cacheable, wo es mit anderer Farbe als Klartext angezeigt wird. Ich erwähne den IDE-Teil nur als einen Hinweis für den Fall, dass es hilft, es sieht so aus, als wäre die IDE im Voraus über diese Anmerkungen informiert oder sie realisiert nur eine Verbindung, die in meinem Beispiel nicht existiert.

Der Wert in checkEntity.key ist '#id' anstelle einer erwarteten Zahl. Ich versuchte mit ExpressionParser aber möglicherweise nicht in der richtigen Weise.

Die einzige Möglichkeit, Parameterwert innerhalb der checkEntity-Annotation zu erhalten, ist der Zugriff auf das Array arguments, das nicht das ist, was ich möchte, da diese Annotation auch in Methoden mit mehr als einem Argument verwendet werden könnte.

Irgendeine Idee?

+0

Keine IDE kann Ihnen kontextbezogene Unterstützung für @ @ Cacheable bieten, da Ihr Aspekt maßgeschneidert ist. Kann ich fragen, welche Art von Funktionalität Sie mit Ihrem Aspekt zu bieten versuchen? Versuchen Sie zu überprüfen, ob eine Entität bereits existiert? – geoand

+0

Dies ist zu überprüfen, ob diese ID (sagen AbteilungId) in eingeloggten Benutzerabteilungen vorhanden ist, auf die sie zugreifen können, eine accessdenied Ausnahme werfen – Chris

+0

Würde nicht Spring Security oder Apache Shiro solche Funktionen bieten, ohne eigene Implementierung rollen zu müssen? – geoand

Antwort

1

Dank @StéphaneNicoll gelang es mir, eine erste Version einer Arbeitslösung zu schaffen:

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

Die 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; 
    } 
} 

Th e Root-Objekt

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; 
    } 
} 
+0

Wissen Sie, wie man es in Frühling 3 macht? AnnotatedElementKey wird in Spring 3 nicht unterstützt. –

+0

@AmitSadafule Sie können dies tun, indem Sie die Parameter an den Compiler übergeben, wenn Sie java8 verwenden. Konnte keinen Weg für ältere Java-Version finden. Siehe https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html – Chris

+0

@Chirs: Danke. Ich habe einen Weg gefunden, dies im Frühjahr 3 zu tun. Dazu muss man ExpressionEvaluator und LazyParamAwareEvaluationContext im Projekt kopieren, da diese Klassen nicht öffentlich sind (sie sind mit dem Paketlevel definiert).Dann müssen Sie folgendes tun: private final ExpressionEvaluator evaluator = new ExpressionEvaluator(); private EvaluationContext createEvaluationContext (Methodenmethode, Object [] args, Object target, Klasse targetClass) { return evaluator.createEvaluationContext (null, method, args, target, targetClass); } –

1

Spring verwendet intern eine ExpressionEvaluator den Frühling Expression Language im key Parameter (siehe CacheAspectSupport)

zu bewerten Wenn Sie das gleiche Verhalten emulieren wollen, müssen Sie einen Blick auf, wie CacheAspectSupport es tut. Hier ist ein Ausschnitt aus dem 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); 
    } 

Ich weiß nicht, welche IDE Sie verwenden, aber es muss mit der @Cacheable Anmerkung in einer anderen Art und Weise beschäftigt als mit den anderen, um die params zu markieren.

+0

Das scheint auch Cache-bezogene Implementierung zu sein. Ich habe den Cache nur als Beispiel erwähnt, um zu zeigen, wie ich ihn benutzen muss. Es wird angenommen, dass Spel den größten Teil der Magie für dich tut. In Bezug auf die IDE ist es IntelliJ, ich habe es nur als Hinweis erwähnt, falls es hilft. – Chris

3

Ich glaube, Sie missverstehen wahrscheinlich, was das Framework für Sie tun soll vs. was Sie tun müssen.

Die SPEL-Unterstützung kann nicht automatisch ausgelöst werden, sodass Sie auf den tatsächlichen (aufgelösten) Wert anstelle des Ausdrucks selbst zugreifen können. Warum? Weil es einen Kontext gibt und als Entwickler müssen Sie diesen Kontext bereitstellen.

Die Unterstützung in Intellij ist die gleiche Sache. Derzeit verfolgen Jetbrains Entwickler die Orte, an denen SPEL verwendet wird, und markieren sie für die Unterstützung von SPEL. Wir haben keine Möglichkeit, die Tatsache auszuführen, dass der Wert ein tatsächlicher SpEL-Ausdruck ist (das ist ein roher java.lang.String schließlich für den Annotationstyp).

Ab 4.2 haben wir einige der Dienstprogramme extrahiert, die die Cache-Abstraktion intern verwendet. Vielleicht möchten Sie davon profitieren (normalerweise CachedExpressionEvaluator und MethodBasedEvaluationContext).

Die neue @EventListener verwendet dieses Zeug, so dass Sie mehr Code haben, den Sie als Beispiele für die Sache betrachten können, die Sie versuchen zu tun: EventExpressionEvaluator.

Zusammengefasst muss Ihr benutzerdefinierter Interceptor etwas tun, das auf dem Wert #id basiert.Diese code snippet ist ein Beispiel für eine solche Verarbeitung und hängt überhaupt nicht von der Cache-Abstraktion ab.

+0

Hallo Stephane, das sieht nach einer Antwort in die richtige Richtung aus. Ich werde es versuchen und ich werde es dich wissen lassen. Vielen Dank! – Chris

0

Ihre Annotation kann mit Methoden mit mehr als 1 Parameter verwendet werden, aber das bedeutet nicht, dass Sie das Array arguments nicht verwenden können. Hier ist eine Lösung:

Zuerst müssen wir den Index des Parameters "ID" finden. Dies kann man wie so tun:

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

wo "paramName" = Ihre "id" param

Als nächstes können Sie die aktuelle ID-Wert von den Argumenten erhalten wie folgt:

Integer parameterIdx = getParameterIdx(joinPoint, "id"); 
Long id = joinPoint.getArgs()[parameterIdx]; 

Of Das setzt natürlich voraus, dass Sie immer den Parameter "id" nennen. Ein Update könnte es sein, damit die Parameternamen auf der Anmerkung angeben, so etwas wie

@Target(ElementType.METHOD) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
public @interface CheckEntity { 
    String message() default "Check entity msg"; 
    String key() default ""; 
    String paramName() default "id"; 
} 
+0

'ProceedingJointPoint' kann nur für' @ Around'-Advices verwendet werden, nicht '@ Before'. Parameternamen ist null. Es gibt Chancen, dass es behoben werden kann (http://stackoverflow.com/questions/25226441/java-aop-joinpoint-does-not-get-parameter-names), aber ich würde nicht lieber die Schnittstelle entfernen noch Parameter Anmerkungen hinzufügen wo immer diese Annotation verwendet wird. – Chris

Verwandte Themen