Spring AOP、Spring MVC工作原理、发展演变、常用注解

Spring AOP

概念

AOP全称为Aspect Oriented Programming,表示面向切面编程。切面指的是将那些与业务无关,但业务模块都需要使用的功能封装起来的技术。

AOP基本术语

**连接点(Joinpoint):**连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。连接点由两个信息确定:

  • 方法( 表示程序执行点,即在哪个目标方法)
  • 相对点(表示方位,即目标方法的什么位置,比如调用前,后等)

切入点(Pointcut): 一般认为,所有的方法都可以认为是连接点,但是我们并不希望在所有的方法上都添加通知,而切入点的作用就是提供一组规则来匹配连接点,给满足规则的连接点添加通知。

**通知、增强(Advice) : **可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知、最终通知等。

**目标对象(Target)**目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。

**织入(Weaving):**织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。

**代理(Proxy):**被AOP织入通知后,产生的结果类。

**切面(Aspect):*切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。切面由切入点和通知组成。Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。

应用

配置pom文件:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.11</version>
</dependency>
<!-- 切面相关的包 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>

编写业务层:

接口:

public interface UserService {
    int saveUser(Map<String,Object> params);
}

实现类:

public class UserServiceImpl implements UserService{
    @Override
    public int saveUser(Map<String, Object> params) {
        System.out.println("保存用户信息" + params);
        return 0;
    }
}

配置业务层:

spring-aop.xml:

<!--业务层对象-->
    <bean id="userService" class="com.qf.aop.service.UserServiceImpl"/>

编写通知类:
通知分为前置通知、后置通知、异常抛出通知、环绕通知、最终通知(没什么用这里不实现)。

前置通知:

接口为MethodBeforeAdvice,其底层实现如下:

public interface MethodBeforeAdvice extends BeforeAdvice {

	/**
	 * Callback before a given method is invoked.
	 */
	void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}

使用前置通知需要实现这个接口:

public class BeforeAdvice implements MethodBeforeAdvice{
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        String methodName = method.getName();
        String className = method.getDeclaringClass().getName();
        System.out.println("准备执行方法:" + className + "." + methodName + "参数:" + Arrays.toString(args));
        
    }
}

写完通知类后需要配置通知:

spring-aop.xml:

<!--业务层对象-->
<bean id="userService" class="com.qf.aop.service.UserServiceImpl"/>


<!--配置通知对象-->
<bean id="before" class="com.qf.aop.advice.BeforeAdvice"/>

当通知对象和业务层对象都纳入IOC容器管理之后,需要将通知对象作用在业务层对象上。Spring提供了aop标签来完成这一功能。

<!--aop配置-->
    <aop:config>
        <!--
           pointcut表示切点,也就是通知会在哪些位置触发
           expression表示切点表达式,切点表达式必须是execution(), execution()方法中的参数必须配置到方法上
           比如 * com.qf.spring.aop.service..*(..)
           第一个 * 表示任意访问修饰符
           com.qf.spring.aop.service.. 最后的两个..表示service包下面的所有子包中的类
           *(..) 表示任意方法, 如果()中没有..,则表示不带参数的方法;有,就表示带任意参数
       -->
        <!--切入点配置-->
        <aop:pointcut id="pc" expression="execution(* com.qf.aop.service..*(..))"/>
        <!--通知配置-->
        <aop:advisor advice-ref="before" pointcut-ref="pc"/>
    </aop:config>
</beans>

测试:

public class AopTest {
    @Test
    public void saveUserTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-aop.xml");
        UserService userService = context.getBean("userService", UserService.class);
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","爱德华");
        map.put("sex","男");
        int i = userService.saveUser(map);
    }
}

注:利用ClassPathXmlApplicationContext拿到配置文件上下文对象,进而拿到bean对象。

后置通知接口:AfterReturningAdvice.

剩下的流程和前置接口相同,编写通知类,配置通知类对象,配置通知。

<!--配置通知对象-->
    <bean id="before" class="com.qf.aop.advice.BeforeAdvice"/>
    <bean id="after" class="com.qf.aop.advice.AfterAdvice"/>

    <!--aop配置-->
    <aop:config>
        
        <!--切入点配置-->
        <aop:pointcut id="pc" expression="execution(* com.qf.aop.service..*(..))"/>
        <!--通知配置-->
        <aop:advisor advice-ref="before" pointcut-ref="pc"/>
        <aop:advisor advice-ref="after" pointcut-ref="pc"/>
    </aop:config>
