Versão remasterizada Java SSM (2) SpringMvc

Noções básicas do SpringMVC

Antes de entrar: "Spring Core Content" "JavaWeb" "JDK9-17 New Features"

Depois de aprender a tecnologia do framework Spring anteriormente, haverá dois grupos de pessoas: um grupo está confuso e ainda não entende o que essa coisa faz; o outro grupo quase entende as ideias centrais, mas não sabe como algo deve funcionar . Mas isso não importa, no estágio SpringMVC você certamente poderá apreciar gradualmente a conveniência que o framework Spring traz para nós.

Nesta fase, retornaremos ao desenvolvimento de aplicações Web do Tomcat para experimentar a grande comodidade que o framework Spring nos traz.

Base teórica MVC

Anteriormente, explicamos a você a arquitetura de três camadas, incluindo:

imagem

Cada camada tem suas próprias responsabilidades, sendo a mais crítica a camada de apresentação, pois equivale a uma camada que interage diretamente com o navegador do usuário, e todas as solicitações serão analisadas por meio dela, e então a camada de negócios será notificada. O processamento, o retorno e o preenchimento de dados de qualquer página são todos concluídos pela camada de apresentação, portanto, é na verdade a camada mais crítica em toda a arquitetura de três camadas.No desenvolvimento real anterior, escrevemos um grande número de Servlets (ou seja, , implementação da camada de apresentação) para lidar com várias solicitações do navegador, mas descobrimos que, para apenas algumas funções pequenas e algumas páginas muito básicas, precisamos escrever quase dez servlets. Se for um site maior, sistemas como Taobao e Bilibili, pode conter dezenas ou até centenas de funções em apenas uma página. Pense em como seria assustador escrever isso.

Portanto, SpringMVC nasceu para resolver este problema. É um excelente framework de camada de apresentação, projetado e implementado usando ideias MVC.

MVC é explicado em detalhes da seguinte forma:

  • M refere-se ao modelo de negócios (Modelo): em termos leigos, é a classe de entidade que usamos para encapsular a transferência de dados.
  • V refere-se à interface do usuário (View): geralmente se refere à página front-end.
  • C é o controlador: o controlador equivale à função básica do Servlet, processando solicitações e retornando respostas.

imagem

SpringMVC espera dissociar os três, realizar suas próprias tarefas e dividir as responsabilidades correspondentes de maneira mais precisa. Por fim, renderize a Visualização e o Modelo para obter a página final e retorne-a ao front-end (assim como usar o Thymeleaf antes, forneça o objeto de dados da entidade e a página do front-end ao Thymeleaf e então ele irá integrá-los e renderizá-los para obtenha os dados finais), e este tutorial também usará o Thymeleaf como analisador de visualização para explicar)


Configure o ambiente e construa o projeto

Aqui continuamos a usar o servidor Tomcat10 anterior. Após o Spring 6, será necessário o Tomcat10 ou superior. Como antes, criamos diretamente um novo projeto JakartaEE.

imagem-20230219162053172

Os arquivos relevantes serão gerados automaticamente após a criação, mas verifique se o URL e o nome do contexto do aplicativo na configuração em execução são consistentes.

Formulário de configuração XML tradicional

O projeto SpringMvc ainda suporta vários formulários de configuração. Aqui explicamos primeiro o formulário de configuração XML mais tradicional.

Primeiro precisamos adicionar dependências relacionadas ao Mvc:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>6.0.10</version>
</dependency>

Então precisamos configurar o web.xml e substituir o DispatcherServlet pelo Servlet que vem com o Tomcat. Aqui o url-pattern precisa ser escrito como /, e a substituição pode ser concluída:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

Então você precisa configurar um ambiente de contexto Spring (ou seja, contêiner) para todo o aplicativo da web. Como o SpringMVC é desenvolvido com base no Spring, ele usa diretamente o contêiner fornecido pelo Spring para implementar várias funções. Portanto, a primeira etapa ainda é a mesma como antes, você precisa escrever um arquivo de configuração:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

Em seguida, precisamos configurar alguns parâmetros de inicialização do DispatcherServlet para especificar o arquivo de configuração que acabamos de criar:

<servlet>
    <servlet-name>mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      	<!--     指定我们刚刚创建在类路径下的XML配置文件       -->
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application.xml</param-value>
    </init-param>
</servlet>

Desta forma, completamos a configuração básica. Agora podemos testar se a configuração está correta. Excluímos a classe Servlet que acompanha o projeto e criamos uma classe Controller usada no Mvc. Não importa se você não tiver aprendi ainda, basta segui-lo. Aqui vamos apenas para testar isso:

@Controller
public class HelloController {
    
    
    @ResponseBody
    @RequestMapping("/")
    public String hello(){
    
    
        return "HelloWorld!";
    }
}

Então precisamos registrar esta classe como um Bean para usá-la normalmente. Vamos escrever o arquivo de configuração do Spring. Aqui configuramos diretamente a verificação de pacotes. A verificação de pacotes sob XML precisa ser ativada assim:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
  	<!-- 需要先引入context命名空间,然后直接配置base-package属性就可以了 -->
    <context:component-scan base-package="com.example"/>
</beans>

Se HelloWorld puder aparecer com sucesso no navegador, a configuração foi bem-sucedida:

imagem-20230219170637540

Amigos espertos podem ter descoberto que o Controller que escrevemos acima é na verdade responsável pelas funções básicas do Servlet. Por exemplo, o que retornamos aqui é a string HelloWorld, então quando acessarmos esse endereço, obteremos os caracteres retornados aqui. String , você pode ver que o método de escrita é muito conciso. Quanto a como isso é feito e como usá-lo, iremos apresentá-lo em detalhes neste capítulo.

Formulário completo de configuração de anotação

Se você deseja descartar completamente o arquivo de configuração e usar o desenvolvimento de anotação pura, você pode adicionar uma classe diretamente. O Tomcat procurará uma classe que implemente a interface ServletContainerInitializer no caminho da classe. Se encontrada, use-a para configurar o contêiner do Servlet. Spring fornece isso A classe de implementação da interface, SpringServletContainerInitializer, é definida por meio de @HandlesTypes(WebApplicationInitializer.class).Esta classe, por sua vez, encontrará a classe que implementa WebApplicationInitializer e atribuirá a tarefa de configuração a eles, então apenas implemente a interface diretamente:

public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    

    @Override
    protected Class<?>[] getRootConfigClasses() {
    
    
        return new Class[]{
    
    WebConfiguration.class};   //基本的Spring配置类,一般用于业务层配置
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
    
    
        return new Class[0];  //配置DispatcherServlet的配置类、主要用于Controller等配置,这里为了教学简单,就不分这么详细了,只使用上面的基本配置类
    }

    @Override
    protected String[] getServletMappings() {
    
    
        return new String[]{
    
    "/"};    //匹配路径,与上面一致
    }
}

Então precisamos adicionar algumas anotações necessárias à classe de configuração:

@Configuration
@EnableWebMvc   //快速配置SpringMvc注解,如果不添加此注解会导致后续无法通过实现WebMvcConfigurer接口进行自定义配置
@ComponentScan("com.example.controller")
public class WebConfiguration {
    
    
}

