머리말
이 기사는 Spring MVC가 개발 한 웹 시스템에서 요청 객체를 얻고 스레드 안전에 대해 논의하는 몇 가지 방법을 소개합니다. 아래에서 많이 말하지 않겠습니다. 자세한 소개를 함께 살펴 보겠습니다.
개요
Spring MVC를 사용하여 웹 시스템을 개발할 때 클라이언트 IP 주소 얻기, 요청 된 URL, 쿠키, 승인 정보 등의 속성, 신체의 데이터 등과 같은 요청을 처리 할 때 요청 객체를 사용해야합니다. Spring MVC에서는 컨트롤러, 서비스 및 기타 대상이 싱글 톤 일 것인지, 요청을 얻는 것이 가장 중요한 문제인지에 대한 질문이 될 것인지에 대한 가장 중요한 문제가 될지 많은 동시 요청이 있습니까? 다른 요청/스레드에 다른 요청 객체가 사용되도록 보장 할 수 있습니까?
여기에 주목해야 할 또 다른 질문이 있습니다. 이전에 언급 한 "요청을 처리 할 때"요청 객체를 어디서 사용합니까? 요청 객체를 얻는 방법에 약간의 차이가 있다는 것을 고려하면 대략 두 가지 범주로 나눌 수 있습니다.
1) 스프링 콩의 요청 개체 사용 : 컨트롤러, 서비스, 저장소 및 구성 요소와 같은 일반 스프링 콩과 같은 MVC 콩을 모두 포함하십시오. 설명의 편의를 위해, 다음 텍스트의 봄의 콩은 모두 짧게 콩이라고합니다.
2) 평범한 Java 객체의 방법 또는 정적 클래스 방법에서와 같은 비 콩에서 요청 객체를 사용하십시오.
또한이 기사는 요청을 나타내는 요청 객체에 대해 논의하지만 사용 된 방법은 응답 객체, 입력 스트림/리더, 출력 스트림/라이터 등에도 적용됩니다. InputStream/Reader가 요청에서 데이터를 읽을 수 있으며 OutputStream/Writer는 응답에 데이터를 쓸 수 있습니다.
마지막으로, 요청 객체를 얻는 방법은 또한 Spring 및 MVC의 버전과 관련이 있습니다. 이 기사는 Spring 4를 기반으로 논의되며 수행 된 실험은 모두 버전 4.1.1을 사용합니다.
스레드 안전을 테스트하는 방법
요청 객체의 스레드 안전 문제는 특별한주의가 필요하므로 아래의 논의를 용이하게하기 위해서는 먼저 요청 객체가 스레드 안전인지 테스트하는 방법을 설명해 봅시다.
테스트의 기본 아이디어는 클라이언트에 대한 많은 동시 요청을 시뮬레이션 한 다음 요청이 서버에서 사용되는지 여부를 결정하는 것입니다.
요청 객체가 동일한 지 여부를 결정하는 가장 직관적 인 방법은 요청 객체의 주소를 인쇄하는 것입니다. 동일하면 동일한 객체가 사용됨을 의미합니다. 그러나 거의 모든 웹 서버 구현에서 스레드 풀이 사용되며, 이는 동일한 스레드로 처리 될 수있는 두 가지 요청으로 이어집니다. 이전 요청이 처리 된 후 스레드 풀은 스레드를 회수하고 스레드를 후속 요청에 다시 설정합니다. 같은 스레드에서 사용 된 요청 객체는 동일 할 수 있습니다 (주소는 동일하고 속성이 다릅니다). 따라서 스레드-안전 메소드의 경우에도 다른 요청에 사용되는 요청 객체 주소가 동일 할 수 있습니다.
이 문제를 피하기 위해 요청 처리 프로세스 중에 스레드가 몇 초 동안 잠을 자도록하는 것이며, 이는 각 스레드가 다른 스레드를 다른 요청에 할당하지 않도록 충분히 오래 작동 할 수 있습니다. 다른 방법은 요청의 다른 속성을 사용하여 요청 객체의 주소가 다른 속성을 사용하여 두 번 구성되는 한, 요청 객체의 주소가 동일하더라도 요청 객체의 사용이 스레드-피세입니다. 이 논문은 테스트를위한 두 번째 방법을 사용합니다.
클라이언트 테스트 코드는 다음과 같습니다 (요청을 별도로 보낼 1000 스레드 생성) :
public class test {public static void main (String [] args)은 예외 {string prefix = uuid.randomuuid (). toString (). replaceall ( "-", "" + "::"; for (int i = 0; i <1000; i ++) {최종 문자열 값 = prefix+i; 새 스레드 () {@override public void run () {try {closeablehttpclient 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 ()가 중단 exception {// …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… ……………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… ……………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… "/t는 반복적으로 나타나고, 요청 동시성은 안전하지 않습니다!"); } else {system.out.println (값); set.add (값); } // 시뮬레이션 프로그램은 일정 시간 동안 실행되었습니다. thread.sleep (1000); }}요청 객체가 스레드-안전한 경우 서버의 인쇄 결과는 다음과 같습니다.
스레드 안전 문제가 있으면 서버의 인쇄 결과가 다음과 같습니다.
특별한 설명이 없으면이 기사의 뒷부분에서 테스트 코드가 생략됩니다.
방법 1 : 컨트롤러에 매개 변수를 추가하십시오
코드 예제
이 방법은 구현하기가 가장 간단하고 컨트롤러 코드를 직접 입력합니다.
@ControllerPublic Class TestController {@requestmapping ( "/test") public void test (httpservletrequest 요청) 중간 exception {// 시뮬레이션 프로그램이 시간 동안 실행되었습니다 .Sleep (1000); }}이 방법의 원칙은 컨트롤러 메소드가 요청을 시작하면 스프링이 요청 객체를 메소드 매개 변수에 할당한다는 것입니다. 요청 객체 외에도이 방법을 통해 얻을 수있는 많은 매개 변수가 있습니다. 자세한 내용은 다음을 참조하십시오
컨트롤러에서 요청 객체를 얻은 후 다른 방법 (예 : 서비스 메소드, 도구 클래스 메소드 등)에서 요청 객체를 사용하려면 이러한 메소드를 호출 할 때 요청 객체를 매개 변수로 전달해야합니다.
실 안전
테스트 결과 : 스레드 안전
분석 : 현재 요청 객체는 메소드 매개 변수로 로컬 변수와 동일하며 의심 할 여지없이 스레드 안전입니다.
장단점
이 방법의 주요 단점은 요청 객체가 쓰기하기에는 너무 중복되며 주로 두 가지 점으로 반영된다는 것입니다.
1) 여러 컨트롤러 메소드에서 요청 객체가 필요한 경우 각 방법에 요청 매개 변수를 추가해야합니다.
2) 요청 객체의 획득은 컨트롤러에서만 시작할 수 있습니다. 요청 객체가 사용되는 장소가 기능 호출 레벨의 더 깊은 장소에있는 경우 전체 통화 체인의 모든 메소드는 요청 매개 변수를 추가해야합니다.
실제로 전체 요청 처리 프로세스 중에 요청 객체는 전체 요청을 통해 실행됩니다. 즉, 타이머와 같은 특수한 경우를 제외하고 요청 객체는 스레드 내부의 전역 변수와 같습니다. 이 방법은이 글로벌 변수를 전달하는 것과 같습니다.
방법 2 : 자동 주입
코드 예제
먼저 코드 업로드 :
@ControllerPublic 클래스 TestController {@autowired private httpservletrequest 요청; // Autowired Request @RequestMapping ( "/test") public void test () Throws InterruptedException {// 시뮬레이션 프로그램이 일정 시간 동안 실행되었습니다. Sleep (1000); }} 실 안전
테스트 결과 : 스레드 안전
분석 : 봄에 컨트롤러의 범위는 싱글 톤 (Singleton)입니다. 즉, 전체 웹 시스템에는 하나의 TestController 만 있습니다. 그러나 주입 된 요청은 다음과 같은 것이기 때문에 스레드 안전입니다.
이러한 방식으로, Bean (이 예제의 TestController)이 초기화되면 스프링은 요청 객체를 주입하지 않고 프록시를 주입합니다. Bean이 요청 객체를 사용해야하는 경우 프록시를 통해 요청 객체를 얻습니다.
다음은 특정 코드를 통한이 구현에 대한 설명입니다.
위의 코드에 중단 점을 추가하고 아래 그림과 같이 요청 객체의 속성을 봅니다.
그림에서 볼 수 있듯이 요청은 실제로 대리입니다. 프록시의 구현은 Autowireutils의 내부 클래스에 표시됩니다.
ObjectFactoryDelegatingInvocationHandler : /*** 반사 invocationHandler의 현재 대상 객체에 대한 게으른 액세스를위한 반사 invocationHandler. */@suppresswarnings ( "Serial") 개인 정적 클래스 ObjectFactoryDelegingInvocationHandler는 invocationHandler, Serializable {Private Final ObjectFactory <?> ObjectFactory; 공개 ObjectFactoryDelegatingInvocationHandler (ObjectFactory <?> ObjectFactory) {this.ObjectFactory = ObjectFactory; } @Override public Object Invoke (오브젝트 프록시, 메소드 메소드, 개체 [] args) 던지기 가능 {// ... umit 관련없는 코드 try {return method.invoke (this.objectFactory.getObject (), args); // 에이전트 구현 핵심 코드} catch (invocationTargeteXception ex) {ex.getTargetexception (); }}}다시 말해, 요청 메소드를 호출 할 때 실제로 ObjectFactory.getObject ()에 의해 생성 된 객체의 메소드 메소드를 호출합니다. ObjectFactory.getObject ()에 의해 생성 된 객체는 실제 요청 객체입니다.
위의 그림을 계속 관찰하고 ObjectFactory 유형이 WebApplicationContextUtils의 내부 클래스 requestObjectFactory임을 확인하십시오. 그리고 requestObjectFactory 코드는 다음과 같습니다.
/*** 요청시 현재 요청 객체를 노출시키는 공장. */ @suppresswarnings ( "Serial") 개인 정적 클래스 요청 Objectory는 ObjectFactory <ServletRequest>, Serializable {@override public servletrequest getObject () {return currentRequestAttributes (). getRequest (); } @override public String toString () {return "current httpservletrequest"; }}그 중에서도 요청 객체를 얻으려면 requestAttributes 객체를 얻으려면 currentRequestAttributes () 메소드를 호출해야합니다. 이 방법의 구현은 다음과 같습니다.
/*** 현재 requestAttributes 인스턴스를 servletRequestAttributes로 반환합니다. */private static servletrequestattributes currentRequestAttributes () {requestAttributes requestAttr = requestContexTholder.currentRequestAttributes (); if (! (servletrequestattributes의 requestAttr instance)) {throw new neveralstateException ( "현재 요청은 서블릿 요청이 아닙니다"); } return (servletRequestAttributes) requestAttr;}requestAttributes 객체를 생성하는 핵심 코드는 클래스 requestContexTholder에 있으며 관련 코드는 다음과 같습니다 (클래스의 관련없는 코드는 생략 됨).
공개 초록 클래스 requestContexTholder {public static requesttributes currentRequestAttributes ()가 불법 스테이트 exception {requestAttributes 속성 = getRequestAttributes (); // 관련없는 논리가 여기에서 생략됩니다 ...... return 속성; } public static requesttributes getRequestAttributes () {requestAttributes 속성 = requestAttributesHolder.get (); if (attributes == null) {attributes = inheritableRequestattributesholder.get (); } return 속성; } private static final strandlocal <requestattributes> requestattributeSholder = new namedthreadlocal <requestattributes> ( "요청 속성"); Private STATIC Final ThreadLocal <requestAttributes> inheritableRequestAttributeSholder = 새로운 지정된 inheritableThreadLocal <requestAttributes> ( "request context");}이 코드에서 생성 된 requesttributes 객체가 스레드 로컬 변수 (ThreadLocal)이므로 요청 객체도 스레드 로컬 변수임을 알 수 있습니다. 이것은 요청 객체의 스레드 안전을 보장합니다.
장단점
이 방법의 주요 장점 :
1) 주입은 컨트롤러로 제한되지 않습니다. 방법 1에서는 컨트롤러에 요청 매개 변수 만 추가 할 수 있습니다. 방법 2의 경우 컨트롤러뿐만 아니라 서비스, 저장소 및 일반 콩을 포함한 콩에도 주입 될 수 있습니다.
2) 주입 된 개체는 요청에 국한되지 않습니다. 요청 객체를 주입하는 것 외에도이 방법은 응답 개체, 세션 개체 등과 같은 요청 또는 세션으로 스코프를 가진 다른 객체를 주입 할 수 있습니다. 스레드 안전을 보장합니다.
3) 코드 중복 감소 : 요청 객체를 BEAN에 주입하여 요청 객체가 필요한 Bean의 다양한 방법에 사용할 수 있으며, 이는 방법 1과 비교하여 코드 중복성을 크게 줄입니다.
그러나이 방법에는 코드 중복성도 있습니다. 이 시나리오를 고려하십시오. 웹 시스템에는 많은 컨트롤러가 있으며 각 컨트롤러는 요청 객체를 사용합니다 (이 시나리오는 실제로 매우 빈번합니다). 현재 요청을 여러 번 주입하려면 코드를 작성해야합니다. 응답을 주입 해야하는 경우 코드는 훨씬 더 성가 시게됩니다. 다음은 자동 주입 방법의 개선을 설명하고 스레드 안전성 및 장점 및 단점을 분석합니다.
방법 3 : 기본 클래스로의 자동 분사
코드 예제
방법 2와 비교하여 코드의 주입 된 부분을 기본 클래스에 넣습니다.
기본 클래스 코드 :
공개 클래스 BaseController {@autowired 보호 httpservletrequest 요청; }컨트롤러 코드는 다음과 같습니다. 다음은 Basecontroller의 파생 클래스입니다. 현재 테스트 코드가 다르기 때문에 서버 테스트 코드는 생략되지 않습니다. 클라이언트는 또한 해당 수정을해야합니다 (동시에 두 URL에 많은 동시 요청을 보내십시오).
@ControllerPublic 클래스 TestController는 BaseController를 확장하여 기존 매개 변수를 저장하여 매개 변수 값이 반복되는지 여부를 결정하여 스레드가 안전한 공개 정적 세트 <string> set = new Hashset <> ()인지 결정합니다. @requestmapping ( "/test") public void test ()는 InterruptedException {String value = request.getParameter ( "key"); // (set.contains (value)) {system.out.println (value + "/t가 반복적으로 나타나면 동시성 요청 동시성이 안전하지 않습니다!"); } else {system.out.println (값); set.add (값); } // 시뮬레이션 프로그램은 일정 시간 동안 실행되었습니다 .Sleep (1000); }} @ControllerPublic 클래스 Test2Controller 확장 BaseController {@requestmapping ( "/test2") public void test2 () throws InterruptedException {String value = request.getParameter ( "key"); // 스레드 안전 판단 (testController가있는 세트를 판단하기 위해 세트를 사용) if (testController.set.contains (value)) {system.out.println (value + "/t는 반복적으로 나타나고 요청 동시성이 안전하지 않습니다!"); } else {system.out.println (값); TestController.set.add (값); } // 시뮬레이션 프로그램은 일정 시간 동안 실행되었습니다. thread.sleep (1000); }} 실 안전
테스트 결과 : 스레드 안전
분석 : 방법 2의 스레드 안전성을 이해하는 데 기초하여, 방법 3은 스레드-안전임을 이해하기 쉽다. 다른 파생 클래스 객체를 생성 할 때, 기본 클래스의 도메인은 다른 파생 클래스 객체에서 다른 메모리 공간을 차지할 것이다. 테스트 결과도 이것을 증명합니다.
장단점
방법 2와 비교하여, 상이한 컨트롤러에서 요청의 반복 주입은 피하고; 그러나 Java가 하나의 기본 클래스 만 상속을 허용한다는 점을 고려하면 컨트롤러가 다른 클래스를 상속 해야하는 경우이 방법은 더 이상 사용하기 쉽지 않습니다.
방법 2 또는 방법 3이든, 콩에 요청을 주입 할 수 있습니다. 다른 메소드 (예 : 도구 클래스의 정적 메소드)가 요청 객체를 사용해야하는 경우이 메소드를 호출 할 때 요청 매개 변수를 전달해야합니다. 아래에 소개 된 방법 4는 공구 클래스와 같은 정적 방법에서 직접 사용할 수 있습니다 (물론 다양한 콩에도 사용할 수 있음).
방법 4 : 수동으로 호출하십시오
코드 예제
@ControllerPublic 클래스 TestController {@requestmapping ( "/test") public void test ()가 InterruptedException {httpservletRequest request = ((servletRequestAttributes) (requestContexTholder.currentRequestattributes ()). // 시뮬레이션 프로그램은 일정 시간 동안 실행되었습니다. }} 실 안전
테스트 결과 : 스레드 안전
분석 :이 방법은 방법 2 (자동 분사)와 유사하며,이 방법은 방법 2에서 자동 분사를 통해 구현 되며이 방법은 수동 메소드 호출을 통해 구현됩니다. 따라서이 방법은 스레드 안전입니다.
장단점
장점 : 빔이 아닌 곳에서 직접 얻을 수 있습니다. 단점 : 더 많은 장소를 사용하는 경우 코드는 매우 번거 롭습니다. 따라서 다른 방법과 함께 사용할 수 있습니다.
방법 5 : @ModelAttribute 메소드
코드 예제
다음 방법과 그 변형 (돌연변이 : 서브 클래스로 요청 및 바인딩)이 종종 온라인에서 볼 수 있습니다.
@ControllerPublic 클래스 TestController {private httpservletRequest 요청; @ModelAttribute public void bindrequest (httpservletrequest 요청) {this.request = request; } @RequestMapping ( "/test") public void test ()가 중단 exception을 던지십시오 {// 시뮬레이션 프로그램이 일정 시간 동안 실행되었습니다. Sleep (1000); }} 실 안전
테스트 결과 : 스레드는 안전하지 않습니다
분석 : @ModelAttribute 주석이 컨트롤러의 메소드를 수정하는 데 사용되는 경우 컨트롤러의 각 @requestmapping 메소드가 실행되기 전에 메소드가 실행됩니다. 따라서이 예에서 BindRequest ()의 함수는 test ()가 실행되기 전에 요청 객체에 값을 할당하는 것입니다. BindRequest () 자체의 매개 변수 요청은 스레드-안전이지만 TestController는 Singleton이므로 TestController 필드로서 REIDGE는 스레드 안전성을 보장 할 수 없습니다.
요약
요약하면, 컨트롤러에서 매개 변수 (메소드 1), 자동 분사 (방법 2 및 메소드 3) 및 수동 호출 (메소드 4)을 추가하고 모두 스레드 안전이며 모두 요청 객체를 얻는 데 사용할 수 있습니다. 요청 객체가 시스템에서 덜 사용되는 경우 어느 중 하나나 사용할 수 있습니다. 더 많이 사용되는 경우 코드 중복성을 줄이기 위해 자동 분사 (방법 2 및 방법 3)를 사용하는 것이 좋습니다. 비 콩에서 요청 객체를 사용해야하는 경우 상단 계층을 호출 할 때 매개 변수를 통해 전달하거나 수동 통화 (방법 4)를 통해 직접 얻을 수 있습니다.
좋아, 위는이 기사의 전체 내용입니다. 이 기사의 내용에 모든 사람의 연구 나 작업에 대한 특정 참조 가치가 있기를 바랍니다. 궁금한 점이 있으면 의사 소통을 위해 메시지를 남길 수 있습니다. Wulin.com을 지원 해주셔서 감사합니다.
참조