Зачем каждый раз вызывать простой метод в сервисе SpringMVC, который работает медленнее, чем статический метод? - PullRequest
1 голос
/ 18 мая 2019

Это слишком много миллисекунд, чтобы ввести метод и выйти из него в сервисе 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 - это тот же экземпляр. Я не хочу изменять все методы статическим методом, потому что некоторый код должен вызываться нестатическим способом.

1 Ответ

1 голос
/ 19 мая 2019

Вероятно, что аспект перехватывает вызов нестатического метода обслуживания.

Есть несколько способов профилировать выполнение и найти горячую точку для вашего вызова службы. Для репликации этой проблемы я использовал тестовый профилировщик NetBeans.

Сначала я создал статические (компонентные) и нестатические службы:

@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 Test File

Затем из выпадающего списка ▶ Profile я выбрал вариант Methods:

Profile Dropdown

Наконец я нажал кнопку ▶ Profile, чтобы профилировать тесты. Я получил такой результат, который показывает, что вызов внедренного сервиса только на 50% дороже, чем вызов статического метода:

Profile 1

Но что, если второй метод был перехвачен аспектом (например, @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, "");
    }

}

После повторного запуска тестов я получил этот результат профилирования на этот раз:

Profile 2

Как видно, транзакционный аспект сделал вызов в DemoService.queryByIdFromCache примерно в 14,5 (10,2 / 0,708) раз медленнее.

Чтобы найти основную причину замедления в вашем методе обслуживания, я предлагаю вам настроить аналогичный тест и профилировать его с помощью NetBeans Profiler (или чего-то подобного).

...