Desta forma também podemos acessar normalmente:

imagem-20230219170637540

De agora em diante, por conveniência, escreveremos uniformemente na forma de anotação completa.

Se a Log Technology relatar um erro e não puder exibir logs relacionados ao Mvc, adicione as seguintes dependências:

<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.33</version>
</dependency>
<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk14</artifactId>
      <version>1.7.33</version>
</dependency>

Após adicioná-lo, você pode imprimir o log normalmente:

imagem-20230630162821105

ControladorControlador

Com SpringMVC, não precisamos mais criar um Servlet para cada endereço de solicitação como antes. Ele usa o DispatcherServletServlet de recurso estático padrão em vez do Servlet de recurso estático padrão fornecido pelo Tomcat. Ou seja, todas as solicitações agora (exceto jsp, porque o Tomcat também fornece um servlet jsp) será DispatcherServletprocessado.

Então, DispatcherServleto que isso nos ajudará a fazer?

imagem

De acordo com a imagem, podemos entender que após nossa solicitação chegar ao servidor Tomcat, ela será entregue à aplicação Web atual para processamento, e SpringMVC será utilizado para processar DispatcherServlettodas as solicitações, o que significa que será utilizado como um ponto de acesso unificado , e todas as solicitações são processadas. Ele faz o agendamento.

Quando uma solicitação passa DispatcherServlet, ela irá primeiro HandlerMapping. Ela mapeará a solicitação para e , por sua vez, HandlerExecutionChainpassará pelos filtros, o que é semelhante aos filtros que aprendemos antes. No entanto, no SpringMVC usamos interceptores e depois os entregamos de acordo com o caminho da solicitação. Selecione o controlador apropriado para processamento. Após a conclusão do processamento do controlador, um objeto será retornado, incluindo o modelo de dados e a visualização. Em termos leigos, são os dados na página e a própria página ( apenas o nome da visualização é suficiente).HandlerInterceptorHandlerAdapterModelAndView

Depois de retornar ModelAndView, ele será entregue ao ViewResolver(analisador de visualização) para processamento. O analisador de visualização analisará toda a página de visualização. SpringMVC vem com alguns analisadores de visualização, mas eles se aplicam apenas a páginas JSP. Também podemos usar o Thymeleaf como antes. Analisador de visualização, para que possamos ler diretamente a página escrita em HTML e analisá-la em uma visualização real com base no nome da visualização fornecido.

Após a conclusão da análise, todos os dados da página precisam ser renderizados na Visualização e, finalmente, retornar a DispatcherServletuma página formada contendo todos os dados e, em seguida, responder ao navegador para concluir todo o processo.

Portanto, na verdade, em todo o processo, só precisamos escrever o Controller correspondente ao caminho da solicitação e configurar o ViewResolver que precisamos, podemos continuar adicionando interceptadores posteriormente, e o SpringMVC nos ajudou a concluir os demais processos.

Configurar resolvedores e controladores de visualização

Primeiro, precisamos implementar a análise e o retorno de página mais básicos. O primeiro passo é configurar o analisador de visualização. Aqui usamos o analisador de visualização fornecido pelo Thymeleaf e importamos as dependências necessárias:

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring6</artifactId>
    <version>3.1.1.RELEASE</version>
</dependency>

Configurar o view resolvedor é muito simples. Basta registrar o correspondente ViewResolvercomo um Bean. Aqui o escrevemos diretamente na classe de configuração:

@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfiguration {
    
    
    //我们需要使用ThymeleafViewResolver作为视图解析器,并解析我们的HTML页面
    @Bean
    public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine springTemplateEngine){
    
    
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setOrder(1);   //可以存在多个视图解析器,并且可以为他们设定解析顺序
        resolver.setCharacterEncoding("UTF-8");   //编码格式是重中之重
        resolver.setTemplateEngine(springTemplateEngine);   //和之前JavaWeb阶段一样,需要使用模板引擎进行解析,所以这里也需要设定一下模板引擎
        return resolver;
    }

    //配置模板解析器
    @Bean
    public SpringResourceTemplateResolver templateResolver(){
    
    
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setSuffix(".html");   //需要解析的后缀名称
        resolver.setPrefix("/");   //需要解析的HTML页面文件存放的位置,默认是webapp目录下,如果是类路径下需要添加classpath:前缀
        return resolver;
    }

    //配置模板引擎Bean
    @Bean
    public SpringTemplateEngine springTemplateEngine(ITemplateResolver resolver){
    
    
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(resolver);   //模板解析器,默认即可
        return engine;
    }
}

Agora que completamos a configuração do view resolvedor, criamos então um Controller. Criar um Controller também é muito simples. Basta adicionar uma anotação em uma classe. Ela será varrida pelo Spring e automaticamente registrada como um Bean do tipo @ControllerController . Então só precisamos escrever um método na classe para tratar a solicitação do endereço correspondente:

@Controller   //直接添加注解即可
public class HelloController {
    
    

    @RequestMapping("/index")   //直接填写访问路径
    public ModelAndView index(){
    
    
        return new ModelAndView("index");  //返回ModelAndView对象,这里填入了视图的名称
      	//返回后会经过视图解析器进行处理
    }
}

Em seguida, criamos um arquivo HTML simples no diretório raiz do classpath:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
</head>
<body>
    <p>欢迎来到GayHub全球最大同性交友网站</p>
</body>
</html>

Descobriremos que após abrir o navegador podemos acessar diretamente nossa página HTML:

imagem-20230220150905300

Antes, quando usávamos o Thymeleaf para analisar alguns dados do backend, precisávamos passá-los pelo Contexto, porém, depois de usar o SpringMvc, podemos fornecer os dados diretamente para a camada de modelo Model:

@RequestMapping(value = "/index")
public ModelAndView index(){
    
    
    ModelAndView modelAndView = new ModelAndView("index");
    modelAndView.getModel().put("name", "啊这");   //将name传递给Model
    return modelAndView;
}

Desta forma, o Thymeleaf pode receber os dados que passamos e analisá-los:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="static/test.js"></script>
</head>
<body>
    HelloWorld!
    <div th:text="${name}"></div>
</body>
</html>

Claro, para simplificar, podemos retornar diretamente o nome da View, e o SpringMVC irá envolvê-lo automaticamente em um objeto ModelAndView:

@RequestMapping(value = "/index")
public String index(){
    
    
    return "index";
}

Também podemos adicionar um Model separadamente como um parâmetro formal para definir, e o SpringMVC nos ajudará automaticamente a passar o objeto de instância por meio de injeção de dependência:

@RequestMapping(value = "/index")
public String index(Model model){
    
      //这里不仅仅可以是Model,还可以是Map、ModelMap
    model.addAttribute("name", "yyds");
    return "index";
}

Com a bênção do framework Spring, comparado ao aplicativo web que escrevemos antes, é simplesmente um nível mais conveniente: basta dizer se você gosta ou não, se gosta ou não.

Observe que você deve garantir que uma linha horizontal apareça sob o nome da visualização e manter pressionada a tecla Ctrl para pular. A configuração está correta (a versão mais recente do IDEA)

