Предисловие
В этой статье будут представлены несколько методов получения объектов запроса в веб -системе, разработанной Spring MVC, и обсудить его безопасность потока. Я не скажу многое ниже, давайте посмотрим на подробное введение вместе.
Обзор
При разработке веб-системы с использованием Spring MVC вам часто приходится использовать объект запроса при обработке запросов, таких как получение клиентского IP-адреса, запрошенный URL-адрес, атрибуты в заголовке (например, файлы cookie, информация о авторизации), данные в теле и т. Д. Поскольку в Spring MVC, контроллер, обслуживание и другие объекты, которые обрабатывают запросы, являются Singletons, наиболее важные задачи. Можно ли гарантировать большое количество одновременных запросов, можно ли гарантировать, что различные объекты запроса используются в разных запросах/потоках?
Здесь есть еще один вопрос: где я использую объект запроса «при обработке запроса», упомянутого ранее? Учитывая, что существуют небольшие различия в методах получения объектов запроса, они могут быть примерно разделены на две категории:
1) Используйте объекты запроса в весенних бобах: включайте оба бобы MVC, такие как контроллер, сервис, репозиторий и обычные пружинные бобы, такие как компонент. Для удобства объяснения бобы весной в следующем тексте все называются бобами для короткого.
2) Используйте объекты запроса в не-мальчиках: например, в методах обычных объектов Java или в статических методах классов.
Кроме того, в этой статье обсуждается объект запроса, представляющий запрос, но используемый метод также применим к объекту ответа, InputStream/Reader, OutputStream/Writer и т. Д.; где inputStream/Reader может читать данные в запросе, а OutputStream/Writer может записать данные в ответ.
Наконец, метод получения объекта запроса также связан с версией Spring и MVC; Эта статья обсуждается на основе Spring 4, и выполненные эксперименты используются версию 4.1.1.
Как проверить безопасность потока
Поскольку проблемы безопасности потока объекта запроса требуют особого внимания, чтобы облегчить обсуждение ниже, давайте сначала объясним, как проверить, является ли объект запроса безопасным.
Основная идея тестирования состоит в том, чтобы моделировать большое количество параллельных запросов на клиенте, а затем определить, используются ли запросы на сервере.
Наиболее интуитивно понятный способ определить, является ли объект запроса одинаковым - это распечатать адрес объекта запроса. Если это то же самое, это означает, что используется тот же объект. Тем не менее, почти во всех реализациях веб -сервера используются пулы потоков, что приводит к двум запросам, которые поступают последовательно, которые могут обрабатываться одним и тем же потоком: после обработки предыдущего запроса пул потоков восстанавливает поток и переназначает поток в последующий запрос. В том же потоке используемый объект запроса, вероятно, будет одинаковым (адрес такой же, атрибуты разные). Следовательно, даже для безопасных потоков методы, адреса объекта запроса, используемые различными запросами, могут быть одинаковыми.
Чтобы избежать этой проблемы, один из методов состоит в том, чтобы позволить потоке спать в течение нескольких секунд во время процесса обработки запросов, что может заставить каждый поток работать достаточно долго, чтобы избежать того же потока, выделяющегося по разным запросам; Другим методом является использование других атрибутов запроса (например, параметров, заголовка, тела и т. Д.) В качестве основы для того, является ли запрос безопасным потоком, потому что даже если разные запросы используют один и тот же поток один за другим (адрес объекта запроса одинаков), если объект запроса строится дважды, используя разные атрибуты, использование объекта запроса является потоком. В этой статье используется второй метод для тестирования.
Код тестирования клиента выглядит следующим образом (создайте 1000 потоков для отправки запросов отдельно):
открытый тест класса {public static void main (string [] args) бросает исключение {string prefix = uuid.randomuuid (). toString (). rylaceall ("-", ") +" :: "; for (int i = 0; i <1000; i ++) {final String value = prefix+i; new Thread () {@Override public void run () {try {useableHttpClient httpClient = httpclients.createdefault (); Httpget httpget = new httpget ("http: // localhost: 8080/test? Key =" + value); httpclient.execute (httpget); httpclient.close (); } catch (ioException e) {e.printstackTrace (); } } } } } }.начинать(); }}}Код контроллера на сервере выглядит следующим образом (код для получения объекта запроса временно опущен):
@Controllerpublic class testcontroller {// сохранить существующие параметры, чтобы определить, дублируются ли параметры, тем самым определяя, является ли поток безопасным общедоступным статическим набором <string> set = new Hashset <> (); @Requestmapping ("/test") public void test () бросает прерывание ………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. ………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. ………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. ………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. «/T появляется неоднократно, запрос параллелизма небезопасна!»); } else {System.out.println (value); set.add (значение); } // Программа моделирования была выполнена в течение определенного периода времени, Thread.sleep (1000); }}Если объект запроса безопасен для потока, результат печати на сервере заключается в следующем:
Если есть проблема с безопасностью потока, результат печати на сервере может выглядеть так:
Если нет особого описания, тестовый код будет опущен из кодов позже в этой статье.
Метод 1: Добавьте параметры в контроллер
Пример кода
Этот метод является самым простым в реализации, и напрямую введите код контроллера:
@Controllerpublic class testcontroller {@requestmapping ("/test") public void test (httpservlectrequest) Throws rewruptedException {// Программа моделирования была выполнена в течение периода времени. Sleep (1000); }}Принцип этого метода заключается в том, что когда метод контроллера начнет обрабатывать запрос, пружина назначит объект запроса параметрам метода. В дополнение к объекту запроса, существует много параметров, которые можно получить с помощью этого метода. Для получения подробной информации, пожалуйста, см.
После получения объекта запроса в контроллере, если вы хотите использовать объект запроса другими методами (например, методы обслуживания, методы класса инструментов и т. Д.), Вам необходимо передать объект запроса в качестве параметра при вызове этих методов.
Безопасность нити
Результаты теста: безопасность потока
Анализ: В настоящее время объект запроса является параметром метода, который эквивалентен локальной переменной и, несомненно, является резервной защитой.
Плюсы и минусы
Основным недостатком этого метода является то, что объект запроса слишком избыточно для написания, что в основном отражается в двух точках:
1) Если объект запроса требуется в нескольких методах контроллера, то параметр запроса должен быть добавлен к каждому методу.
2) Приобретение объекта запроса может запуститься только с контроллера. Если место, где используется объект запроса, находится в более глубоком месте уровня вызова функции, то все методы во всей цепочке вызовов необходимо добавить параметр запроса.
Фактически, в течение всего процесса обработки запросов объект запроса проходит через весь запрос; То есть, за исключением особых случаев, таких как таймеры, объект запроса эквивалентен глобальной переменной внутри потока. Этот метод эквивалентен передаче этой глобальной переменной вокруг.
Метод 2: Автоматическая инъекция
Пример кода
Первый загрузите код:
@Controllerpublic class testcontroller {@autowired private httpservletrequest запрос; // AutoWailired запрос @Requestmapping ("/test") public void test () Throws refruptEdException {// Программа моделирования была выполнена в течение периода времени. Sleep (1000); }} Безопасность нити
Результаты теста: безопасность потока
Анализ: Весной объем контроллера является Singleton (Singleton), что означает, что во всей веб -системе есть только один тестовый контроллер; Но введенный запрос безопасен для потока, потому что:
Таким образом, когда инициализируется фасоль (TestController в этом примере), Spring не вводит не объект запроса, а прокси; Когда боб необходимо использовать объект запроса, объект запроса получается через прокси.
Ниже приведено описание этой реализации через конкретный код.
Добавьте точки останова в приведенном выше коде и просмотрите свойства объекта запроса, как показано на рисунке ниже:
Как видно на рисунке, запрос на самом деле является прокси: реализация прокси показана во внутреннем классе AutoWireUtils
ObjectFactoryDelegatingInvocationHandler: /*** Отражающий vlocationhandler для ленивого доступа к текущему целевому объекту. */@PuppressWarnings ("Serial") Частный статический класс объектно -функциорирование, сфереинвенсиокации, реализует vocationhandler, serializable {private final finalfactory <?> ObjectFactory; public objectFactoryDelegatingInvocationHandler (ObjectFactory <?> ObjectFactory) {this.ObjectFactory = objectFactory; } @Override public Object voloke (объект прокси, метод метода, Object [] args) бросает Throwable {// ... опустить нерелевантный код try {return method.invoke (this.objectfactory.getobject (), args); // CARE CANCE CANCE CANCE AGGET AGGET} (vococationTargetException ex) {throw ex.getTargetException (); }}}Другими словами, когда мы называем метод запроса, мы фактически называем метод метода объекта, сгенерированного objectFactory.getObject (); Объект, сгенерированный ObjectFactory.getObject (), является реальным объектом запроса.
Продолжайте наблюдать приведенном выше рисунке и обнаружите, что тип объекта объекта является внутренним классом requestObjectfactory of WebApplicationContextUtils; и код запроса obstobjectfactory заключается в следующем:
/*** Фабрика, которая раскрывает текущий объект запроса по требованию. */ @PuppressWarnings ("Serial") Частный статический класс requestObjectfactory реализует objectFactory <ServletRequest>, serializable {@Override public servletRequest getObject () {return currentRequestattributes (). GetRequest (); } @Override public String toString () {return "current httpservlectrequest"; }}Среди них, чтобы получить объект запроса, вам необходимо вызвать метод CurrentRequestattributes (), чтобы получить объект запроса. Реализация этого метода следующая:
/*** Возвращает текущий экземпляр запроса requestTributes в качестве ServletRequestattributes. */private static servletRequestattributes currentRequestattributes () {requestattributes requestattr = requestContextholder.currentRequestattributes (); if (! (requestAttr exanceof servletrequestattributes)) {бросить новое allodalStateException («Текущий запрос не является запросом сервлета»); } return (servletrequestattributes) requestattr;}Код основного ядра, который генерирует объект RequestAttributes, находится в Class RequestExtexTholder, где соответствующий код выглядит следующим образом (не связанный код в классе опущен):
Public Abstract Class RequestExtholder {public static requestatributes currentRequestTtributes () Throws OldalStateException {requestAttributes attributes = getRequestTtributes (); // нерелевантная логика здесь опущена ...... вернуть атрибуты; } public static requesttributes getRequestattributes () {requestAttributes attributes = requestattributesholder.get (); if (attributes == null) {attributes = inehytableRequestattributeSholder.get (); } возвращать атрибуты; } private static final threadlocal <requestAttributes> requestAttributeSholder = new newThreadlocal <SearchAttributes> ("Атрибуты запроса"); Private Static Final Threadlocal <seekAttributes> inehytableRequestattributeSholder = new newInheritableTreadlocal <requestAttributes> ("Context");}Из этого кода мы видим, что сгенерированный объект requestattributes является локальной переменной потоком (Threadlocal), поэтому объект запроса также является локальной переменной потока; Это обеспечивает безопасность потока объекта запроса.
Плюсы и минусы
Основные преимущества этого метода:
1) Инъекция не ограничивается контроллером: В методе 1 только параметр запроса может быть добавлен в контроллер. Для метода 2 он может быть введен не только в контроллер, но и в любом бобах, включая услуги, репозиторий и обычные бобы.
2) инъекционный объект не ограничивается запросом: в дополнение к инъекции объекта запроса, этот метод также может внедрить другие объекты с объемом в качестве запроса или сеанса, таких как объекты ответа, объекты сеанса и т. Д.; и обеспечить безопасность потока.
3) Уменьшите избыточность кода: просто введите объект запроса в бобы, который требует объекта запроса, и его можно использовать в различных методах бобов, что значительно снижает избыточность кода по сравнению с методом 1.
Однако этот метод также имеет избыточность кода. Рассмотрим этот сценарий: в веб -системе есть много контроллеров, и каждый контроллер использует объект запроса (этот сценарий на самом деле очень часты). В настоящее время вам нужно написать код для запроса много раз; Если вам также необходимо ввести ответ, код будет еще более громоздким. Ниже описывается улучшение метода автоматического впрыска и анализирует его безопасность и преимущества и недостатки.
Метод 3: Автоматическая впрыска в базовый класс
Пример кода
По сравнению с методом 2, поместите инъецированную часть кода в базовый класс.
Код базового класса:
открытый класс BaseController {@autowired защищенный httpservletrequest запрос; }Код контроллера выглядит следующим образом; Вот два полученных класса базового контроллера. Поскольку тестовый код будет отличаться в настоящее время, тестовый код сервера не пропущен; Клиент также должен внести соответствующие изменения (отправьте большое количество параллельных запросов в два URL одновременно).
@Controllerpublic class testcontroller extends basecontroller {// сохранить существующие параметры, чтобы определить, повторяется ли значение параметра, тем самым определяя, является ли поток безопасным общедоступным статическим набором <string> set = new Hashset <> (); @Requestmapping ("/test") public void test () бросает прерывание {строки value = request.getParameter ("key"); // Проверьте безопасность потока if (set.contains (value)) {System.out.println (значение + "/T появляется многократно, запрос параллелизм небезопасен!"); } else {System.out.println (value); set.add (значение); } // Программа моделирования была выполнена в течение периода времени. Sleep (1000); }} @Controllerpublic class test2controller extends basecontroller {@requestmapping ("/test2") public void test2 () бросает прерывание {строковое значение = request.getParameter ("key"); // Судебная безопасность потока (Используйте набор с TestController для судьи) if (testController.set.Contains (value)) {System.out.println (значение + "/T появляется многократно, запрос параллелизм не является безопасным!"); } else {System.out.println (value); Testcontroller.set.add (значение); } // Программа моделирования была выполнена в течение определенного периода времени, Thread.sleep (1000); }} Безопасность нити
Результаты теста: безопасность потока
Анализ: На основании понимания безопасности потока метода 2 легко понять, что метод 3-это защитный поток: при создании различных объектов полученных классов домены в базовом классе (вот введенный запрос) будут занимать различное пространство памяти в разных объектах класса, то есть размещение запроса на инъекцию в базовом классе не влияет на безопасность потока; Результаты теста также доказывают это.
Плюсы и минусы
По сравнению с методом 2, можно избежать повторного введения запросов в разных контроллерах; Однако, учитывая, что Java допускает наследство одного базового класса, если контроллер должен наследовать другие классы, этот метод больше не прост в использовании.
Будь то метод 2 или метод 3, вы можете вводить только запросы в бобы; Если другие методы (такие как статические методы в классе инструментов) должны использовать объекты запроса, вам необходимо передавать параметры запроса при вызове этих методов. Метод 4, представленный ниже, может использоваться непосредственно в статических методах, таких как классы инструментов (конечно, его также можно использовать в различных бобах).
Метод 4: звонить вручную
Пример кода
@Controllerpublic class testcontroller {@requestmapping ("/test") public void test () Throws ErruptExexception {httpservlectrequest request = (((ServletRequestattributes) (requestContextholder.currentRequestttributes ())). GetRequest (); // Программа моделирования была выполнена в течение периода времени. Sleep (1000); }} Безопасность нити
Результаты теста: безопасность потока
Анализ: этот метод аналогичен методу 2 (автоматическое внедрение), за исключением того, что он реализован путем автоматического впрыска в методе 2, и этот метод реализуется с помощью ручного вызова метода. Следовательно, этот метод также безопасен.
Плюсы и минусы
Преимущества: могут быть получены непосредственно в не-пейзажах. Недостатки: если вы используете больше мест, код очень громоздкий; Следовательно, его можно использовать в сочетании с другими методами.
Метод 5: метод @modelattribute
Пример кода
Следующий метод и его варианты (Mutation: Поместите запрос и BindRequest в подклассах) часто можно увидеть в Интернете:
@Controllerpublic class testcontroller {private httpservletrequest запрос; @Modelattribute public void bindrequest (httpservletrequest) {this.request = request; } @RequestMapping ("/test") public void test () Throws TreamptEdException {// Программа моделирования была выполнена в течение периода времени. Sleep (1000); }} Безопасность нити
Результат теста: поток небезопасна
Анализ: Когда аннотация @modelattribute используется для изменения метода в контроллере, его функция заключается в том, что метод будет выполнен до выполнения каждого метода @requestmapping в контроллере. Следовательно, в этом примере функция bindRequest () заключается в назначении значения объекту запроса до выполнения Test (). Хотя запрос параметров в самом bindRequest () является потоковым, поскольку TestController-это Singleton, запрос, как поле TestController, не может гарантировать безопасность потоков.
Суммировать
Подводя итог, добавление параметров (метод 1), автоматическое инъекцию (метод 2 и метод 3) и ручные вызовы (метод 4) в контроллере-все это резьбовые и все могут использоваться для получения объектов запроса. Если объект запроса используется меньше в системе, можно использовать любой метод; Если он используется больше, рекомендуется использовать автоматическую инъекцию (метод 2 и метод 3) для снижения избыточности кода. Если вам необходимо использовать объект запроса в не-Bean, вы можете либо передать его через параметры при вызове верхнего уровня, либо вы можете напрямую получить его с помощью ручных вызовов (метод 4).
Хорошо, вышеупомянутое содержимое этой статьи. Я надеюсь, что содержание этой статьи имеет определенную справочную ценность для каждого обучения или работы. Если у вас есть какие -либо вопросы, вы можете оставить сообщение для общения. Спасибо за поддержку Wulin.com.
Ссылки