Prefácio
Este artigo introduzirá vários métodos para obter objetos de solicitação no sistema Web desenvolvido pelo Spring MVC e discutirá sua segurança de threads. Não vou dizer muito abaixo, vamos dar uma olhada na introdução detalhada juntos.
Visão geral
When developing a web system using Spring MVC, you often need to use a request object when processing requests, such as obtaining the client IP address, the requested URL, attributes in the header (such as cookies, authorization information), data in the body, etc. Since in Spring MVC, the Controller, Service and other objects that handle requests are singletons, the most important issue to be paid attention to when obtaining request objects is whether the request object is thread-safe: when Há um grande número de solicitações simultâneas, pode -se garantir que diferentes objetos de solicitação sejam usados em diferentes solicitações/threads?
Há outra pergunta a ser observada aqui: onde uso o objeto de solicitação "ao processar uma solicitação" mencionada anteriormente? Considerando que existem pequenas diferenças nos métodos de obtenção de objetos de solicitação, eles podem ser divididos aproximadamente em duas categorias:
1) Use objetos de solicitação em grãos de mola: inclua feijões MVC, como controlador, serviço, repositório e feijões comuns, como componente. Por conveniência da explicação, os grãos na primavera no texto a seguir são chamados de feijão para abreviar.
2) Use objetos de solicitação em não beans: como nos métodos de objetos Java comuns ou nos métodos estáticos das classes.
Além disso, este artigo discute em torno do objeto de solicitação que representa a solicitação, mas o método usado também é aplicável ao objeto de resposta, InputStream/Reader, OutputStream/Writer, etc.; Onde o InputStream/Reader pode ler os dados na solicitação e o outputStream/Writer pode gravar dados na resposta.
Finalmente, o método de obter o objeto de solicitação também está relacionado à versão do Spring e MVC; Este artigo é discutido com base na primavera 4, e os experimentos realizados estão todos usando a versão 4.1.1.
Como testar a segurança do tópico
Como os problemas de segurança do thread do objeto de solicitação precisam de atenção especial, a fim de facilitar a discussão abaixo, vamos primeiro explicar como testar se o objeto de solicitação é seguro para threads.
A idéia básica de teste é simular um grande número de solicitações simultâneas no cliente e, em seguida, determinar se as solicitações são usadas no servidor.
A maneira mais intuitiva de determinar se o objeto de solicitação é o mesmo é imprimir o endereço do objeto de solicitação. Se for o mesmo, significa que o mesmo objeto é usado. No entanto, em quase todas as implementações do servidor da web, são usados pools de threads, o que leva a duas solicitações que chegam em sucessão, que podem ser processadas pelo mesmo thread: após a solicitação anterior ser processada, o pool de thread recupera o thread e reatribua o thread para a solicitação subsequente. No mesmo encadeamento, o objeto de solicitação usado provavelmente será o mesmo (o endereço é o mesmo, os atributos são diferentes). Portanto, mesmo para métodos seguros para threads, os endereços do objeto de solicitação usados por diferentes solicitações podem ser os mesmos.
Para evitar esse problema, um método é deixar o encadeamento dormir por alguns segundos durante o processo de processamento de solicitação, o que pode fazer com que cada thread funcione o tempo suficiente para evitar o mesmo thread alocando para diferentes solicitações; O outro método é usar outros atributos da solicitação (como parâmetros, cabeçalho, corpo etc.) como base para se a solicitação é segura por threads, porque, mesmo que diferentes solicitações usem o mesmo thread um após o outro (o endereço do objeto de solicitação é o mesmo), desde que o objeto de solicitação seja construído duas vezes usando diferentes atributos, o uso da solicitação é o seguinte. Este artigo usa o segundo método para teste.
O código de teste do cliente é o seguinte (crie 1000 threads para enviar solicitações separadamente):
public class Test {public static void main (string [] args) lança exceção {string prefix = uuid.randomuuid (). tostring (). replaceall ("-", "") + "::"; for (int i = 0; i <1000; i ++) {final da string value = prefixo+i; novo thread () {@Override public void run () {try {CloseableHttpClient httpClient = httpClients.createFault (); Httpget httpget = new httpget ("http: // localhost: 8080/teste? Key =" + value); httpclient.execute (httpget); httpclient.close (); } catch (ioexception e) {e.printStackTrace (); } } } } } }.começar(); }}}O código do controlador no servidor é o seguinte (o código para obter o objeto de solicitação é temporariamente omitido):
@ControllerPublic Classe testController {// Armazene os parâmetros existentes para determinar se os parâmetros são duplicados, determinando assim se o thread é um conjunto estático público seguro <Set> set = new HashSet <> (); @RequestMapping ("/test") public void test () lança interruptedException {// ……………………………………………………………. …………………………………………………………………………………. ……………………………………………………………. …………………………………………………………………………………. "/T aparece repetidamente, a concorrência de solicitação não é segura!"); } else {System.out.println (valor); set.add (valor); } // O programa de simulação foi executado por um período de tempo, Thread.sleep (1000); }}Se o objeto de solicitação for seguro, o resultado do servidor será o seguinte:
Se houver um problema de segurança de threads, o resultado da impressão no servidor pode parecer o seguinte:
Se não houver descrição especial, o código de teste será omitido dos códigos posteriormente neste artigo.
Método 1: Adicione parâmetros ao controlador
Exemplo de código
Este método é o mais simples de implementar e inserir diretamente o código do controlador:
@ControllerPublic Classe testController {@RequestMapping ("/test") Teste de void public (solicitação httpServletRequest) lança interruptedException {// O programa de simulação foi executado por um período de tempo. }}O princípio deste método é que, quando o método do controlador inicia o processamento da solicitação, a mola atribuirá o objeto de solicitação aos parâmetros do método. Além do objeto de solicitação, existem muitos parâmetros que podem ser obtidos através deste método. Para detalhes, consulte: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-methods
Depois de obter o objeto de solicitação no controlador, se você deseja usar o objeto de solicitação em outros métodos (como métodos de serviço, métodos de classe de ferramenta etc.), você precisa passar o objeto de solicitação como um parâmetro ao chamar esses métodos.
Segurança do thread
Resultados do teste: segurança do thread
Análise: Nesse momento, o objeto de solicitação é um parâmetro de método, que é equivalente a uma variável local e, sem dúvida, é segura por threads.
Prós e contras
A principal desvantagem desse método é que o objeto de solicitação é muito redundante para escrever, o que se reflete principalmente em dois pontos:
1) Se o objeto de solicitação for necessário em vários métodos do controlador, o parâmetro de solicitação precisará ser adicionado a cada método.
2) A aquisição do objeto Solicitação só pode começar pelo controlador. Se o local onde o objeto de solicitação for usado estiver em um local mais profundo do nível de chamada de função, todos os métodos em toda a cadeia de chamadas precisam adicionar o parâmetro de solicitação.
De fato, durante todo o processo de processamento de solicitação, o objeto de solicitação é executado por toda a solicitação; Ou seja, exceto para casos especiais, como temporizadores, o objeto de solicitação é equivalente a uma variável global dentro do encadeamento. Este método é equivalente a passar essa variável global.
Método 2: Injeção automática
Exemplo de código
Primeiro, envie o código:
@ControllerPublic Classe testController {@AUTOWIRED PRIVADO HTTPSERVLETREQUEST Solicitação; // solicitação automática @RequestMapping ("/test") public void test () lança interruptedException {// O programa de simulação foi executado por um período de tempo. }} Segurança do thread
Resultados do teste: segurança do thread
Análise: Na primavera, o escopo do controlador é Singleton (Singleton), o que significa que, em todo o sistema da Web, existe apenas um testcontroller; Mas a solicitação injetada é segura para fios, porque:
Dessa forma, quando o feijão (testcontrolador neste exemplo) é inicializado, a primavera não injeta um objeto de solicitação, mas um proxy; Quando o feijão precisa usar o objeto de solicitação, o objeto de solicitação é obtido através do proxy.
A seguir, é apresentada uma descrição desta implementação através do código específico.
Adicione pontos de interrupção ao código acima e visualize as propriedades do objeto de solicitação, conforme mostrado na figura abaixo:
Como pode ser visto na figura, a solicitação é realmente um proxy: a implementação do proxy é mostrada na classe interna de AutoCireutils
ObjectFactoryDelegatingInvocationHandler: /*** Invocação reflexiva Handler para obter acesso preguiçoso ao objeto de destino atual. */@Supressorwarnings ("serial") classe estática privada ObjectFactoryDelegatingInvocationHandler implementa a InvocationHandler, serializável {private final ObjectFactory <?> ObjectFactory; public ObjectFactoryDelegatingInvocationHandler (ObjectFactory <?> ObjectFactory) {this.ObjectFactory = ObjectFactory; } @Override Public Object Invoke (proxy do objeto, método do método, objeto [] args) lança arremesso {// ... omita código irrelevante, tente {return method.invoke (this.ObjectFactory.getObject (), args); // Implementação do agente Código principal} Catch (InvocationTargeTexception Ex) {Throw Ex.getTargeTexception (); }}}Em outras palavras, quando chamamos o método da solicitação, na verdade chamamos o método do método do objeto gerado pelo objectFactory.getObject (); O objeto gerado pelo objectFactory.getObject () é o objeto de solicitação real.
Continue a observar a figura acima e descobrir que o tipo ObjectFactory é a classe interna RequestObjectFactory of WebApplicationContextutils; e o código RequestObjectFactory é o seguinte:
/*** Fábrica que expõe o objeto de solicitação atual sob demanda. */ @Supressorwarnings ("serial") classe estática privada requestObjectfactory implementos ObjectFactory <VerletRequest>, Serializable {@Override public servletRequest getObject () {return currentRequestAttributes (). GetRequest (); } @Override public string tostring () {return "atual httpServletRequest"; }}Entre eles, para obter o objeto Solicitação, você precisa chamar o método currentRequestAttributes () para obter o objeto Requestattributes. A implementação deste método é a seguinte:
/*** Retorne a instância do requestattributes atuais como servletRequestAttributes. */private estático servletRequestattributes currentRequestAttributes () {requestAttributes requestAtTtr = requestContextholder.currentRequestAttributes (); if (! (requestAtTtr instanceof servletRequestAttributes)) {lança nova ilegalStateException ("a solicitação atual não é uma solicitação de servlet"); } return (servletRequestAttributes) requestattr;}O código principal que gera o objeto RequestAttributes está no Classe RequestContextholder, onde o código relevante é o seguinte (o código não relacionado na classe é omitido):
Classe de abstração pública requestContextholder {public static requestattributes currentRequestAttributes () lança ilegalStateException {requestAttributes atributes = getRequestAttributes (); // A lógica irrelevante é omitida aqui ...... Atributos de retorno; } public static requestAttributes getRequestAttributes () {requestAttributes atributes = requestAttributesHolder.get (); if (atributes == NULL) {atributes = heritableReQuestAttributesHolder.get (); } retornar atributos; } private Static Final ThreadLocal <PolicStTributes> requestattributesHoldder = new NometThreadLocal <RequestAttributes> ("atributos de solicitação"); private estático final ThreadLocal <PolicSttributes> herançaBereQuestAttributesHolder = new NomeNheritableThreadLocal <PolicStTributes> ("Contexto da solicitação");}A partir deste código, podemos ver que o objeto RequestAttributes gerado é uma variável local Thread (ThreadLocal), portanto, o objeto de solicitação também é uma variável local de encadeamento; Isso garante a segurança do thread do objeto de solicitação.
Prós e contras
As principais vantagens deste método:
1) A injeção não se limita ao controlador: no método 1, apenas o parâmetro de solicitação pode ser adicionado ao controlador. Para o método 2, ele pode não apenas ser injetado no controlador, mas também em qualquer feijão, incluindo serviço, repositório e feijões comuns.
2) O objeto injetado não se limita à solicitação: Além de injetar o objeto de solicitação, esse método também pode injetar outros objetos com escopo como solicitação ou sessão, como objetos de resposta, objetos de sessão, etc.; e verifique se a segurança do thread.
3) Reduza a redundância do código: basta injetar o objeto de solicitação no feijão que requer o objeto de solicitação e pode ser usado em vários métodos do feijão, o que reduz bastante a redundância do código em comparação com o método 1.
No entanto, esse método também possui redundância de código. Considere o cenário: existem muitos controladores no sistema da Web e cada controlador usa um objeto de solicitação (esse cenário é realmente muito frequente). No momento, você precisa escrever código para injetar solicitação muitas vezes; Se você também precisar injetar resposta, o código será ainda mais pesado. A seguir, descreve a melhoria do método de injeção automática e analisa sua segurança e vantagens e desvantagens.
Método 3: Injeção automática na classe base
Exemplo de código
Comparado com o método 2, coloque a parte injetada do código na classe base.
Código de classe base:
classe pública Basecontroller {@Autowired Protected HttpServletRequest Solicy; }O código do controlador é o seguinte; Aqui estão duas classes derivadas de controle de base. Como o código de teste será diferente no momento, o código de teste do servidor não será omitido; O cliente também precisa fazer modificações correspondentes (envie um grande número de solicitações simultâneas para os dois URLs ao mesmo tempo).
@ControllerPublic Classe testController estende o Basecontroller {// Armazene os parâmetros existentes para determinar se o valor do parâmetro é repetido, determinando assim se o thread é um conjunto estático público seguro <string> set = new Hashset <> (); @RequestMapping ("/test") public void test () lança interruptedException {string value = request.getParameter ("key"); // Verifique a segurança do thread if (set.contains (value)) {System.out.println (valor + "/t aparece repetidamente, a concorrência de solicitação não é segura!"); } else {System.out.println (valor); set.add (valor); } // O programa de simulação foi executado por um período de tempo.Sleep (1000); }} @ControllerPublic Classe test2Controller estende Basecontroller {@RequestMapping ("/test2") public void test2 () lança interruptedException {string value = request.getParameter ("key"); // Juiz Segurança do thread (use um conjunto com testController para julgar) se (testController.set.contains (value)) {System.out.println (value + "/t aparece repetidamente, a concorrência de solicitação não é segura!"); } else {System.out.println (valor); TestController.set.add (valor); } // O programa de simulação foi executado por um período de tempo, Thread.sleep (1000); }} Segurança do thread
Resultados do teste: segurança do thread
Análise: Com base na compreensão da segurança do encadeamento do método 2, é fácil entender que o Método 3 é seguro para o fio: ao criar diferentes objetos de classe derivada, os domínios da classe base (aqui está a solicitação injetada) ocupará um espaço de memória diferente em diferentes objetos de classe derivada, ou seja, colocar o código injetado de solicitação na classe base não terá impacto na segurança; Os resultados do teste também provam isso.
Prós e contras
Comparado com o método 2, é evitada a injeção repetida de solicitações em diferentes controladores; No entanto, considerando que o Java permite apenas a herança de uma classe base, se o controlador precisar herdar outras classes, esse método não é mais fácil de usar.
Seja o método 2 ou o método 3, você só pode injetar solicitações no feijão; Se outros métodos (como métodos estáticos na classe de ferramentas) precisarem usar objetos de solicitação, você precisará passar os parâmetros de solicitação ao chamar esses métodos. O método 4 introduzido abaixo pode ser usado diretamente em métodos estáticos, como classes de ferramentas (é claro, ele também pode ser usado em vários feijões).
Método 4: Ligue manualmente
Exemplo de código
@ControllerPublic Classe testController {@ReQuestMapping ("/test") public void test () lança interruptedException {httpServletReQuest request = ((servLetRequestattributes) (requestContexTholder.currentRequestTrributes ())). // O programa de simulação foi executado por um período de tempo.Sleep (1000); }} Segurança do thread
Resultados dos testes: segurança do thread
Análise: Este método é semelhante ao método 2 (injeção automática), exceto que é implementada por meio de injeção automática no método 2, e esse método é implementado através da chamada de método manual. Portanto, esse método também é seguro para threads.
Prós e contras
Vantagens: pode ser obtido diretamente em não-bens. Desvantagens: se você usar mais lugares, o código é muito pesado; Portanto, pode ser usado em conjunto com outros métodos.
Método 5: @modelattribute Método
Exemplo de código
O método a seguir e suas variantes (mutação: colocar solicitação e bindRequest em subclasses) são frequentemente vistas online:
@ControllerPublic Classe testController {Private HttpServletRequest Request; @Modelattribute public void bindRequest (solicitação httpServletRequest) {this.request = request; } @ReQuestMapping ("/test") public void test () lança interruptedException {// O programa de simulação foi executado por um período de tempo.sleep (1000); }} Segurança do thread
Resultado do teste: Thread não está seguro
Análise: Quando a anotação @modelattribute é usada para modificar o método no controlador, sua função é que o método será executado antes que cada método @RequestMapping no controlador seja executado. Portanto, neste exemplo, a função do bindRequest () é atribuir um valor ao objeto de solicitação antes que o teste () seja executado. Embora a solicitação de parâmetro no BindRequest () seja segura para roscas, como o testController é o Singleton, a solicitação, como um campo do testcontrolador, não pode garantir a segurança do encadeamento.
Resumir
Para resumir, a adição de parâmetros (método 1), injeção automática (método 2 e método 3) e chamadas manuais (método 4) no controlador são todas seguras de rosca e podem ser usadas para obter objetos de solicitação. Se o objeto de solicitação for usado menos no sistema, qualquer método poderá ser usado; Se for usado mais, é recomendável usar injeção automática (método 2 e método 3) para reduzir a redundância do código. Se você precisar usar um objeto de solicitação em um não-feijão, poderá passar por parâmetros ao ligar para a camada superior ou obtê-lo diretamente através de chamadas manuais (Método 4).
Ok, o acima é o conteúdo inteiro deste artigo. Espero que o conteúdo deste artigo tenha certo valor de referência para o estudo ou trabalho de todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.
Referências