Nossa página também pode conter alguns recursos estáticos, como js e css, então aqui também precisamos configurá-la para que os recursos estáticos possam ser analisados ​​​​por meio do servlet padrão fornecido pelo Tomcat. Precisamos deixar a classe de configuração implementar a interface para que isso no WebMvcConfigureraplicativo da web Quando o programa for iniciado, outras configurações serão realizadas com base no conteúdo de nosso método substituído:

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    
    
    configurer.enable();   //开启默认的Servlet
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
    
    registry.addResourceHandler("/static/**").addResourceLocations("/static/");
    //配置静态资源的访问路径
}

Vamos escrever o conteúdo front-end:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <!-- 引用静态资源,这里使用Thymeleaf的网址链接表达式,Thymeleaf会自动添加web应用程序的名称到链接前面 -->
    <script th:src="@{/static/test.js}"></script>
</head>
<body>
    <p>欢迎来到GayHub全球最大同性交友网站</p>
</body>
</html>

Crie test.jse escreva o seguinte conteúdo:

window.alert("欢迎来到GayHub全球最大同性交友网站")

Por fim, visite a página. Uma janela pop-up será exibida quando a página for carregada. Desta forma, concluímos a configuração mais básica da página. Comparado com o método anterior, é muito mais simples, evitando diretamente a necessidade de escrever um grande número de Servlets para lidar com solicitações.

Explicação detalhada do @RequestMapping

Já aprendemos como criar um controlador para lidar com nossas solicitações. Depois, só precisamos adicionar um método ao controlador para lidar com as solicitações correspondentes. Antes, precisávamos escrever completamente um Servlet para implementá-lo, mas agora só precisamos Você só precisa adicionar um @RequestMappingpara alcançá-lo. Na verdade, também podemos saber pelo seu nome que esta anotação estabelece uma relação de mapeamento entre solicitações e métodos de processamento de solicitações. Quando uma solicitação é recebida, o método de processamento de solicitações correspondente pode ser chamado de acordo com o relacionamento de mapeamento. Então, vamos falar sobre isso primeiro @RequestMapping. A anotação é definida da seguinte forma:

@Mapping
public @interface RequestMapping {
    
    
    String name() default "";

    @AliasFor("path")
    String[] value() default {
    
    };

    @AliasFor("value")
    String[] path() default {
    
    };

    RequestMethod[] method() default {
    
    };

    String[] params() default {
    
    };

    String[] headers() default {
    
    };

    String[] consumes() default {
    
    };

    String[] produces() default {
    
    };
}

O mais crítico é o atributo path (equivalente ao valor), que determina o caminho da solicitação processado pelo método atual. Observe que o caminho deve ser globalmente exclusivo. Qualquer caminho só pode ser processado por um método. É um array, que significa que este método não só pode ser usado para processar um determinado caminho de solicitação, mas também pode usar este método para processar vários caminhos de solicitação:

@RequestMapping({
    
    "/index", "/test"})
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

Agora quando acessarmos /index ou /test, ele será processado através deste método.

Também podemos @RequestMappingadicioná-lo diretamente ao nome da classe para adicionar um prefixo de caminho a todos os mapeamentos de solicitação nesta classe, como:

@Controller
@RequestMapping("/yyds")
public class MainController {
    
    

    @RequestMapping({
    
    "/index", "/test"})
    public ModelAndView index(){
    
    
        return new ModelAndView("index");
    }
}

Então agora precisamos acessar /yyds/indexou /yyds/testobter esta página. Podemos visualizar diretamente todos os mapeamentos de solicitação definidos pelo aplicativo da web atual na seção de endpoint abaixo do IDEA e podemos acessar diretamente um determinado caminho por meio do cliente da web integrado fornecido pelo IDEA.

Os caminhos também suportam correspondência de curingas:

  • ?: Representa qualquer caractere, como @RequestMapping("/index/x?")/index/xa, /index/xb, etc.
  • *: Indica que quaisquer caracteres 0-n, como @RequestMapping("/index/*")/index/lbwnb, /index/yyds, etc., podem ser correspondidos.
  • **: Indica o diretório atual ou um diretório multinível baseado no diretório atual, por exemplo, @RequestMapping("/index/**")pode corresponder a /index, /index/xxx, etc.

Vejamos o próximo atributo de método. Como o nome sugere, é o tipo de método da solicitação. Podemos limitar o método de solicitação, como:

@RequestMapping(value = "/index", method = RequestMethod.POST)
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

Agora, se usarmos diretamente o navegador para acessar esta página, ele mostrará o método 405 não suportado, porque o padrão do navegador é usar diretamente o método GET para obter a página, e aqui especificamos o método POST para acessar este endereço, então o acesso falhar, agora vamos para o endpoint. Use o método POST para acessar e obter a página com sucesso.

imagem-20230220152559862

Também podemos usar anotações derivadas para definir diretamente mapeamentos de solicitações para tipos especificados:

@PostMapping(value = "/index")
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

O mapeamento de solicitação especificado diretamente como o tipo de solicitação POST é usado aqui @PostMapping. Da mesma forma, há também @GetMappingo método de solicitação GET que pode ser especificado diretamente, que não será listado aqui.

Podemos usar paramsatributos para especificar quais parâmetros de solicitação a solicitação deve transportar, como:

@RequestMapping(value = "/index", params = {
    
    "username", "password"})
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

Por exemplo, aqui exigimos que a solicitação carregue usernameatributos password, caso contrário ela não poderá ser acessada. Também suporta expressões, por exemplo podemos escrever assim:

@RequestMapping(value = "/index", params = {
    
    "!username", "password"})
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

Adicionar um ponto de exclamação antes do nome de usuário indica que a solicitação não tem permissão para transportar este parâmetro, caso contrário não será acessível. Podemos até definir um valor fixo diretamente:

@RequestMapping(value = "/index", params = {
    
    "username!=test", "password=123"})
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

Desta forma, o parâmetro de solicitação nome de usuário não pode ser testado, e a senha deve ser 123, caso contrário o acesso não será possível.

headerO uso do atributo é paramsigual ao do atributo, mas requer qual conteúdo precisa ser transportado no cabeçalho da solicitação, como:

@RequestMapping(value = "/index", headers = "!Connection")
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

Então, se o cabeçalho da solicitação contiver Connectionatributos, ele não estará acessível. Duas outras propriedades:

  • consome: Especifique o tipo de conteúdo de envio (Content-Type) para processamento de solicitações, como application/json, text/html;
  • produz: especifica o tipo de conteúdo a ser retornado. Será retornado somente se o tipo (Accept) no cabeçalho da solicitação contiver o tipo especificado;

Explicação detalhada de @RequestParam e @RequestHeader

Vamos dar uma olhada em como obter os parâmetros na solicitação.

Precisamos apenas adicionar um parâmetro formal ao método e adicionar @RequestParamuma anotação na frente do parâmetro formal:

@RequestMapping(value = "/index")
public ModelAndView index(@RequestParam("username") String username){
    
    
    System.out.println("接受到请求参数:"+username);
    return new ModelAndView("index");
}

