如何统计一个Bean中的方法调用次数

典型回答 通过AOP即可实现,通过AOP对Bean进行代理,在每次执行方法前或者后进行几次计数统计。这个主要就是考虑好如何避免并发情况下不准,以及如何使用AOP实现代理。 主要的代码如下: 首先我们先自定义一个注解,有了这个注解之后,我们可以在想要统计的方法上加上这个注解: 1 2 3 4 5 6 7 8 9 10 11 12 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author Hollis **/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MethodCallCount { } 接下来定义一个切面,来对这个注解进行增强处理: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Aspect @Component public class MethodCallCounterAspect { private Map<String, AtomicInteger> methodCallCountMap = new ConcurrentHashMap<>(); @Around("@annotation(com.hollis.chuang.java.bagu.MethodCallCount)") public Object facade(ProceedingJoinPoint pjp) throws Exception { Method method = ((MethodSignature) pjp.getSignature()).getMethod(); String methodName = method.getName(); try{ return pjp.proceed(); } catch (Throwable e) { //异常处理 } finally{ //计数+1 AtomicInteger counter = methodCallCountMap.computeIfAbsent(methodName, k -> new AtomicInteger(0)); counter.incrementAndGet(); } } public int getMethodCallCount(String methodName) { AtomicInteger counter = methodCallCountMap.get(methodName); return (counter != null) ? counter.get() : 0; } } 有了以上注解和切面后,只需要在我们想要统计的方法上使用该注解就行了: ...

March 22, 2026 · 1 min · santu

Spring中shutdownhook作用是什么?

典型回答 在Spring框架中,Shutdown Hook(关闭钩子)是一种机制,用于在应用程序关闭时执行一些清理操作。 Spring会向JVM注册一个shutdown hook,在接收到关闭通知的时候,进行bean的销毁,容器的销毁处理等操作。 在Spring框架中,可以使用使用AbstractApplicationContext类或其子类来注册Shutdown Hook。这些类提供了一个registerShutdownHook()方法,用于将Shutdown Hook与应用程序上下文关联起来。 很多中间件的优雅上下线的功能(优雅停机),都是基于Spring的shutdown hook的机制实现的,比如Dubbo的优雅下线。 ✅什么是Dubbo的优雅停机,怎么实现的? 还有我们经常在Spring中使用的以下两种方式,其实都是基于shutdown hook实现的。如: 1、实现DisposableBean接口,实现destroy方法: 1 2 3 4 5 6 7 8 9 10 @Slf4j @Component public class HollisShutdownHook implements DisposableBean { @Override public void destroy() throws Exception { // 清理资源 } } 2、使用@PreDestroy注解 1 2 3 4 5 6 7 8 9 10 11 12 @Service public class HollisBean { @PreDestroy public void cleanup() { // 执行清理逻辑 System.out.println("Performing cleanup before bean destruction..."); // 关闭资源、释放连接等 // ... } } 当然,我们也可以借助Spring的事件机制,来自己注册一个hook,如下: ...

March 22, 2026 · 1 min · santu

SpringBoot和Spring的区别是什么?

典型回答 Spring是一个非常强大的企业级Java开发框架(Java的腾飞他居功至伟),提供了一系列模块来支持不同的应用需求,如依赖注入、面向切面编程、事务管理、Web应用程序开发等。而SpringBoot的出现,主要是起到了简化Spring应用程序的开发和部署,特别是用于构建微服务和快速开发的应用程序。 相比于Spring,SpringBoot主要在这几个方面来提升了我们使用Spring的效率,降低开发成本: 1、自动配置:Spring Boot通过Auto-Configuration来减少开发人员的配置工作。我们可以通过依赖一个starter就把一坨东西全部都依赖进来,使开发人员可以更专注于业务逻辑而不是配置。 ✅Springboot是如何实现自动配置的? 2、内嵌Web服务器:Spring Boot内置了常见的Web服务器(如Tomcat、Jetty),这意味着您可以轻松创建可运行的独立应用程序,而无需外部Web服务器。 ✅SpringBoot是如何实现main方法启动Web项目的? 3、约定大于配置:SpringBoot中有很多约定大于配置的思想的体现,通过一种约定的方式,来降低开发人员的配置工作。如他默认读取spring.factories来加载Starter、读取application.properties或application.yml文件来进行属性配置等。

March 22, 2026 · 1 min · santu

Spring的AOP在什么场景下会失效?

典型回答 ✅介绍一下Spring的AOP 首先,Spring的AOP其实是通过动态代理实现的,所以,想要让AOP生效,前提必须是动态代理生效,并且可以调用到代理对象的方法。 什么情况下会不走代理对象的调用呢? 首先就是类内部的调用,比如一些私有方法调用,内部类调用,以及同一个类中方法的自调用等。如: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 //1 public class MyService { public void doSomething() { doInternal(); // 自调用方法 } public void doInternal() { System.out.println("Doing internal work..."); } } //2 public class MyService { public void doSomething() { doInternal(); // 自调用私有方法 } private void doInternal() { System.out.println("Doing internal work..."); } } //3 public class OuterClass { private class InnerClass { public void doSomething() { System.out.println("Doing something in inner class..."); } } public void invokeInnerClassMethod() { InnerClass innerClass = new InnerClass(); innerClass.doSomething(); // 调用内部类方法 } } 以上,都是因为在对象内部直接调用其他方法,就会用原始对象直接调用了,不会调用到代理对象,所以代理会失效。 ...

March 22, 2026 · 1 min · santu

在Spring中如何使用Spring Event做事件驱动

典型回答 Spring Event是Spring框架中的一种事件机制,它允许不同组件之间通过事件的方式进行通信。Spring框架中的事件机制建立在观察者模式的基础上,允许应用程序中的组件注册监听器来监听特定类型的事件,并在事件发生时执行相应的操作。 Spring Event的使用需要定义以下三个内容: 事件(Event):事件是一个普通的Java对象,用于封装关于事件发生的信息。通常,事件类会包含一些数据字段,以便监听器能够获取事件的相关信息。 事件发布者(Event Publisher):事件发布者是负责触发事件并通知所有注册的监听器的组件。 事件监听器(Event Listener):事件监听器是负责响应特定类型事件的组件。它们实现了一个接口或者使用注解来标识自己是一个事件监听器,并定义了在事件发生时需要执行的逻辑。 实现一个Spring Event 假设我们在用户注册成功之后,需要发送一条欢迎短信,那么就可以通过事件来实现。 首先定义一个Event,需要继承ApplicationEvent类。 1 2 3 4 5 6 public class RegisterSuccessEvent extends ApplicationEvent { public RegisterSuccessEvent(RegisterInfo registerInfo) { super(registerInfo); } } 在他的构造方法中,可以定义一个具体的事件内容。 然后再定义一个事件的监听者: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /** * 案件中心内部事件监听器 * * @author Hollis */ @Component public class RegisterEventListener { @EventListener(RegisterSuccessEvent.class) public void onApplicationEvent(RegisterSuccessEvent event) { RegisterInfo registerInfo = (RegisterInfo) event.getSource(); //执行发送欢迎短信的逻辑 } } 以上这个监听器,在监听到一个RegisterSuccessEvent事件被发出来之后,会调用onApplicationEvent方法,我们就可以在这个方法中实现我们的发送欢迎短信的业务逻辑。 ...

March 22, 2026 · 1 min · santu

Spring中的事务事件如何使用?

典型回答 @TransactionalEventListener 是 Spring Framework 提供的一个注解,用于处理事务事件。 它可以在事务提交前后(或回滚前后)触发事件监听器,以执行一些特定的操作。这个注解的使用场景是在需要基于事务状态执行后处理逻辑时非常有用。 Spring事件机制见: ✅在Spring中如何使用Spring Event做事件驱动 上面链接介绍的Spring Event的使用方式是没有考虑事务的,如果要考虑在事务的过程中发送和处理事件,则可以用@TransactionalEventListener 如以下是一个简单的例子: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Service public class UserService { @Autowired private ApplicationEventPublisher eventPublisher; @Transactional public void registerUser(User user) { // 用户注册逻辑 // 发布用户注册事件 UserRegistrationEvent registrationEvent = new UserRegistrationEvent(user); eventPublisher.publishEvent(registrationEvent); } } @Component public class UserRegistrationEventListener { @TransactionalEventListener public void handleUserRegistrationEvent(UserRegistrationEvent event) { // 事务成功提交后执行的逻辑 sendWelcomeEmail(event.getUser()); } private void sendWelcomeEmail(User user) { // 发送欢迎邮件 } } 在上面的示例中,UserRegistrationEvent 事件在用户注册成功后发布,然后 UserRegistrationEventListener 中的 handleUserRegistrationEvent 方法在事务成功提交后触发,发送欢迎邮件。 ...

March 22, 2026 · 1 min · santu

为什么不建议直接使用Spring的@Async

典型回答 @Async中关于线程池的使用部分在AsyncExecutionInterceptor中,在这个类中有一个getDefaultExecutor方法, 当我们没有做过自定义线程池的时候,就会用SimpleAsyncTaskExecutor这个线程池。 1 2 3 4 5 @Override protected Executor getDefaultExecutor(BeanFactory beanFactory) { Executor defaultExecutor = super.getDefaultExecutor(beanFactory); return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor()); } 但是这里需要注意的是,他并不是无脑的的直接创建一个新的,这部分在扩展知识中讲 SimpleAsyncTaskExecutor这玩意坑很大,其实他并不是真的线程池,它是不会重用线程的,每次调用都会创建一个新的线程,也没有最大线程数设置。并发大的时候会产生严重的性能问题。 他的doExecute核心逻辑如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 /** * Template method for the actual execution of a task. * <p>The default implementation creates a new Thread and starts it. * @param task the Runnable to execute * @see #setThreadFactory * @see #createThread * @see java.lang.Thread#start() */ protected void doExecute(Runnable task) { Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task)); thread.start(); } 所以,我们应该自定义线程池来配合@Async使用,而不是直接就用默认的。 ...

March 22, 2026 · 3 min · santu

什么是Spring的三级缓存

典型回答 在Spring的BeanFactory体系中,BeanFactory是Spring IoC容器的基础接口,其DefaultSingletonBeanRegistry类实现了BeanFactory接口,并且维护了三级缓存: 1 2 3 4 5 6 7 8 public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { //一级缓存,保存完成的Bean对象 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //三级缓存,保存单例Bean的创建工厂 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); //二级缓存,存储"半成品"的Bean对象 private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); } **singletonObjects是一级缓存,存储的是完整创建好的单例bean对象。**在创建一个单例bean时,会先从singletonObjects中尝试获取该bean的实例,如果能够获取到,则直接返回该实例,否则继续创建该bean。 **earlySingletonObjects是二级缓存,存储的是尚未完全创建好的单例bean对象。**在创建单例bean时,如果发现该bean存在循环依赖,则会先创建该bean的"半成品"对象,并将"半成品"对象存储到earlySingletonObjects中。当循环依赖的bean创建完成后,Spring会将完整的bean实例对象存储到singletonObjects中,并将earlySingletonObjects中存储的代理对象替换为完整的bean实例对象。这样可以保证单例bean的创建过程不会出现循环依赖问题。 **singletonFactories是三级缓存,存储的是单例bean的创建工厂。**当一个单例bean被创建时,Spring会先将该bean的创建工厂存储到singletonFactories中,然后再执行创建工厂的getObject()方法,生成该bean的实例对象。在该bean被其他bean引用时,Spring会从singletonFactories中获取该bean的创建工厂,创建出该bean的实例对象,并将该bean的实例对象存储到singletonObjects中。 扩展知识 三级缓存与循环依赖 ✅三级缓存是如何解决循环依赖的问题的?

March 22, 2026 · 1 min · santu

SpringBoot如何做优雅停机?

典型回答 在Web应用开发中,确保应用可以平稳可靠的关闭是至关重要的。在我们常用的Spring Boot中其实提供了内置功能来优雅地处理应用程序的关闭的能力。 先说一下啥是优雅停机,其实他指的是以受控方式终止应用程序的过程,允许它完成任何正在进行的任务,释放资源,并确保数据的完整性。与突然终止应用程序不同,优雅停机确保所有进程都得到优雅停止,以防止潜在的数据损坏或丢失。 从Spring Boot 2.3开始,SpringBoot内置了优雅停机的功能。想要启用优雅停机也非常简单,你只需在你的application.properties文件中添加一行代码: 1 server.shutdown=graceful 通过这个设置,当你停止服务器时,它将不再接受新的请求。并且服务器也不会立即关闭,而是等待正在进行的请求处理完。 这个等待的时间我们是可以自定义的: 1 spring.lifecycle.timeout-per-shutdown-phase=2m 默认的等待时长是30秒,我们通过以上配置可以将这个等待时长延长直2分钟。 扩展知识 Spring Boot Actuator Shutdown Endpoint 想要在Spring Boot Actuator中启用优雅停机,需要做如下配置。 首先增加Maven依赖: 1 2 3 4 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 然后增加配置项, 1 2 management.endpoints.web.exposure.include=* management.endpoint.shutdown.enabled=true 要优雅停机应用,可以使用HTTP POST请求来调用关闭端点。例如,可以使用curl命令或工具来发送POST请求: 1 curl -X POST http://localhost:8080/actuator/shutdown 当你发送POST请求到/actuator/shutdown时,应用将接收到关闭命令并开始进行优雅停机。应用会等待一段时间以完成正在进行的请求处理,然后关闭。

March 22, 2026 · 1 min · santu

@Lazy注解能解决循环依赖吗?

典型回答 答案是:能,但是要看情况,他可以用来解决构造器注入这种方式下的循环依赖。 循环依赖的问题,以及如何基于三级缓存解决循环依赖的问题,可以去看以下几篇,这里不再赘述了。 ✅什么是Spring的循环依赖问题? ✅三级缓存是如何解决循环依赖的问题的? ✅Spring解决循环依赖一定需要三级缓存吗? 同时,我们也介绍过,Spring利用三级缓存是无法解决构造器注入这种循环依赖的。那么,这种循环依赖就束手无策了吗? 其实并不是,这种循环依赖可以借助Spring的@Lazy来解决。 **@Lazy 是Spring框架中的一个注解,用于延迟一个bean的初始化,直到它第一次被使用。**在默认情况下,Spring容器会在启动时创建并初始化所有的单例bean。这意味着,即使某个bean直到很晚才被使用,或者可能根本不被使用,它也会在应用启动时被创建。@Lazy 注解就是用来改变这种行为的。 也就是说,当我们使用 @Lazy 注解时,Spring容器会在需要该bean的时候才创建它,而不是在启动时。这意味着如果两个bean互相依赖,可以通过延迟其中一个bean的初始化来打破依赖循环。 假设我们有两个类 ClassA 和 ClassB,它们之间存在循环依赖。我们可以使用 @Lazy 来解决这个问题: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Component public class ClassA { private final ClassB classB; @Autowired public ClassA(@Lazy ClassB classB) { this.classB = classB; } // ... } @Component public class ClassB { private final ClassA classA; @Autowired public ClassB(ClassA classA) { this.classA = classA; } // ... } 在这个例子中,ClassA 的构造器依赖 ClassB,但我们使用了 @Lazy 注解来标记这个依赖。这意味着 ClassB 的实例会在首次被实际使用时才创建,而不是在创建 ClassA 的实例时。这样,Spring容器可以先创建 ClassA 的实例(此时不需要立即创建 ClassB),然后创建 ClassB 的实例,最后解决 ClassA 对 ClassB 的依赖。 ...

March 22, 2026 · 1 min · santu

留言给博主