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

news2024/11/17 16:36:58

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>

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何在Windows 11上复制文件和文件夹路径?这里提供几种方法

在Windows 11上复制文件或文件夹的路径就像在右键单击菜单中选择一个选项或按键盘快捷键一样简单。我们将向你展示如何在电脑上以各种方式进行操作。 从右键单击菜单 复制文件或文件夹路径的最简单方法是在该项目的右键单击菜单中选择一个选项。你也可以使用此方法复制多个项…

Golang | Leetcode Golang题解之第218题天际线问题

题目&#xff1a; 题解&#xff1a; type pair struct{ right, height int } type hp []pairfunc (h hp) Len() int { return len(h) } func (h hp) Less(i, j int) bool { return h[i].height > h[j].height } func (h hp) Swap(i, j int) { h[i], h[j]…

2024年【北京市安全员-B证】报名考试及北京市安全员-B证最新解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年北京市安全员-B证报名考试为正在备考北京市安全员-B证操作证的学员准备的理论考试专题&#xff0c;每个月更新的北京市安全员-B证最新解析祝您顺利通过北京市安全员-B证考试。 1、【多选题】《中华人民共和国安…

【HICE】dns正向解析

1.编辑仓库 2.挂载 3.下载软件包 4.编辑named.conf 5.编辑named.haha 6.重启服务 7.验证本地域名是否解析

一个pdf分割成多个pdf,一个pdf分成多个pdf

在数字化办公和学习中&#xff0c;pdf格式因其良好的兼容性和稳定性而受到广泛欢迎。但有时候&#xff0c;我们可能需要将一个大的pdf文件分割成多个小文件&#xff0c;以便于分享、打印或编辑。今天&#xff0c;我就来教大家几种简单有效的方法&#xff0c;让你轻松实现pdf文件…

Python应用开发——30天学习Streamlit Python包进行APP的构建(13)

st.chat_input 显示聊天输入窗口小部件。 Function signature[source]st.chat_input(placeholder="Your message", *, key=None, max_chars=None, disabled=False, on_submit=None, args=None, kwargs=None) Returns(str or None) The current (non-empty) value of…

HTML【详解】表格 table 标签(table的属性,语义化表格,简易表格,合并单元格)

html 中的表格 <table> 由行 <tr> 组成&#xff0c;每行由单元格 <td> 组成。 所以表格是由行组成&#xff08;行由列组成&#xff09;&#xff0c;而不是由行和列组成。 table 标签 display: table &#xff0c;属于块级元素。 table 的属性 border&#…

[图解]企业应用架构模式2024新译本讲解23-标识映射2

1 00:00:00,950 --> 00:00:02,890 好&#xff0c;我们往下走 2 00:00:04,140 --> 00:00:04,650 一样的 3 00:00:04,660 --> 00:00:07,170 这前面也见过了&#xff0c;定义一个对象数组 4 00:00:07,870 --> 00:00:12,820 数组的长度就是字段的数量&#xff0c;4个…

2 极/2 零 (2P2Z) 补偿器

极/2 零 &#xff08;2P2Z&#xff09; 补偿器是模拟 II 型控制器的数字实现。它是一种滤波器&#xff0c;通过考虑两个极点和一个零点&#xff0c;将特定的增益和相位升压引入系统。您必须战略性地选择每个极点和零点的频率位置&#xff0c;这将有助于实现所需的系统性能。在该…

Lunaproxy与711Proxy的对比与优劣分析

今天我们来深入对比两款在市场上备受关注的代理IP服务&#xff1a;Lunaproxy和711Proxy。接下来&#xff0c;我们将从多个角度对这两款服务进行详细分析&#xff0c;帮助大家做出明智的选择。 优势分析 711Proxy的优势 1. 性价比高&#xff1a;711Proxy提供多种灵活的套餐选…

一款强大且免费开源的多连接数据库管理工具