</beans>
异常抛出通知

异常抛出接口为ThrowsAdvice。

注意:异常通知类接口没有要重写的方法,而是自定义。

public class ExceptionAdvice implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex){
        String methodName = method.getName();
        String className = method.getDeclaringClass().getName();
        System.out.println("执行方法时:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",发生了异常:" + ex.getMessage());
    }
}

配置和前面相同:

<bean id="exception" class="com.qf.aop.advice.ExceptionAdvice" />



<aop:advisor advice-ref="exception" pointcut-ref="pc"/>
环绕通知

接口:MethodInterceptor

注意:1.这里重写的方法参数为MethodInvocation invocation,可以通过invocation.getMethod();//获取被拦截的方法。

public class AroundAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();//获取被拦截的方法对象
        Object[] args = invocation.getArguments();//获取方法的参数
        Object target = invocation.getThis();//获取代理对象
        String methodName = method.getName();
        String className = method.getDeclaringClass().getName();
        System.out.println("准备执行方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args));
        Object returnVal = method.invoke(target, args);
        System.out.println("执行完方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",得到返回值:" + returnVal);
        return returnVal;
    }
}

环绕通知可以实现前置通知、后置通知、异常抛出通知的功能,所以配置文件中只需要配置环绕通知即可。

<!--业务层对象-->
<bean id="userService" class="com.qf.aop.service.UserServiceImpl"/>


<!--配置通知对象-->

<bean id="around" class="com.qf.aop.advice.AroundAdvice"/>

<!--aop配置-->
<aop:config>

    <!--切入点配置-->
    <aop:pointcut id="pc" expression="execution(* com.qf.aop.service..*(..))"/>
    <!--通知配置-->

    <aop:advisor advice-ref="around" pointcut-ref="pc"/>
</aop:config>

AspectJ

简介:

AspectJ是一个面向切面的框架,它扩展了Java语言,定义了AOP 语法,能够在编译期提供代码的织入。Spring通过集成AspectJ实现了以注解的方式定义增强类,大大减少了配置文件中的工作量

注解:

  • @Aspect 切面标识
  • @Pointcut 切入点
  • @Before 前置通知
  • @AfterReturning 后置通知
  • @Around 环绕通知
  • @AfterThrowing 异常抛出通知

通知类编写:

package com.qf.aop.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.util.Arrays;

@Aspect
public class AspectJAdvice {

