2015-10-08 30 views
6

Sto cercando di usare qualcosa di simile a org.springframework.cache.annotation.Cacheable:metodo Passo argomento Aspetto di annotazione personalizzati

annotazione personalizzato:

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

Aspetto:

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

Servizio:

@Service 
@Transactional 
public class EntityServiceImpl implements EntityService { 

    @CheckEntity(key = "#id") 
    public Entity getEntity(Long id) { 
     return new Entity(id); 
    } 
}  

My IDE (IntelliJ) non vede nulla di speciale con l'utilizzo di key = "#id" in contrasto con usi simili per Cacheable dove viene mostrato con colori diversi rispetto al testo normale. Sto citando la parte IDE solo come suggerimento nel caso in cui aiuti, sembra che l'IDE sia a conoscenza in anticipo di queste annotazioni o semplicemente realizzi qualche connessione che non esiste nel mio esempio.

Il valore nel checkEntity.key è '#id' invece di un numero previsto. Ho provato a utilizzare ExpressionParser ma forse non nel modo giusto.

L'unico modo per ottenere il valore del parametro all'interno dell'annotazione checkEntity è accedere all'array degli argomenti che non è quello che voglio perché questa annotazione potrebbe essere utilizzata anche in metodi con più di un argomento.

Qualche idea?

+0

Nessun IDE sarà in grado di fornire supporto contestuale per '@ Cacheable', perché il tuo aspetto è fatto su misura. Posso chiederti che tipo di funzionalità stai tentando di fornire con il tuo Aspect? Stai cercando di controllare e vedere se un'entità esiste già? – geoand

+0

Questo serve per verificare se questo id (diciamo departmentId) esiste nei reparti degli utenti registrati a cui possono avere accesso, lanciare un'eccezione di accesso negato altrimenti – Chris

+0

Spring Security o Apache Shiro non fornirebbero tali funzionalità senza dover implementare la propria implementazione? – geoand

risposta

1

Grazie a @StéphaneNicoll sono riuscito a creare una prima versione di una soluzione di lavoro:

L'aspetto

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

di espressioni

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

Sai come farlo nella primavera 3? AnnotatedElementKey non è supportato nella primavera 3. –

+0

@AmitSadafule Puoi farlo passando i parametri al compilatore se usi java8. Impossibile trovare un modo per la versione java precedente. Vedere https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html – Chris

+0

@Chirs: Grazie. Ho trovato un modo per farlo in primavera 3. Per questo si deve copiare ExpressionEvaluator e LazyParamAwareEvaluationContext nel progetto in quanto queste classi non sono pubbliche (sono definite con scope a livello di pacchetto).Quindi è necessario eseguire il seguente valutatore finale privato ExpressionEvaluator = new ExpressionEvaluator(); private EvaluationContext createEvaluationContext (metodo Method, Object [] args, Object target, Classe targetClass) { return evaluator.createEvaluationContext (null, method, args, target, targetClass); } –

1

Primavera utilizza internamente un ExpressionEvaluator per valutare la lingua Primavera espressione nel parametro key (vedi CacheAspectSupport)

Se si desidera emulare lo stesso comportamento, hanno uno sguardo a come CacheAspectSupport lo sta facendo. Ecco un frammento di codice:

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

Non so che ide che si sta utilizzando, ma deve fare i conti con la @Cacheable annotazioni in un modo diverso che con gli altri al fine di evidenziare i params.

+0

Questo sembra essere un'implementazione troppo legata alla cache. Ho menzionato la cache solo come un esempio per mostrare come ho bisogno di usarlo. Si suppone che lo spel esegua la maggior parte della magia per te. Per quanto riguarda l'IDE è IntelliJ, l'ho appena menzionato come suggerimento nel caso in cui sia d'aiuto. – Chris

3

Penso che probabilmente fraintendiate ciò che il framework dovrebbe fare per voi e ciò che dovete fare.

Il supporto SpEL non può essere attivato automaticamente in modo da poter accedere al valore effettivo (risolto) anziché all'espressione stessa. Perché? Perché c'è un contesto e come sviluppatore devi fornire questo contesto.

Il supporto in Intellij è la stessa cosa. Attualmente gli sviluppatori di Jetbrains tracciano i luoghi in cui viene utilizzato SpEL e li contrassegnano per il supporto SpEL. Non abbiamo alcun modo di dimostrare il fatto che il valore è un'espressione SPEL reale (questo è un raw java.lang.String sul tipo di annotazione dopo tutto).

A partire da 4.2, abbiamo estratto alcune delle utilità che l'astrazione della cache utilizza internamente. Si consiglia di beneficiare di tali elementi (in genere CachedExpressionEvaluator e MethodBasedEvaluationContext).

Il nuovo @EventListener sta utilizzando quella roba in modo da avere più codice che puoi guardare come esempi per la cosa che stai cercando di fare: EventExpressionEvaluator.

In breve, l'intercettore personalizzato deve eseguire un'operazione basata sul valore #id.Questo code snippet è un esempio di tale elaborazione e non dipende affatto dall'astrazione della cache.

+0

Ciao Stephane, anche se sembra una risposta nella giusta direzione. Ci proverò e ti farò sapere. Grazie! – Chris

0

L'annotazione può essere utilizzata con metodi con più di 1 parametro, ma ciò non significa che non è possibile utilizzare la matrice di argomenti. Ecco una soluzione:

Prima dobbiamo trovare l'indice del parametro "id". Questo si può fare in questo modo:

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

dove "paramName" = il vostro "id" param

successivo è possibile ottenere il valore id reale dagli argomenti in questo modo:

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

Di Naturalmente questo presuppone che tu chiamerai sempre quel parametro "id". Una soluzione ci potrebbe essere quella di consentire di specificare il nome del parametro sul annotazioni, qualcosa come

@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' può essere utilizzato solo per i consigli' @ Around', non '@ Before'. I nomi dei parametri sono nulli. Ci sono possibilità che possa essere corretto (http://stackoverflow.com/questions/25226441/java-aop-joinpoint-does-not-get-parameter-names) ma non preferirei rimuovere l'interfaccia né aggiungere annotazioni ai parametri ovunque questa annotazione verrà utilizzata. – Chris