Precisamos @RequestParampreencher o nome do parâmetro e o valor do parâmetro será passado automaticamente para o parâmetro formal. Podemos usá-lo diretamente no método. Observe que se o nome do parâmetro for igual ao nome do parâmetro formal, o parâmetro o valor pode ser obtido mesmo sem adicioná-lo @RequestParam.

Uma vez adicionado @RequestParam,a solicitação deve conter os parâmetros especificados. Também podemos definir o atributo require como false para tornar o atributo não obrigatório:

@RequestMapping(value = "/index")
public ModelAndView index(@RequestParam(value = "username", required = false) String username){
    
    
    System.out.println("接受到请求参数:"+username);
    return new ModelAndView("index");
}

Também podemos definir diretamente um valor padrão. Quando o parâmetro de solicitação estiver faltando, o valor padrão pode ser usado diretamente:

@RequestMapping(value = "/index")
public ModelAndView index(@RequestParam(value = "username", required = false, defaultValue = "伞兵一号") String username){
    
    
    System.out.println("接受到请求参数:"+username);
    return new ModelAndView("index");
}

Se você precisar usar algumas das classes Servlet originais, como:

@RequestMapping(value = "/index")
public ModelAndView index(HttpServletRequest request){
    
    
    System.out.println("接受到请求参数:"+request.getParameterMap().keySet());
    return new ModelAndView("index");
}

Basta adicioná-lo diretamente HttpServletRequestcomo um parâmetro formal. SpringMVC passará automaticamente o HttpServletRequestobjeto original da solicitação. Da mesma forma, também podemos adicioná-lo HttpServletResponsecomo um parâmetro formal, ou até mesmo passar HttpSession diretamente como um parâmetro:

@RequestMapping(value = "/index")
public ModelAndView index(HttpSession session){
    
    
    System.out.println(session.getAttribute("test"));
    session.setAttribute("test", "鸡你太美");
    return new ModelAndView("index");
}

Também podemos passar parâmetros de solicitação diretamente para uma classe de entidade:

@Data
public class User {
    
    
    String username;
    String password;
}

Observe que todos os parâmetros devem ser incluídos no método set ou construtor. Os parâmetros da solicitação serão automaticamente correspondidos de acordo com os nomes dos campos na classe:

@RequestMapping(value = "/index")
public ModelAndView index(User user){
    
    
    System.out.println("获取到cookie值为:"+user);
    return new ModelAndView("index");
}

@RequestHeaderO uso é @RequestParamconsistente, mas é usado para obter parâmetros de cabeçalho de solicitação e não será demonstrado aqui.

@CookieValue e@SessionAttrbutie

Usando @CookieValueanotações, também podemos obter rapidamente as informações do cookie transportadas pela solicitação:

@RequestMapping(value = "/index")
public ModelAndView index(HttpServletResponse response,
                          @CookieValue(value = "test", required = false) String test){
    
    
    System.out.println("获取到cookie值为:"+test);
    response.addCookie(new Cookie("test", "lbwnb"));
    return new ModelAndView("index");
}

Da mesma forma, a Sessão também pode ser obtida rapidamente usando anotações:

@RequestMapping(value = "/index")
public ModelAndView index(@SessionAttribute(value = "test", required = false) String test,
                          HttpSession session){
    
    
    session.setAttribute("test", "xxxx");
    System.out.println(test);
    return new ModelAndView("index");
}

Verifica-se que ao utilizar o framework SpringMVC, o desenvolvimento de toda a aplicação web se torna muito simples. A maioria das funções requer apenas uma anotação. É graças ao framework Spring que o SpringMVC pode mostrar seus talentos.

Redirecionamentos e encaminhamento de solicitação

Redirecionamento e encaminhamento de solicitação também são muito simples, só precisamos adicionar um prefixo na frente do nome da visualização, como redirecionamento:

@RequestMapping("/index")
public String index(){
    
    
    return "redirect:home";
}

@RequestMapping("/home")
public String home(){
    
    
    return "home";
}

Ao adicionar redirect:um prefixo, o redirecionamento pode ser facilmente implementado. Quanto ao encaminhamento de solicitação, na verdade é o mesmo. Use o forward:prefixo para indicar o encaminhamento para outros mapeamentos de solicitação:

@RequestMapping("/index")
public String index(){
    
    
    return "forward:home";
}

@RequestMapping("/home")
public String home(){
    
    
    return "home";
}

Usando SpringMVC, apenas um prefixo é necessário para implementar o redirecionamento e o encaminhamento de solicitação, o que é muito conveniente.

Escopo da web de beans

Ao aprender Spring, explicamos o escopo do Bean, incluindo singletone prototype. Bean será criado em modos de instância única e de instância múltipla, respectivamente. No SpringMVC, seu escopo continua a ser subdividido:

  • solicitação: para cada solicitação HTTP, o Bean definido usando o escopo da solicitação gerará uma nova instância e o Bean desaparecerá após a conclusão da solicitação.
  • Sessão: Para cada sessão, um Bean definido usando o escopo da sessão gerará uma nova instância e o Bean desaparecerá após a expiração da sessão.
  • sessão global: não é comumente usada, nenhuma explicação será dada.

Aqui criamos uma classe de teste para testar:

public class TestBean {
    
    

}

Em seguida registre-o como um Bean. Observe que você precisa adicionar @RequestScopeou @SessionScopeindicar o escopo Web deste Bean:

@Bean
@RequestScope
public TestBean testBean(){
    
    
    return new TestBean();
}

Então injetamos automaticamente no Controller:

@Controller
public class MainController {
    
    

    @Resource
    TestBean bean;

    @RequestMapping(value = "/index")
    public ModelAndView index(){
    
    
        System.out.println(bean);
        return new ModelAndView("index");
    }
}

Descobrimos que a instância do Bean obtida era diferente a cada vez, e então alteramos seu escopo para @SessionScopeque o escopo fosse elevado para Sessão.Desde que os cookies do navegador sejam limpos, será considerada a mesma sessão, desde que seja na mesma sessão, a instância do Bean permanece inalterada.

Na verdade, ele também é implementado através de um proxy.Os métodos que chamamos no Bean serão encaminhados para o objeto Bean real para execução.


Estilo repousante

A definição chinesa é "Transição de estado da camada de apresentação" (o nome é bastante sofisticado). Não é um padrão, mas um estilo de design. Sua principal função é utilizar completa e corretamente as características do protocolo HTTP e padronizar o caminho URI para aquisição de recursos. Em termos leigos, o design de estilo RESTful permite que parâmetros sejam passados ​​ao servidor por meio de emenda de URL. O objetivo é fazer com que a URL pareça mais concisa e prática, e podemos fazer uso completo de uma variedade de métodos de solicitação HTTP (POST/GET/ PUT/DELETE) para executar diferentes tipos de operações no mesmo endereço de solicitação.

Portanto, com esse estilo de conexão, podemos ler parâmetros diretamente do caminho da requisição, como:

http://localhost:8080/mvc/index/123456

Podemos processar diretamente o caminho de próximo nível do índice como um parâmetro de solicitação, o que significa que os parâmetros de solicitação atuais estão incluídos no caminho da solicitação:

@RequestMapping("/index/{str}")
public String index(@PathVariable String str) {
    
    
    System.out.println(str);
    return "index";
}