大家好&#xff0c;今天给大家分享一款免费开源的跨平台数据库管理工具DbGate。 DbGate是一款免费开源的跨平台数据库管理工具&#xff0c;支持多种数据库&#xff0c;包括MySQL、PostgreSQL、SQL Server、MongoDB、SQLite等。它可以在Windows、Linux、Mac操作系统上运行&#…

Zynq系列FPGA实现SDI视频编解码+UDP以太网传输,基于GTX高速接口,提供3套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本博已有的以太网方案本博已有的FPGA图像缩放方案1G/2.5G Ethernet PCS/PMA or SGMII架构以太网通信方案AXI 1G/2.5G Ethernet Subsystem架构以太网通信方案本方案的缩放应用本方案在Xilinx--Kintex系列…

全面解析智慧校园行政办公的协作日程功能

在智慧校园的行政办公生态系统中&#xff0c;协作日程功能成为促进团队互动与工作同步的桥梁&#xff0c;它超越了传统个人日程的范畴&#xff0c;强调的是集体效率与信息的无缝对接。这一功能设计的核心&#xff0c;在于创造一个开放而有序的平台&#xff0c;让教育工作者们能…

开源数据科学平台Anaconda简介

开源数据科学平台Anaconda简介 零、时光宝盒 最近&#xff0c;某金融行业女性选择以跳楼的形式结束自己的生命&#xff0c;这件不幸的事情成了热门话题&#xff0c;各种猜测的都有&#xff0c;有些人评论的话真的很过分。我想起前段时间看到的&#xff0c;有个女学生跳江&#…

数据的存储方式——大小端序

大小端存储的故事源自于《格列佛游记》&#xff08;Gullivers Travels&#xff09;&#xff0c;这是爱尔兰作家乔纳森斯威夫特&#xff08;Jonathan Swift&#xff09;于1726年所著的一部讽刺小说。在其中&#xff0c;主人公格列佛&#xff08;Lemuel Gulliver&#xff09;游历…

[leetcode hot 150]第一百一十七题,填充每个节点的下一个右侧节点

题目&#xff1a; 给定一个二叉树&#xff1a; struct Node {int val;Node *left;Node *right;Node *next; } 填充它的每个 next 指针&#xff0c;让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点&#xff0c;则将 next 指针设置为 NULL 。 初始状态下&#x…

测试环境:使用OpenSSL生成证书并配置Https

文章目录 需求1、安装OpenSSL1.1、安装包下载1.2、安装&#xff08;以window 64位为例&#xff09;1.3、配置环境变量&#xff08;非必须&#xff09; 2、生成证书2.1、新建文件夹2.2、生成根证书2.2.1、生成私钥2.2.2、生成根证书&#xff0c;并且自签名 2.3、服务端证书生成2…

2-4 Softmax 回归的从零开始实现

就像我们从零开始实现线性回归一样&#xff0c; 我们认为softmax回归也是重要的基础&#xff0c;因此应该知道实现softmax回归的细节。 本节我们将使用刚刚在2-3节中引入的Fashion-MNIST数据集&#xff0c; 并设置数据迭代器的批量大小为256。 import torch from IPython impo…

Python 编程高手必会的10个单行代码:一招鲜,吃遍天

大家好&#xff0c;在Python编程中&#xff0c;我们时常需要高效、简洁的代码来解决复杂的问题。今天&#xff0c;我将向大家介绍10个非常有用的Python单行代码。 一行代码指的是将复杂的任务浓缩在一行代码中完成。它充分利用Python的简洁和强大&#xff0c;使代码更简洁、更…

k8s record 20240703

1. containerd 它不用于直接和开发人员互动&#xff0c;在这方面不和docker竞争 containerd的用时最短&#xff0c;性能最好。 containerd 是容器的生命周期管理&#xff0c;容器的网络管理等等&#xff0c;真正让容器运行需要runC containerd 是一个独立的容器运行时&am…