为啥每次调用 Spring MVC 服务中的简单方法都比静态方法慢?
Posted
技术标签:
【中文标题】为啥每次调用 Spring MVC 服务中的简单方法都比静态方法慢?【英文标题】:Why calling a simple method in SpringMVC service super slow than static method every time?为什么每次调用 Spring MVC 服务中的简单方法都比静态方法慢? 【发布时间】:2019-05-18 09:00:05 【问题描述】:在 SpringMVC 服务中进入一个方法和退出该方法花费了太多毫秒。但是,如果我将方法修改为静态,则只需 1 毫秒。 我们的项目是一个SpringMVC-Mybatis项目。我发现控制器进入一个服务方法和退出该方法大约需要70毫秒。 该方法从具有 128 个键的静态映射中获取值。
@Controller
public class OrdersController extends BaseAction
@Autowired(required=false)
private CompanyService<Company> companyService;
public void validateInfo()
Company company = CompanyService.queryByIdFromCache(account);// cost about 70 milliseconds, but if the method is static it is 1 milliseconds.
@Service("companyService")
public class CompanyService<T> extends BaseService<T>
private static ConcurrentMap<Integer, Company>cache = new ConcurrentHashMap<>(); //map with 128 keys
public Company queryByIdFromCache(Integer id)
return cache.get(id);
我希望该方法在 2 毫秒内完成。 该服务在单例模式下工作。和 companyService 是同一个实例。 我不想把所有的方法都改成静态方法,因为有些代码必须用非静态的方式调用。
【问题讨论】:
一种可能的解释是 Spring Bean 被创建为代理。因此,他们不能代理static
方法。这意味着如果方法是static
,它不会被代理,因此不必被拦截(这会花费额外的时间和资源)。您可以尝试在方法内设置断点,并使用AopUtils.isAopProxy()
或类似方法来验证这一点。
你是如何开始测量的?
@Eugene JIT
与确定他的OrdersController
是否被 Spring 代理有什么关系?
@user991710,我会试试的。谢谢。
@Eugene:对不起,我已经删除了日志。
【参考方案1】:
很可能是切面拦截了对非静态服务方法的调用。
有几种方法可以分析执行情况并找到服务调用的热点。我使用 NetBeans 的 Test Profiler 重现了这个问题。
首先,我创建了静态(组件)和非静态服务:
@Service
public class DemoService
private static final ConcurrentMap<Integer, String> CACHE = new ConcurrentHashMap<>();
public DemoService()
for (int i = 0; i < 128; i++)
CACHE.put(i, String.valueOf(i));
public String queryByIdFromCache(Integer id)
return CACHE.getOrDefault(id, "");
public class DemoStaticService
private static final ConcurrentMap<Integer, String> CACHE = new ConcurrentHashMap<>();
static
for (int i = 0; i < 128; i++)
CACHE.put(i, String.valueOf(i));
public static String queryByIdFromCache(Integer id)
return CACHE.getOrDefault(id, "");
然后我创建了一个控制器,其中包含两个操作,一个调用非静态注入服务,另一个调用使用静态方法的服务:
@RestController
@RequestMapping(path = "/demo")
public class DemoController
@Autowired
private DemoService demoService;
@GetMapping(path = "/demo")
public String callService(@RequestParam Integer id)
return demoService.queryByIdFromCache(id);
@GetMapping(path = "/static-demo")
public String callStaticService(@RequestParam Integer id)
return DemoStaticService.queryByIdFromCache(id);
之后,我编写了两个单元测试来帮助我分析服务方法:
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoControllerTest
@Autowired
private DemoController demoController;
@Before
public void before()
demoController.callService(1);
demoController.callStaticService(1);
@Test
public void testCallService()
for (int id = 0; id < 128; id++)
demoController.callService(id);
@Test
public void testCallStaticService()
for (int id = 0; id < 128; id++)
demoController.callStaticService(id);
打开测试文件后,我选择了Profile -> Profile Test File
菜单项:
然后从▶ Profile
下拉菜单中,我选择了Methods
选项:
最后我点击了▶ Profile
按钮来分析测试。我得到了这个结果,这表明对注入服务的调用仅比对静态方法的调用贵 50%:
但是如果第二种方法被切面拦截了怎么办(例如@Transactional
)?
为了测试这一点,我更新了 DemoService
并使其方法具有事务性
@Service
public class DemoService
private static final ConcurrentMap<Integer, String> CACHE = new ConcurrentHashMap<>();
public DemoService()
for (int i = 0; i < 128; i++)
CACHE.put(i, String.valueOf(i));
@Transactional
public String queryByIdFromCache(Integer id)
return CACHE.getOrDefault(id, "");
重新运行测试后,我这次得到了这个分析结果:
可以看出,事务方面对DemoService.queryByIdFromCache
的调用慢了大约 14.5 (10.2/0.708) 倍。
要找出服务方法变慢的根本原因,我建议您设置一个类似的测试并使用 NetBeans Profiler(或类似的工具)对其进行分析。
【讨论】:
非常感谢。我已经设置log4j调试级别来跟踪,发现原因是aop在真正进入方法之前拦截开始事务并在返回后提交事务。因为性能对我们来说非常关键,我决定直接从mysql-connector调用低级api- java.以上是关于为啥每次调用 Spring MVC 服务中的简单方法都比静态方法慢?的主要内容,如果未能解决你的问题,请参考以下文章