我的BeanPostProcessor在Spring Boot中不起作用

时间:2019-06-18 14:14:26

标签: java spring spring-boot spring-mvc

我编写了BeanPostProcessor,以便所有标有 @Timing 批注的方法在控制台中显示其执行时间。

我使用Spring Boot。

我的 BeanPostProcessor 看起来像这样:

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

    }

这是我的注释 @Timing

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

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

我在dao类中的一种方法上声明了此批注:

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

启动应用程序时,没有问题,但是执行请求时,控制台中什么都看不到。看来BeanPostProcessor本身编写正确。我找不到错误所在。

我还想知道如何将有关方法执行时间的信息传输到json或某些List(不重要)中的前端。

3 个答案:

答案 0 :(得分:1)

我通常为此使用方面

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

定时

package some.thing;

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


}

依赖性:

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

关于春季AOP的一般信息

Spring AOP

(我没有尝试过此代码是否有效,请从我的项目中复制粘贴的内容)

答案 1 :(得分:1)

首先,这种功能已经存在。 Spring Boot与允许这种行为的Micrometer框架集成(Spring Boot 1.x使用dropwizard度量标准以及可选的micrometer反向端口支持都允许这种声明式的注释)。

https://getbootstrap.com/docs/4.1/utilities/spacing/#notation是千分尺文档的相关章节。

到目前为止,我知道这是最好的选择,但是,如果您仍然喜欢自己动手(原因可能是所有这些计量框架都围绕指标维护了一些数学模型(带有滑动窗口和所有内容),并且如果您愿意某些东西更类似于出于调试目的的概要分析之类的东西,那么您可以考虑自己做一些事情。

现在,由其他答案提出有关Spring AOP的信息。我(这只是我的看法)认为,在这种情况下,使用bean后处理器比AOP具有优势。 首先,也许您根本不使用spring AOP,而仅使用普通spring。 选择这种实现方式的第二个原因是性能,AOP向Stack添加了很多调用。 AOP的明显优势是实现的简单性。

因此,假设您确实要使用BPP方式:

我认为首先,您应该检查Bean Post Processor在应用程序启动期间是否在春季之前被“识别”。

为了进行检查,您可以在BPP中创建一个无参构造器,并在其中打印“ Hello from BPP”之类的内容,或使用调试器。

现在,关于建议的实现: 您只需遍历这些方法并仅创建一次代理。没有必要在代理之上创建代理。...因此,所提供的代码是错误的。

我认为您应该遍历方法,准备方法列表并记住该集合,然后创建一个具有invoke方法的代理,该代理将检查该方法是否在方法集中。做代理魔术,否则只需将调用委派给基础bean。

当您采用这种方式时,应牢记两件事:

  • 代理不适用于真正的类,仅适用于接口。如果您有一个课程并且不能使用该界面,则需要弄弄CGLIB

  • 其他bean后处理器也可以将您的bean包装在某种代理中,例如,如果测量用@Transactional注释的方法怎么办?

答案 2 :(得分:0)

您正在遍历所有方法,但是如果第一个方法没有Timing批注,则会返回Bean:

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

这意味着您只会在找到第一个方法作为注释时才创建自​​定义代理。

您可以摆脱else子句,并在for循环之后让return bean处理没有方法带有注释的情况。