Observe que podemos adicionar manualmente informações semelhantes a espaços reservados ao caminho da solicitação, de modo que tudo na posição do espaço reservado seja usado como parâmetros de solicitação, e a lista formal de parâmetros do método deve incluir um parâmetro com o mesmo nome do espaço reservado e anotações adicionados @PathVariable. Parâmetros ou @PathVariableespecificados como nomes de espaço reservado por anotações:

@RequestMapping("/index/{str}")
public String index(@PathVariable("str") String text){
    
    
    System.out.println(text);
    return "index";
}

Se não estiver configurado corretamente, aparecerá uma linha amarela no nome do método.

Podemos dividi-lo de acordo com diferentes funções:

  • POST http://localhost:8080/mvc/index - adiciona informações do usuário e carrega dados do formulário
  • GET http://localhost:8080/mvc/index/{id} - Obtém informações do usuário, o id é colocado diretamente no caminho da solicitação
  • PUT http://localhost:8080/mvc/index - Modifica as informações do usuário e transporta os dados do formulário
  • DELETE http://localhost:8080/mvc/index/{id} - exclui informações do usuário, o id é colocado diretamente no caminho da solicitação

Escrevemos quatro mapeamentos de solicitação respectivamente:

@Controller
public class MainController {
    
    

    @RequestMapping(value = "/index/{id}", method = RequestMethod.GET)
    public String get(@PathVariable("id") String text){
    
    
        System.out.println("获取用户:"+text);
        return "index";
    }

    @RequestMapping(value = "/index", method = RequestMethod.POST)
    public String post(String username){
    
    
        System.out.println("添加用户:"+username);
        return "index";
    }

    @RequestMapping(value = "/index/{id}", method = RequestMethod.DELETE)
    public String delete(@PathVariable("id") String text){
    
    
        System.out.println("删除用户:"+text);
        return "index";
    }

    @RequestMapping(value = "/index", method = RequestMethod.PUT)
    public String put(String username){
    
    
        System.out.println("修改用户:"+username);
        return "index";
    }
}

Este é apenas um estilo de design, basta entendê-lo.


Interceptador interceptador

Os interceptadores são uma parte importante de todo o SpringMVC. Os interceptadores são semelhantes aos filtros e são usados ​​para interceptar algumas solicitações ilegais. No entanto, os filtros que explicamos antes atuam em Servlets e só podem ser alcançados com sucesso após passar por camadas de filtros. Servlet e o interceptor não está antes do Servlet, está entre o Servlet e o RequestMapping, o que equivale ao DispatcherServlet interceptar a requisição antes de entregá-la ao método no Controller correspondente. Ele interceptará apenas todas as requisições correspondentes ao mapeamento de requisições definido na solicitação Controller.. (recursos estáticos não serão interceptados), a diferença entre os dois deve ser distinguida aqui.

imagem-20230630194651686

Criar interceptador

Para criar um interceptor precisamos implementar uma HandlerInterceptorinterface:

public class MainInterceptor implements HandlerInterceptor {
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        System.out.println("我是处理之前!");
        return true;   //只有返回true才会继续,否则直接结束
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    
        System.out.println("我是处理之后!");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
      	//在DispatcherServlet完全处理完请求后被调用
        System.out.println("我是完成之后!");
    }
}

Então precisamos nos registrar na classe de configuração:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    
    
    registry.addInterceptor(new MainInterceptor())
      .addPathPatterns("/**")    //添加拦截器的匹配路径,只要匹配一律拦截
      .excludePathPatterns("/home");   //拦截器不进行拦截的路径
}

Agora acessamos a página de índice no navegador e o interceptor entrou em vigor.

Obtenha a ordem de execução dos interceptores de finalização:

我是处理之前!
我是处理!
我是处理之后!
我是完成之后!

Em outras palavras, antes e depois do processamento, o processamento de mapeamento de solicitação real é incluído, e o método é executado uma vez após todo o processo.Na verdade, todo o processo é afterCompletionsemelhante ao Filtro que conhecíamos antes, mas antes do processamento, só precisamos Basta retornar verdadeiro ou falso para indicar se foi interceptado, em vez de usar FilterChain para transmiti-lo.

Então, vamos dar uma olhada no que acontecerá se false for retornado antes do processamento:

我是处理之前!

Através dos resultados, descobrimos que uma vez retornado falso, todos os processos subsequentes serão cancelados. E se ocorrer uma exceção durante o processamento?

@RequestMapping("/index")
public String index(){
    
    
    System.out.println("我是处理!");
    if(true) throw new RuntimeException("");
    return "index";
}

O resultado é:

我是处理之前!
我是处理!
我是完成之后!

Descobrimos que se uma exceção for lançada durante o processamento, o postHandlemétodo de pós-processamento não será executado, mas afterCompletiono método será executado. Podemos obter a exceção lançada neste método.

interceptador multiestágio

O artigo anterior introduziu a situação em que existe apenas um interceptor. Vejamos como ele será executado se houver vários interceptores. Criamos o segundo interceptador da mesma maneira:

public class SubInterceptor implements HandlerInterceptor {
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        System.out.println("二号拦截器:我是处理之前!");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    
        System.out.println("二号拦截器:我是处理之后!");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
        System.out.println("二号拦截器:我是完成之后!");
    }
}

Registre o segundo interceptador:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    
    
  	//一号拦截器
    registry.addInterceptor(new MainInterceptor()).addPathPatterns("/**").excludePathPatterns("/home");
  	//二号拦截器
    registry.addInterceptor(new SubInterceptor()).addPathPatterns("/**");
}

Observe que a ordem de interceptação é a ordem de registro, portanto os interceptadores serão executados sequencialmente de acordo com a ordem de registro. Podemos abrir o navegador e executá-lo uma vez:

一号拦截器:我是处理之前!
二号拦截器:我是处理之前!
我是处理!
二号拦截器:我是处理之后!
一号拦截器:我是处理之后!
二号拦截器:我是完成之后!
一号拦截器:我是完成之后!

postHandleAssim como o filtro multinível, antes do processamento, as interceptações são realizadas na ordem de frente para trás, mas após a conclusão do processamento, os métodos de pós-processamento são executados na ordem inversa e, após a conclusão, são executados na mesma ordem inversa. ordem após todas as execuções.

E daí se o interceptor nº 1 retornar falso antes do processamento?

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
    System.out.println("一号拦截器:我是处理之前!");
    return false;
}

O resultado é o seguinte:

一号拦截器:我是处理之前!

Descobrimos que, assim como no caso de um único interceptador, uma vez que o interceptor retorne falso, ele não continuará mais, independentemente de haver um interceptor ou não.

Manipulação de exceção

Quando ocorre uma exceção em nosso método de mapeamento de solicitação, ela será exibida diretamente na página front-end. Isso ocorre porque o SpringMVC nos fornece uma página padrão de tratamento de exceções. Quando ocorre uma exceção, nossa solicitação será transferida diretamente para uma exceção dedicada. página de manipulação.controlador para processamento.

Podemos personalizar um controlador de tratamento de exceções. Assim que ocorrer uma exceção especificada, ela será transferida para este controlador para execução:

