1
votes

Mon BeanPostProcessor ne fonctionne pas dans Spring Boot

J'ai écrit mon BeanPostProcessor pour que toutes les méthodes marquées de mon annotation @Timing affichent l'heure de leur exécution dans la console.

J'utilise Spring Boot.

Mon BeanPostProcessor ressemble à ceci:

@Timing
public  List<Map<String, Object>> selectQuery() {
    String selectQuery = prop.getMYSQL_SELECT();
    return mysqlTemplate.queryForList(selectQuery);
}

Ceci est mon annotation @Timing

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Timing {
}

Je déclare cette annotation sur l'une des méthodes de la classe dao:

    import com.example.version2.annotation.Timing;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.stereotype.Component;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;

    @Component
    public class TimingBeanPostProcessor implements BeanPostProcessor {

        @Override
        public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
            Class type = bean.getClass();
            Method[] methods = type.getMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(Timing.class)) {
                    Object proxy = Proxy.newProxyInstance(type.getClassLoader(),type.getInterfaces(), new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            long before = System.nanoTime();
                            Object retVal = method.invoke(bean, args);
                            long after = System.nanoTime();
                            System.out.println("Method worked: " + (after - before) + " nano seconds");
                            return retVal;
                        }
                    });
                    return proxy;
                } else {
                    return bean;
                }
            }

             return bean;
        }

        @Override
        public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
            return bean;
        }

    }

Au démarrage de l'application, il n'y a pas de problème, mais quand je exécuter la demande, je ne vois rien dans la console. Il semble que BeanPostProcessor lui-même a écrit correctement. Je ne trouve pas quelle est l'erreur.

Je voudrais aussi savoir comment je peux transférer ces informations sur le moment où la méthode est exécutée vers le frontend dans json ou une liste (pas important).


3 commentaires

Je n'ai jamais utilisé cela auparavant, mais cela me semble étrange que pour chaque méthode annotée, vous renvoyez une classe proxy. Parce que je crois comprendre que Proxy.newProxyInstance renvoie une classe. J'ai écrit une annotation de timing il y a quelque temps et j'ai utilisé Spring AOP pour cela, c'était beaucoup plus facile d'utiliser les aspects.


@ThomasAndolf merci. Pouvez-vous montrer l'exemple, s'il vous plaît?


Pourquoi ne pas utiliser le support des métriques de Spring Boot, au lieu d'essayer d'écrire le vôtre?


3 Réponses :


1
votes

J'utilise généralement des aspects pour cette dépendance

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Timed

package some.thing;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed {


}

:

@Aspect
public class TimedAspect {

    @Around("@annotation(some.thing.Timed)")
    public Object timeSomething(ProceedingJoinPoint joinPoint) throws Throwable {
        final long before = System.nanoTime();
        final Object returnValue = joinPoint.proceed()
        final long after = System.nanoTime();
        System.out.println("Method worked: " + (after - before) + " nano seconds");
        return returnValue;       
    }
}

informations générales sur Spring AOP

Spring AOP p >

(je n'ai pas essayé si ce code fonctionne, copiez des éléments collés à partir d'un de mes projets)


0 commentaires

0
votes

Vous parcourez toutes les méthodes en boucle mais vous retournez le bean si la première méthode n'a pas l'annotation Timing :

for (Method method : methods) {
    if (method.isAnnotationPresent(Timing.class)) {
        Object proxy = ...
         return proxy;
     } else {
         return bean;
     }

Cela signifie que vous ne créez votre proxy personnalisé que si la première méthode que vous trouvez comme annotation.

Vous pouvez vous débarrasser de la clause else et laisser votre return bean après la boucle for gère le cas où aucune méthode n'a l'annotation.


0 commentaires

1
votes

Tout d'abord, une telle fonctionnalité existe déjà. Spring Boot s'intègre au framework Micrometer qui permet ce type de comportement (Spring Boot 1.x utilise des métriques dropwizard avec un support optionnel de backport micromètre qui autorise ce style déclaratif d'annotations).

Voici un chapitre pertinent de la documentation sur les micromètres.

De loin, c'est la meilleure option que je connaisse, mais si vous préférez toujours faire le vôtre (la raison peut être que tous ces cadres de mesure maintiennent un modèle mathématique (avec une fenêtre glissante et tout) autour des métriques, et si vous le souhaitez quelque chose qui ressemble plus au profilage à des fins de débogage ou autre, alors vous pourriez envisager de faire vos propres trucs).

Maintenant, un mot sur Spring AOP suggéré par d'autres réponses. Je pense (et ce n'est que mon avis) que l'utilisation de post-processeurs de haricots a son avantage sur AOP dans ce cas. Tout d'abord, vous n'utilisez peut-être pas du tout le ressort AOP, uniquement un ressort ordinaire. La deuxième raison d'opter pour ce style d'implémentation est la performance, AOP ajoute pas mal d'appels à la pile. L'avantage évident d'AOP est la simplicité de mise en œuvre.

Alors, supposons que vous vouliez une méthode BPP:

Je pense que tout d'abord vous devriez vérifier que le Bean Post Processor est "reconnu" au printemps lors du démarrage de l'application.

Pour vérifier cela, vous pouvez créer un consructeur sans argument dans BPP et y imprimer quelque chose comme "Hello from BPP" ou utiliser le débogueur.

Maintenant, concernant l'implémentation suggérée: Vous devez parcourir les méthodes et créer un proxy une seule fois. Il ne sert à rien de créer un proxy sur proxy sur proxy ... Le code présenté est donc faux.

Je pense que vous devriez parcourir les méthodes, préparer une liste de méthodes et mémoriser cet ensemble, puis créer un proxy qui aura une méthode invoke qui vérifiera si la méthode est dans l'ensemble des méthodes et si c'est le cas, faites la magie du proxy, sinon déléguez simplement l'appel au bean sous-jacent.

Deux choses que vous devez garder à l'esprit lorsque vous procédez de cette façon:

  • Le proxy ne fonctionnera pas avec de vraies classes, uniquement avec l'interface. Si vous avez une classe et que vous ne travaillez pas avec l'interface, vous devrez jouer avec CGLIB

  • D'autres postprocesseurs de bean peuvent également envelopper votre bean dans une sorte de proxy, par exemple, que se passe-t-il si vous mesurez une méthode annotée avec @Transactional ?


0 commentaires