    @Before(value = "execution(* com.qf.aop.service..*(..))")
    public void before(JoinPoint jp){
        Signature signature = jp.getSignature();//获取签名
        Object[] args = jp.getArgs();//获取方法参数
        if(signature instanceof MethodSignature){
            //如果签名是方法签名
            Method method = ((MethodSignature) signature).getMethod();//获取方法
            String methodName = method.getName();
            String className = method.getDeclaringClass().getName();
            System.out.println("准备执行方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args));
        }
    }

    @AfterReturning(value = "execution(* com.qf.aop.service..*(..))", returning = "returnValue")
    public void after(JoinPoint jp, Object returnValue){
        Object[] args = jp.getArgs(); //获取方法参数
        Signature signature = jp.getSignature(); //获取签名
        if(signature instanceof MethodSignature){ //如果签名是方法签名
            Method method = ((MethodSignature) signature).getMethod(); //获取方法
            String methodName = method.getName();
            String className = method.getDeclaringClass().getName();
            System.out.println("执行完方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",得到返回值:" + returnValue);
        }
    }

    @AfterThrowing(value = "execution(* com.qf.aop.service..*(..))", throwing = "t")
    public void exception(JoinPoint jp, Throwable t){
        Object[] args = jp.getArgs(); //获取方法参数
        Signature signature = jp.getSignature(); //获取签名
        if(signature instanceof MethodSignature){ //如果签名是方法签名
            Method method = ((MethodSignature) signature).getMethod(); //获取方法
            String methodName = method.getName();
            String className = method.getDeclaringClass().getName();
            System.out.println("执行方法时:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",发生了异常:" + t.getMessage());
        }
    }

    @Around("execution(* com.qf.aop.service..*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();//获取方法的参数
        Object target = pjp.getTarget(); //获取代理对象
        Signature signature = pjp.getSignature(); //获取签名
        if(signature instanceof MethodSignature) { //如果签名是方法签名
            Method method = ((MethodSignature) signature).getMethod(); //获取被拦截的方法对象
            String methodName = method.getName();
            String className = method.getDeclaringClass().getName();
            try {
                System.out.println("准备执行方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args));
                Object returnValue = method.invoke(target, args);
                System.out.println("执行完方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",得到返回值:" + returnValue);
                return returnValue;
            } catch (Throwable t){
                System.out.println("执行方法时:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",发生了异常:" + t.getMessage());
                throw t;
            }
        }
        return null;
    }
}

启用注解支持:

<!--配置通知对象-->
<bean class="com.qf.aop.advice.AspectJAdvice"/> 


<!--启动AspectJ注解 自动为类生成代理-->
        <aop:aspectj-autoproxy proxy-target-class="true"/>

SpringMVC

简介

1. Spring MVC

SpringMVC是一个Java 开源框架, 是Spring Framework生态中的一个独立模块,它基于 Spring 实现了Web MVC(数据、业务与展现)设计模式的请求驱动类型的轻量级Web框架,为简化日常开发,提供了很大便利。

2. Spring MVC 核心组件

  • DispatcherServlet 前置控制器

    负责接收请求、分发请求

  • Handler 处理器

    处理器包括了拦截器、控制器中的方法等,主要负责处理请求

  • HandlerMapping 处理器映射器

    解析配置文件、扫描注解,将请求与处理器进行匹配

  • HandlerAdpter 处理器适配器

    根据请求来找到匹配的处理器,这个过程称为适配

  • ViewResolver 视图解析器

    处理器执行后得到的结果可能是一个视图,但这个视图属于逻辑视图(页面中存在逻辑代码,比如循环、判断),需要使用视图解器行处理,这个过程称为渲染视图

Spring MVC工作原理

在这里插入图片描述

mvc工作原理

前端发送的请求由DispatcherServlet接收到,然后根据提供的HandlerMapping来分发过去,在分发请求过程中使用到HandlerAdapter来适配处理器(因为处理的类型无法确定),找到对应的处理器适配器之后就会执行这个处理器,执行会得到一个ModelAndView,然后交给ViewResolver进行解析得到视图位置,然后对视图进行渲染,渲染完成后将渲染好的视图交给DispatcherServlet,然后传回前端展示。

Spring MVC发展演变

1.Bean的名字或ID匹配URL请求

由于版本更新,使用新版本会无法完成过时的功能,但是为了更好地理解演变的过程,这里使用低版本:

<!--低版本-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

首先需要再web.xml配置文件中配置DispatcherServlet,包括初始化参数(全局上下文,自己项目的配置文件路径以及使得项目启动时就创建servlet的初始化参数)

<servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

然后是自己写的spring-mvc.xml配置,这里需要写视图解析器、处理器映射器(处理器适配器采用默认的,在底层mvc框架中会根据处理器类型寻找合适的处理器适配器)。具体的逻辑为前端发送请求->处理器映射方式、配置控制器找到控制器->控制器返回modelandview->视图解析器解析路径找到jsp文件:

 <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--处理器映射的方式:使用bean的名字或者id的值来与请求匹配-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

    <!--通过id值匹配请求的URL-->
    <bean id="/user" class="com.qf.controller.UserController"/>

处理器映射器给出映射的方式:使用bean的名字或者id,然后DispatcherServlet找到处理器适配器,处理器适配器提供id和处理器的路径。然后底层会根据路径找到处理器。

处理器:

public class UserController extends AbstractController {
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        return new ModelAndView("user");
    }
}

处理器返回ModelAndView给适配器,DispatcherServlet根据id拿到返回的ModelAndView,并交给视图解析器进行处理,最终将处理好的路径进行渲染。

但是这样做有一个问题,每一个请求都需要一个控制器与之对应,如果有很多请求,那么就要写很多个控制器。开发效率极为低下,而Spring提供了方法名匹配请求来解决这个问题。

2.Bean方法名匹配请求

方法名解析器:InternalPathMethodNameResolver,将方法名作为匹配URL请求的依据,与控制器关联起来。

这样一来请求就可以直接与控制器中的方法关联,那么控制器中的方法就应该有多个。

多操作控制器:

MultiActionController控制器类,供其他控制器类继承,在其子类中可以编写多个处理请求的方法,然后使用方法名解析器去匹配请求。

控制器:

public class UserMultiController extends MultiActionController {

    //这个方法匹配/login请求
    public ModelAndView login(HttpServletRequest request, HttpServletResponse response){
        return new ModelAndView("login");
    }

    //这个方法匹配/register请求
    public ModelAndView register(HttpServletRequest request,HttpServletResponse response){
        return new ModelAndView("register");
    }
}

编写完控制器后需要写相应的控制器映射器(视图解析器不变):

spring-mvc.xml:

<!--方法名解析器,处理映射的方式:使用方法名-->
    <bean id="methodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver"/>
    
    <!--/login 请求使用该bean对象处理-->
    <bean id="/login" class="com.qf.controller.UserMultiController">
        <property name="methodNameResolver" ref="methodNameResolver"/>
    </bean>
    <!--/register 请求使用该bean对象处理-->
    <bean id="/register" class="com.qf.controller.UserMultiController">
        <property name="methodNameResolver" ref="methodNameResolver"/>
    </bean>
    

按照这种匹配请求的方式,如果一个控制器要处理多个请求,就会导致此配置文件无限扩展,变得冗杂,后期难以维护,这时如何解决?

Spring提供了SimpleUrlHandlerMapping映射器,该映射器支持一个控制器与多个请求匹配的同时也解决了配置信息繁多的问题。

3.简单URL处理器映射

在Bean方法名匹配请求方式的控制器不变的基础上,只需要改动控制器映射器即可:

spring-mvc.xml:

<!--使用简单URL处理器映射-->
        <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
            <property name="mappings">
                <props>
                    <prop key="/view">userController</prop>
                    <prop key="/user/*">userMultiController</prop>
                </props>

            </property>
        </bean>

        <bean id="userController" class="com.qf.controller.UserController"/>
        <bean id="userMultiController" class="com.qf.controller.UserMultiController"/>

随着业务的增加,控制器的数量也为增加,请求的匹配也会增多,xml文件里虽然减少了冗余,但每次增加方法也会增加代码量,如何解决?

-Spring提供了DefaultAnnotationHandlerMapping映射器,支持使用注解来匹配请求,这样就解决了请求匹配导致配置信息繁多的问题,同时还提升了开发效率。

注解匹配请求

控制器中通过@Controller注解说明这是一个处理器,方法中通过@RequestMapping注解注明映射信息。

controller:

@Controller
public class UserAnnotationController {
    @RequestMapping(value = "/login",method = RequestMethod.GET)
    public String login(){
        return "login";
    }
    
    @RequestMapping(value = "register",method = RequestMethod.GET)
    public String register(){
        return "register";
    }
}

注意:这次的controller不需要实现接口或者继承抽象类了,也就意味着可以自定义方法,只需要在方法上加注解就可以达到映射的效果。

写好处理器后需要配置(视图解析器还是不用变,只需要改变处理器映射器就行):
spring-mvc.xml:

<!--类上的注解处理器-->
        <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
        <!--方法上的注解处理器-->
        <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <!--扫描包,使得该包下类以及类中定义的方法上所使用的注解生效-->
        <context:component-scan base-package="com.qf.controller" />

新的版本配置
<!--较新的版本使用该标签启动注解支持-->
<mvc:annotation-driven/>
<!--扫描包,使类和类方法注解生效-->
<context:component-scan base-package="com.qf.controller"/>

相当于使用一句话代替了原来对类和方法上注解处理器的声明。


Spring MVC常用注解

@Controller

控制器的标识

@Controller
public class UserController{
    
}

@RequestMapping

该注解用于匹配请求(注明URI)

@Controller
@RequestMapping("/user")
public class UserController{
    
    @RequestMapping(value="/login", method=RequestMethod.POST)
    public int login(){
        return 1;
    }
}

@RequestBody

该方法只能用在方法的参数上,用于从请求体中获取数据并注入参数中,并且获取的数据只能是JSON格式的数据。

@Controller
@RequestMapping("/user")
public class UserController{
    
    @RequestMapping(value="/login", method=RequestMethod.POST)
    public int login(@RequestBody User user){
        return 1;
    }
}

@ResponseBody

该注解用于向页面传递数据,如果没有该注解,那么controller方法中返回的任何数据都会被认为是一个页面字符串。

@Controller
@RequestMapping("/user")
public class UserController{
    
    @RequestMapping(value="/login", method=RequestMethod.POST)
    @ResponseBody
    public int login(@RequestBody User user){
        return 1;
    }
}

@RequestParam

该注解只能用在方法的参数上, 用于从 URL 查询字符串或表单参数中提取参数。

@Controller
@RequestMapping("/user")
public class UserController{
    
    @RequestMapping(value="/search", method=RequestMethod.GET)
    @ResponseBody
    public List<User> searchUsers(@RequestParam(value="name") String name){
        return new ArrayList<>();
    }
}

注意:@RequestParam和@PathVariable的区别:

@PathVariable

  • 用于从 URL 路径中提取参数。
  • 例如:提取 http://example.com/user/john 中的 john
  • 用于 RESTful 风格的 URL。

@RequestParam

  • 用于从 URL 查询字符串或表单参数中提取参数。
  • 例如:提取 http://example.com/user/search?name=john 中的 name,或提取表单提交的数据。
  • 适用于查询字符串参数和表单参数。

@PathVariable

该注解只能应用在方法的参数上,用于从请求路径中获取数据并注入至参数中

@Controller
@RequestMapping("/user")
public class UserController{
    // /user/admin
    @RequestMapping(value="/{username}", method=RequestMethod.GET)
    @ResponseBody
    public User queryUser(@PathVariable("username") String username){
        return new User();
    }
}

注意: 花括号 {} 用于定义路径变量,表示 URL 中的动态部分,这些部分将被提取并传递给控制器方法的参数。 前端在发送请求时,必须用具体的 username 替换路径变量 。

@RequestHeader

该注解只能应用在方法的参数上,用于从请求头中获取数据

@RequestMapping("/find")  
public void findUsers(@RequestHeader("Content-Type") String contentType) {//从请求头中获取Content-Type的值
}  

@CookieValue

该注解只能应用在方法的参数上,用于从请求中获取cookie的值

@RequestMapping("/find")  
public void findUsers(@CookieValue("JSESSIONID") String jsessionId) {//从请cookie中获取jsessionId的值
}  

@ControllerAdvice

该注解只能应用在类上,表示这个类就是处理异常的控制器

/**
 * 异常处理的控制器
 */
@ControllerAdvice //这个注解就是spring mvc提供出来做全局异常统一处理的
public class ExceptionController {
}

@ExceptionHandler

该注解只能应用在@ControllerAdvice或者@RestControllerAdvice标识的类的方法上用来处理异常

/**
 * 异常处理的控制器
 */
@ControllerAdvice //这个注解就是spring mvc提供出来做全局异常统一处理的
public class ExceptionController {

    @ExceptionHandler //异常处理器
    @ResponseBody //响应至页面
    public String handleException(Exception e){
        return e.getMessage();
    }
}

Spring 对 RESTFUL的支持

@RestController

相当于@Controller 和 @ResponseBody 注解的组合。表示该类中的所有方法执行完成后所返回的结果直接向页面输出。

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping

静态资源处理

静态资源无法访问的原因

静态资源包含html、js、css、图片、字体文件等。静态文件没有url-pattern,所以默认是无法访问的。之所以可以访问,是因为tomcat中有一个全局的servlet:org.apache.catalina.servlets.DefaultServlet,它的url-pattern是 “/”, 所以项目中不能匹配的静态资源请求,都由这个Servlet来处理。但在SpringMVC中DispatcherServlet也采用了"/" 作为url-pattern, 那么项目中不会再使用全局的Serlvet,这样就造成了静态资源不能完成访问。

处理方案

方案一:修改DispatcherServlet对应的url-pattern修改为"/"以外的其他匹配样式。

方案二(建议):将所有的静态资源放进一个static包中,如果需要访问,则将defaultServlet的url-pattern的url-mapping改为/static/*

<!-- web.xml -->
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/static/*</url-pattern>
  </servlet-mapping>

方案三:利用default-servlet-handler 将处理静态资源的请求转发给容器的默认 Servlet ,而不是给DispatcherServlet。

<!-- spring-mvc.xml -->
<!-- 
这个handler就是处理静态资源的,它的处理方式就是将请求转会到tomcat中名为default的Servlet 
-->
<mvc:default-servlet-handler/>
<!-- mapping是访问路径,location是静态资源存放的路径 -->
<mvc:resources mapping="/static/**" location="/static/" />

中文乱码处理

在web.xml中配置字符编码过滤器CharacterEncodingFilter

<filter>
    <filter-name>encodingFilter</filter-name>
    <!--字符编码过滤器-->
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <!--编码格式-->
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <!--强制编码-->
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/775168.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

智能文档革新:合合信息智能文档处理平台上线基金合同抽取模型!

一、什么是基金合同&#xff1f; 基金合同是指具有平等地位主体的基金当事人在基金活动中&#xff0c;为规范其间的权利、义务&#xff0c;依意思表示一致而形成的契约或协议。《证券投资基金法》第五十二条规定&#xff0c;公开募集基金的基金合同应当包括下列内容: &#x…

软件游戏d3dcompiler_43.dll丢失怎么办,总结几种有效的方法

在使用电脑时&#xff0c;可能会碰到找不到d3dcompiler_43.dll的问题。即在使用过程中&#xff0c;突然弹出一个提示“d3dcompiler_43.dll丢失”&#xff0c;由于此文件的缺失&#xff0c;部分程序将无法启动。为恢复正常使用&#xff0c;我们需要修复此文件。接下来&#xff0…

【C++】 解决 C++ 语言报错:Undefined Reference

文章目录 引言 未定义引用&#xff08;Undefined Reference&#xff09;是 C 编程中常见的错误之一&#xff0c;通常在链接阶段出现。当编译器无法找到函数或变量的定义时&#xff0c;就会引发未定义引用错误。这种错误会阻止生成可执行文件&#xff0c;影响程序的正常构建。本…

Java项目:基于SSM框架实现的中小企业人力资源管理系统【ssm+B/S架构+源码+数据库+开题报告+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的中小企业人力资源管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简…

matlab 绘制高等数学中的二维函数示例

matlab 绘制高等数学中的二维函数示例 绘制高等数学中的二维函数示例绘制结果 绘制高等数学中的二维函数示例 clc,clear,close all; % 定义方程 eqn (x, y) (x.^2 y.^2).^3 - y.^4;% 绘制方程曲线和坐标轴 ezplot(eqn, [-2, 2, -2, 2]) hold on % 在同一图形中保持绘图% 绘…

国际上备考所有云计算/IT证书的五大优质免费课程网站

最近越来越多的小伙伴来问小李哥&#xff0c;小李哥亚马逊云科技AWS认证大满贯是在哪里上课复习的呢&#xff1f;全部上付费课程那不是一笔巨款吗&#xff1f;小李哥这次来盘点备考国际上IT证书的5大优质免费课程网站(不只是亚马逊云科技AWS的课程&#xff0c;其他课程同样可以…

满足GMSL静电防护要求的方案

什么是GMSL&#xff1f;它是做什么用的&#xff1f;它有什么优点&#xff1f;设计GMSL防静电有啥难度&#xff1f; 带着这些疑问我们先了解下什么是GMSL。 一&#xff0e;简述 GMSL GMSL&#xff08;Gigabit Multimedia Serial Link&#xff09;即千兆多媒体串行链路&#xf…

odoo 物联网 设备数据采集方案

图一 架构手稿(许老师专属) 图二 架构简图 部署 方案一&#xff1a; odoo业务数据库与设备采集数据库使用一个instance。 缺点&#xff1a;重启pg服务相互影响。 方案二&#xff1a; odoo业务数据库与设备采集数据库独立部署&#xff0c;使用两个instance。 优点&#xff1a;…

一个使用率超高的大数据实验室是如何练成的?

厦门大学嘉庚学院“大数据应用实训中心”&#xff08;以下简称“实训中心”&#xff09;自2022年建成以来&#xff0c;已经成为支撑“大数据专业”复合型人才培养的重要支撑&#xff0c;目前实训中心在专业课程实验教学、项目实训、数据分析类双创比赛、毕业设计等方面都有深入…

CVPR2024自动驾驶轨迹预测方向的论文整理

2024年自动驾驶轨迹预测方向的论文汇总 1、Producing and Leveraging Online Map Uncertainty in Trajectory Prediction 论文地址&#xff1a;https://arxiv.org/pdf/2403.16439 提出针对在线地图不确定性带给轨迹预测的影响对应的解决方案。 在轨迹预测中&#xff0c;利用在…

vscode连接SSH——连接学校服务器,使用conda配置个人环境并使用

服务器的连接 在vscode远程资源管理中配置配置文件&#xff0c;如下图&#xff1a; 然后点击左下角进行连接&#xff1a; 点击需要连接的服务器&#xff0c;输入对应密码即可登录成功。 服务器上创建自己的环境 确保服务器上已安装anaconda。 先查看服务器上的conda信息&…

Linux_共享内存通信

目录 1、共享内存原理 2、申请共享内存 2.1 ftok 2.2 测试shmget、ftok 2.3 查看系统下的共享内存 3、关联共享内存 3.1 测试shmat 4、释放共享内存 4.1 测试shmctl 5、实现共享内存通信 6、共享内存的特性 结语 前言&#xff1a; 在Linux下&#xff0c;有一…

jenkins在使用pipeline时,为何没有方块形视图

项目场景&#xff1a; 安装完Jenkins时后&#xff0c;通过pipeline创建的项目任务。 问题描述 在立即构建后&#xff0c;没有显示每个阶段的视图。 原因分析&#xff1a; 原因是&#xff0c;刚安装的Jenkins&#xff0c;这个视图不是Jenkins自带的功能&#xff0c;而必须安装…

Cannot resolve symbol ‘log`

idea里的代码log变红色&#xff0c;是因为缺少Lombok插件。 安装lombok插件即可。安装完应用&#xff0c;重启软件就好了。 依次点击菜单栏中的 File → Settings&#xff08;Windows/Linux&#xff09; 或 IntelliJ IDEA → Preferences&#xff08;macOS&#xff09;。在设置…

设计模式探索:单例模式

1. 什么是单例模式? 定义: 单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一种全局访问点以访问该实例。常见的场景包括身份证号码、政府等需要唯一实例的情况。 单例模式通常用于那些需要在应用程序中仅存在一个实例的情况,例如配置管理器、线程池、数据…

ret2syscall简单总结

主要是自己的简单的学习总结。 知识点 关于系统调用如何传递参数问题&#xff0c;即系统调用约定&#xff08;syscall&#xff0c;int 80h&#xff0c;svc&#xff09;_int 80h intel汇编用法-CSDN博客 ret2syscall的做题思路&#xff08;以32位程序为例&#xff09; - ZikH…

2024年【山东省安全员A证】考试试卷及山东省安全员A证考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 山东省安全员A证考试试卷根据新山东省安全员A证考试大纲要求&#xff0c;安全生产模拟考试一点通将山东省安全员A证模拟考试试题进行汇编&#xff0c;组成一套山东省安全员A证全真模拟考试试题&#xff0c;学员可通过…

react 项目中预防xss攻击的插件 dompurify

一、安装 $ yarn add dompurify $ yarn add --dev types/dompurify 二、使用 import DOMPurify from dompurify;// 1、处理&#xff1a; DOMPurify.sanitize(htmlContent)// 2、之后放进 dangerouslySetInnerHTML dangerouslySetInnerHTML{{ __html: cleanHTML }} 如&#…

Django自动生成Swagger接口文档 —— Python

1. 前言 当接口开发完成&#xff0c;紧接着需要编写接口文档。传统的接口文档通常都是使用Word或者一些接口文档管理平台进行编写&#xff0c;但此类接口文档维护更新比较麻烦&#xff0c;每次接口有变更&#xff0c;需要手动修改接口文档。在实际的工作中&#xff0c;经常会遇…