@ControllerAdvice
public class ErrorController {
    
    

    @ExceptionHandler(Exception.class)
    public String error(Exception e, Model model){
    
      //可以直接添加形参来获取异常
        e.printStackTrace();
        model.addAttribute("e", e);
        return "500";
    }
}

Em seguida, escrevemos uma página especificamente para exibir exceções:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  500 - 服务器出现了一个内部错误QAQ
  <div th:text="${e}"></div>
</body>
</html>

Em seguida, modifique:

@RequestMapping("/index")
public String index(){
    
    
    System.out.println("我是处理!");
    if(true) throw new RuntimeException("您的氪金力度不足,无法访问!");
    return "index";
}

Após o acesso, descobrimos que o console gerará informações de exceção e a página também é uma página customizada.

Formato de dados JSON e solicitações Axios

JSON (JavaScript Object Notation, JS Object Notation) é um formato leve de troca de dados.

O que defendemos agora é o modelo de desenvolvimento que separa o front-end e o back-end, em vez de entregar todo o conteúdo ao back-end para renderização e depois enviá-lo ao navegador. Em outras palavras, o conteúdo de toda a página Web é escrito em o início, e os dados nele são O front-end executa o código JS para obtê-lo dinamicamente do servidor e, em seguida, renderiza (preenche) o front-end. Isso pode reduzir bastante a pressão no back-end, e o back-end só precisa transmitir dados importantes (no próximo estágio do SpringBoot, iremos adotar completamente um modelo de desenvolvimento com separação de front-end e back-end)

Formato de dados JSON

Como queremos realizar a separação entre front-end e back-end, devemos concordar com um modo de transmissão de dados mais eficiente para transmitir os dados fornecidos pelo back-end para a página front-end. Assim nasceu o JSON. É muito fácil de entender e tem excelente compatibilidade com o front end. Portanto, o método de transmissão de dados mais convencional agora é realizado através do formato JSON.

Um dado no formato JSON tem a seguinte aparência, tomando o objeto aluno como exemplo:

{
    
    "name": "杰哥", "age": 18}

Vários alunos podem ser representados na forma de uma matriz:

[{
    
    "name": "杰哥", "age": 18}, {
    
    "name": "阿伟", "age": 18}]

O relacionamento aninhado pode ser expresso como:

{
    
    "studentList": [{
    
    "name": "杰哥", "age": 18}, {
    
    "name": "阿伟", "age": 18}], "count": 2}

Inclui diretamente o nome e o valor do atributo, que é muito semelhante ao objeto do JavaScript. Depois de chegar ao front-end, ele pode ser convertido diretamente em um objeto, e a operação e o conteúdo são lidos na forma de um objeto , o que equivale a expressá-lo na forma de uma string. Um objeto JS que podemos testar diretamente na janela do console:

let obj = JSON.parse('{"studentList": [{"name": "杰哥", "age": 18}, {"name": "阿伟", "age": 18}], "count": 2}')
//将JSON格式字符串转换为JS对象
obj.studentList[0].name   //直接访问第一个学生的名称

Também podemos converter objetos JS em strings JSON:

JSON.stringify(obj)

Nosso backend pode retornar dados para o frontend na forma de uma string JSON, para que depois que o frontend obtiver os dados, ele possa obtê-los rapidamente, o que é muito conveniente.

Então, como o back-end cria dados rapidamente no formato JSON? Primeiro precisamos importar as seguintes dependências:

<dependency>
      <groupId>com.alibaba.fastjson2</groupId>
      <artifactId>fastjson2</artifactId>
      <version>2.0.34</version>
</dependency>

Existem muitas estruturas de análise JSON, as mais comumente usadas são Jackson e FastJSON. Aqui usamos FastJSON do Alibaba para análise, que atualmente é conhecido como a estrutura de análise JSON mais rápida, e a versão FastJSON 2 já foi lançada.

A primeira coisa a apresentar é JSONObject, que é usado da mesma forma que Map e é ordenado (implementando a interface LinkedHashMap), por exemplo, armazenamos vários dados nele:

@RequestMapping(value = "/index")
public String index(){
    
    
    JSONObject object = new JSONObject();
    object.put("name", "杰哥");
    object.put("age", 18);
    System.out.println(object.toJSONString());   //以JSON格式输出JSONObject字符串
    return "index";
}

O resultado final que obtemos é:

{
    
    "name": "杰哥", "age": 18}

Na verdade, JSONObject é uma representação de objeto de dados JSON. Há também JSONArray, que representa um array e é usado da mesma forma que List. Outros JSONObjects ou JSONArrays podem ser aninhados no array:

@RequestMapping(value = "/index")
public String index(){
    
    
    JSONObject object = new JSONObject();
    object.put("name", "杰哥");
    object.put("age", 18);
    JSONArray array = new JSONArray();
    array.add(object);
    System.out.println(array.toJSONString());
    return "index";
}

O resultado é:

[{
    
    "name": "杰哥", "age": 18}]

Quando ocorrer uma referência circular, ela será analisada de acordo com a seguinte sintaxe:

imagem

Também podemos criar diretamente uma classe de entidade e convertê-la em dados no formato JSON:

@RequestMapping(value = "/index", produces = "application/json")
@ResponseBody
public String data(){
    
    
    Student student = new Student();
    student.setName("杰哥");
    student.setAge(18);
    return JSON.toJSONString(student);
}

Aqui modificamos produceso valor e definimos o tipo de conteúdo retornado como , indicando que o servidor retornou dados no formato JSON (claro, não há problema em não defini-los e também pode ser exibido, isso é para fins de padronização) e em seguida, adicionamos uma representação application/jsonao método. indicar que este controlador retorna dados de string por padrão) não é o nome da visualização, mas precisa retornar diretamente uma string como dados da página. Desta forma, o que é retornado ao navegador é o caractere que retornamos diretamente.string content.@ResponseBody@RestController

Em seguida, usamos a classe de ferramenta JSON para convertê-lo em uma string de formato JSON, abrir o navegador e obter os dados no formato JSON.

SpringMVC é muito inteligente, podemos retornar diretamente um tipo de objeto, que será automaticamente convertido para o formato de string JSON:

@RequestMapping(value = "/data", produces = "application/json")
@ResponseBody
public Student data(){
    
    
    Student student = new Student();
    student.setName("杰哥");
    student.setAge(18);
    return student;
}

Observe que você precisa adicionar um conversor FastJSON à classe de configuração. Aqui você precisa primeiro adicionar uma dependência:

<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2-extension-spring6</artifactId>
    <version>2.0.34</version>
</dependency>

Em seguida, escreva a configuração:

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    
    
    converters.add(new FastJsonHttpMessageConverter());
}

Tente novamente e o conteúdo será automaticamente convertido para o formato JSON e enviado ao cliente.

Solicitação assíncrona do Axios

Anteriormente explicamos como enviar dados no formato JSON para o navegador, agora vamos dar uma olhada em como solicitar dados do servidor.

imagem

Nesta parte, voltaremos à introdução do conteúdo relacionado ao front-end. Claro, ainda estamos apenas entendendo e não precisamos aprender detalhadamente o conhecimento do desenvolvimento de projetos de front-end.

