为啥每次调用 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 -&gt; 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 服务中的简单方法都比静态方法慢?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Mvc总结

从 Spring MVC 控制器访问服务层

spring mvc中的单元测试

为啥自定的springmvc converter不起作用吗

为啥@JavaConfig 在 Spring MVC 中不起作用?

1.2 啥是Spring,为啥它与微服务有关