Por que o front-end precisa usar solicitações assíncronas? Isso ocorre porque nossas páginas da web são dinâmicas (dinâmico aqui não significa efeitos de animação, mas a capacidade de atualizar conteúdo em tempo real). Por exemplo, quando clicamos em um botão, novo conteúdo irá aparecer ou pular para Novas páginas, atualizar dados na página, etc. tudo precisa ser implementado por meio de JS para concluir solicitações assíncronas.

Solicitações assíncronas de front-end referem-se ao envio de solicitações ao servidor ou outros recursos no front-end sem bloquear a interface do usuário ou outras operações. Em uma solicitação síncrona tradicional, quando uma solicitação é enviada, o navegador aguarda a resposta do servidor, durante a qual o usuário não pode realizar outras operações. As solicitações assíncronas, por outro lado, permitem que o usuário continue outras operações enquanto aguarda uma resposta, enviando a solicitação para segundo plano. Este mecanismo melhora a experiência do usuário e permite que as páginas sejam atualizadas em tempo real. Os métodos comuns de solicitação assíncrona de front-end incluem o uso do objeto XMLHttpRequest, a API Fetch e o uso do método AJAX na biblioteca jQuery, bem como a estrutura Axios mais comumente usada atualmente.

Suponha que temos dados no backend que precisam ser atualizados em tempo real (mudanças ao longo do tempo) e agora precisam ser atualizados e exibidos em tempo real no frontend. Aqui tomamos o simples uso da estrutura axios como exemplo para mostrar você como iniciar uma solicitação assíncrona e atualizar nossa página.

A primeira é a página inicial, basta copiar a lição de casa:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script>
</head>
<body>
  <p>欢迎来到GayHub全球最大同性交友网站</p>
  <p>用户名: <span id="username"></span></p>
  <p>密码: <span id="password"></span></p>
</body>
</html>

Em seguida, usamos a estrutura axios para solicitar dados JSON diretamente do backend:

<script>
    function getInfo() {
      
      
        axios.get('/mvc/test').then(({
       
       data}) => {
      
      
            document.getElementById('username').innerText = data.username
            document.getElementById('password').innerText = data.password
        })
    }
</script>

Desta forma, conseguimos a aquisição de dados do servidor e os atualizamos na página. Os desenvolvedores front-end usam JS para iniciar solicitações assíncronas, que podem atingir vários efeitos, enquanto nossos desenvolvedores back-end só precisam se preocupar com o retorno da interface dados corretos. No entanto, isso já possui o protótipo de desenvolvimento front-end e back-end separados (na verdade, demonstramos isso antes de usar solicitações XHR no estágio JavaWeb, mas naquela época eram dados puros)

Então vamos ver como enviar dados de um objeto JS para o servidor e analisá-los. Aqui tomamos um login simples como exemplo:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script>
</head>
<body>
  <p>欢迎来到GayHub全球最大同性交友网站</p>
  <button onclick="login()">立即登录</button>
</body>
</html>

Aqui ainda usamos axios para enviar solicitação POST:

<script>
    function login() {
      
      
        axios.post('/mvc/test', {
      
      
            username: 'test',
            password: '123456'
        }, {
      
      
            headers: {
      
      
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }).then(({
       
       data}) => {
      
      
            if(data.success) {
      
      
                alert('登录成功')
            } else {
      
      
                alert('登录失败')
            }
        })
    }
</script>

O servidor só precisa adicionar um objeto para receber na posição do parâmetro request (o mesmo de antes, pois aqui também estão os dados do formulário enviado):

@ResponseBody
@PostMapping(value = "/test", produces = "application/json")
public String hello(String username, String password){
    
    
    boolean success = "test".equals(user.getUsername()) && "123456".equals(user.getPassword());
    JSONObject object = new JSONObject();
    object.put("success", success);
    return object.toString();
}

Também podemos converter objetos js em strings JSON para transmissão. Aqui precisamos usar o método ajax para processar:

<script>
    function login() {
      
      
        axios.post('/mvc/test', {
      
      
            username: 'test',
            password: '123456'
        }).then(({
       
       data}) => {
      
      
            if(data.success) {
      
      
                alert('登录成功')
            } else {
      
      
                alert('登录失败')
            }
        })
    }
</script>

Se precisarmos ler os dados no formato JSON enviados pelo front-end, precisaremos adicionar @RequestBodyanotações neste momento:

@ResponseBody
@PostMapping(value = "/test", produces = "application/json")
public String hello(@RequestBody User user){
    
    
    boolean success = "test".equals(user.getUsername()) && "123456".equals(user.getPassword());
    JSONObject object = new JSONObject();
    object.put("success", success);
    return object.toString();
}

Dessa forma, conseguimos a comunicação entre o front e o backend usando strings JSON.

Implementar upload e download de arquivos

Usando SpringMVC, podemos implementar facilmente o upload e download de arquivos. Precisamos adicionar um novo método no MainInitializer:

public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    

    ...

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
    
    
      	// 直接通过registration配置Multipart相关配置,必须配置临时上传路径,建议选择方便打开的
        // 同样可以设置其他属性:maxFileSize, maxRequestSize, fileSizeThreshold
        registration.setMultipartConfig(new MultipartConfigElement("/Users/nagocoler/Download"));
    }
}

Então podemos escrever o Controller diretamente:

@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public String upload(@RequestParam MultipartFile file) throws IOException {
    
    
    File fileObj = new File("test.png");
    file.transferTo(fileObj);
    System.out.println("用户上传的文件已保存到:"+fileObj.getAbsolutePath());
    return "文件上传成功!";
}

Por fim, adicione um ponto de upload de arquivo no front end:

<div>
    <form action="upload" method="post" enctype="multipart/form-data">
        <input type="file" name="file">
        <input type="submit">
    </form>
</div>

Desta forma, após clicar em enviar, o arquivo será carregado no servidor.

O download é praticamente igual ao que escrevemos antes, apenas use HttpServletResponse diretamente e transfira os dados para o fluxo de saída.

@RequestMapping(value = "/download", method = RequestMethod.GET)
@ResponseBody
public void download(HttpServletResponse response){
    
    
    response.setContentType("multipart/form-data");
    try(OutputStream stream = response.getOutputStream();
        InputStream inputStream = new FileInputStream("test.png")){
    
    
        IOUtils.copy(inputStream, stream);
    }catch (IOException e){
    
    
        e.printStackTrace();
    }
}

Adicione um ponto de download à página inicial:

<a href="download" download="test.png">下载最新资源</a>

Interpretar o código-fonte do DispatcherServlet

**Nota:** Esta parte é opcional!

Até agora, aprendemos quase tudo sobre SpringMVC, mas no final ainda precisamos ter um entendimento profundo de como o DispatcherServlet subjacente é agendado, então iremos explicá-lo da perspectiva do código-fonte.

Primeiro precisamos encontrar DispatcherServleto nível superior HttpServletBean, que é herdado diretamente aqui HttpServlet, então vamos primeiro dar uma olhada no que ele faz no método de inicialização:

public final void init() throws ServletException {
    
    
  	//读取配置参数,并进行配置
    PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
    
    
        try {
    
    
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
            this.initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        } catch (BeansException var4) {
    
    
            if (this.logger.isErrorEnabled()) {
    
    
                this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
            }

            throw var4;
        }
    }
		//此初始化阶段由子类实现,
    this.initServletBean();
}

Vejamos a seguir initServletBean()como o método é implementado, que é FrameworkServletdefinido na subclasse:

protected final void initServletBean() throws ServletException {
    
    
    this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
    
    
        this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
    }

    long startTime = System.currentTimeMillis();

    try {
    
    
      	//注意:我们在一开始说了SpringMVC有两个容器,一个是Web容器一个是根容器
      	//Web容器只负责Controller等表现层内容
      	//根容器就是Spring容器,它负责Service、Dao等,并且它是Web容器的父容器。
      	//初始化WebApplicationContext,这个阶段会为根容器和Web容器进行父子关系建立
        this.webApplicationContext = this.initWebApplicationContext();
        this.initFrameworkServlet();
    } catch (RuntimeException | ServletException var4) {
    
    
      //...以下内容全是打印日志
}

imagem

Vamos dar uma olhada em initWebApplicationContextcomo ele é inicializado:

protected WebApplicationContext initWebApplicationContext() {
    
    
  	//这里获取的是根容器,一般用于配置Service、数据源等
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
    
    
      	//如果webApplicationContext在之前已经存在,则直接给到wac
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
    
    
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
            if (!cwac.isActive()) {
    
    
                if (cwac.getParent() == null) {
    
    
                  	//设定根容器为Web容器的父容器
                    cwac.setParent(rootContext);
                }

                this.configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }

    if (wac == null) {
    
    
      	//如果webApplicationContext是空,那么就从ServletContext找一下有没有初始化上下文
        wac = this.findWebApplicationContext();
    }

    if (wac == null) {
    
    
      	//如果还是找不到,直接创个新的,并直接将根容器作为父容器
        wac = this.createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
    
    
        synchronized(this.onRefreshMonitor) {
    
    
          	//此方法由DispatcherServlet实现
            this.onRefresh(wac);
        }
    }

    if (this.publishContext) {
    
    
        String attrName = this.getServletContextAttributeName();
      	//把Web容器丢进ServletContext
        this.getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

Vejamos a seguir os métodos implementados em DispatcherServlet onRefresh():

@Override
protected void onRefresh(ApplicationContext context) {
    
    
    initStrategies(context);
}
    
protected void initStrategies(ApplicationContext context) {
    
    
  	//初始化各种解析器
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
  	//在容器中查找所有的HandlerMapping,放入集合中
  	//HandlerMapping保存了所有的请求映射信息(Controller中定义的),它可以根据请求找到处理器Handler,但并不是简单的返回处理器,而是将处理器和拦截器封装,形成一个处理器执行链(类似于之前的Filter)
    initHandlerMappings(context);
  	//在容器中查找所有的HandlerAdapter,它用于处理请求并返回ModelAndView对象
  	//默认有三种实现HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter
  	//当HandlerMapping找到处理请求的Controller之后,会选择一个合适的HandlerAdapter处理请求
  	//比如我们之前使用的是注解方式配置Controller,现在有一个请求携带了一个参数,那么HandlerAdapter会对请求的数据进行解析,并传入方法作为实参,最后根据方法的返回值将其封装为ModelAndView对象
    initHandlerAdapters(context);
  	//其他的内容
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

Já entendemos o processo de inicialização do DispatcherServlet, então vamos dar uma olhada em como o DispatcherServlet é agendado. Primeiro, nossa solicitação passará definitivamente e depois HttpServletserá entregue aos métodos correspondentes doGet, doPost e outros métodos de processamento. Em FrameworkServletReescreva-o e use-o processRequestpara processamento:

protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    this.processRequest(request, response);
}

protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    this.processRequest(request, response);
}

Vamos ver o que processRequestfoi feito :

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
  	//前期准备工作
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = this.buildLocaleContext(request);
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
    this.initContextHolders(request, localeContext, requestAttributes);

    try {
    
    
      	//重点在这里,这里进行了Service的执行,不过是在DispatcherServlet中定义的
        this.doService(request, response);
    } catch (IOException | ServletException var16) {
    
    
        //...
}

Por favor, seja paciente. As camadas inferiores dessas estruturas grandes geralmente são camadas de bonecas matryoshka, porque a hierarquia ficará mais clara quando escrita desta forma. Então, vamos dar uma DispatcherServletolhada em

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
   //...
    try {
    
    
      	//重点在这里,这才是整个处理过程中最核心的部分
        this.doDispatch(request, response);
    } finally {
    
    
        //...
}

Finalmente encontrei a parte principal:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
    
    
        try {
    
    
            ModelAndView mv = null;
            Object dispatchException = null;

            try {
    
    
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
              	//在HandlerMapping集合中寻找可以处理当前请求的HandlerMapping
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
    
    
                    this.noHandlerFound(processedRequest, response);
                  	//找不到HandlerMapping则无法进行处理
                    return;
                }

              	//根据HandlerMapping提供的信息,找到可以处理的HandlerAdapter
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                String method = request.getMethod();
                boolean isGet = HttpMethod.GET.matches(method);
                if (isGet || HttpMethod.HEAD.matches(method)) {
    
    
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
    
    
                        return;
                    }
                }

              	//执行所有拦截器的preHandle()方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    
    
                    return;
                }

              	//使用HandlerAdapter进行处理(我们编写的请求映射方法在这个位置才真正地执行了)
              	//HandlerAdapter会帮助我们将请求的数据进行处理,再来调用我们编写的请求映射方法
              	//最后HandlerAdapter会将结果封装为ModelAndView返回给mv
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
    
    
                    return;
                }

                this.applyDefaultViewName(processedRequest, mv);
              	//执行所有拦截器的postHandle()方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
    
    
                dispatchException = var20;
            } catch (Throwable var21) {
    
    
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }

          	//最后处理结果,对视图进行渲染等,如果抛出异常会出现错误页面
            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } catch (Exception var22) {
    
    
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } catch (Throwable var23) {
    
    
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }

    } finally {
    
    
        if (asyncManager.isConcurrentHandlingStarted()) {
    
    
            if (mappedHandler != null) {
    
    
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
    
    
            this.cleanupMultipart(processedRequest);
        }

    }
}

Portanto, com base na análise do código-fonte acima, o fluxograma final é obtido:

imagem

Embora depois de concluir este capítulo, tenhamos basicamente conseguido reescrever um sistema de gerenciamento de biblioteca mais avançado baseado em Spring, mas o complexo problema de verificação de login ainda não foi resolvido. Se ainda escrevermos a verificação de login da maneira anterior, é obviamente muito simples. É apenas um login, mas sem qualquer divisão de permissão ou processamento de criptografia. Precisamos de uma estrutura de verificação de permissão mais avançada para nos ajudar a implementar a operação de login. No próximo capítulo, explicaremos em detalhes como usar o mais avançado Estrutura SpringSecurity.ASD.

Acho que você gosta

Origin blog.csdn.net/qq_25928447/article/details/131562205
Recomendado
Clasificación