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{@Overridepublic 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{@Overridepublic 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 {@Testpublic 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 {@Overridepublic 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 {@Overrideprotected 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)@ResponseBodypublic int login(@RequestBody User user){return 1;}
}

@RequestParam

該注解只能用在方法的參數上, 用于從 URL 查詢字符串或表單參數中提取參數。

@Controller
@RequestMapping("/user")
public class UserController{@RequestMapping(value="/search", method=RequestMethod.GET)@ResponseBodypublic 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)@ResponseBodypublic 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.pswp.cn/web/40672.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/40672.shtml
英文地址,請注明出處:http://en.pswp.cn/web/40672.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

AQWA | 水動力分析 二階波浪力

&#x1f3c6;本文收錄于「Bug調優」專欄&#xff0c;主要記錄項目實戰過程中的Bug之前因后果及提供真實有效的解決方案&#xff0c;希望能夠助你一臂之力&#xff0c;幫你早日登頂實現財富自由&#x1f680;&#xff1b;同時&#xff0c;歡迎大家關注&&收藏&&…

Midjourney對圖片細微調整和下載保存

點擊v2是對第二圖片細微調整。 點擊u3對第3張圖片進行放大。 保存圖片: 對點擊u3放大的圖片&#xff0c;雙擊 , 右鍵保存圖片

停車場小程序的設計

管理員賬戶功能包括&#xff1a;系統首頁&#xff0c;個人中心&#xff0c;車主管理&#xff0c;商家管理&#xff0c;停車場信息管理&#xff0c;預約停車管理&#xff0c;商場收費管理&#xff0c;留言板管理 微信端賬號功能包括&#xff1a;系統首頁&#xff0c;停車場信息…

審核平臺前端新老倉庫遷移

背景 審核平臺接入50業務&#xff0c;提供在線審核及離線質檢、新人培訓等核心能力&#xff0c;同時提供數據報表、資源追蹤、知識庫等工具。隨著平臺的飛速發展&#xff0c;越來越多的新業務正在或即將接入審核平臺&#xff0c;日均頁面瀏覽量為百萬級別。如今審核平臺已是公司…

代碼提交錯分支了怎么辦?

你有么有遇到過正在開發的代碼&#xff0c;提交到生產環境的分支去&#xff0c;遇到這種情況怎么辦&#xff1f; 問題重現&#xff1a; 這段注釋// AAAAAAAAAAA 本來應該寫在dev分支的&#xff0c;現在提交并push到master分支了 現在第一步&#xff0c;撤回提交 第二步&…

第1章 認識 Vite

明白了&#xff0c;這里是第1章內容的詳細展開版本&#xff1a; 第1章 認識 Vite 1 . 什么是 Vite Vite 是一個由尤雨溪&#xff08;Vue.js 的創始人&#xff09;開發的前端構建工具&#xff0c;旨在提供極快的開發體驗。Vite 的名字來源于法語&#xff0c;意為“快速”&…

python繪制一維離散點

在Python中&#xff0c;繪制一維離散點通常意味著我們要在一條直線上標記出幾個特定的點。這可以通過多種庫來實現&#xff0c;但最常見和強大的庫之一是matplotlib。以下是一個詳細的代碼示例&#xff0c;它展示了如何使用matplotlib庫來繪制一維離散點&#xff0c;并且這個示…

C++語言常見錯誤分析匯總

在一個工程里出現兩個main函數時 3.obj : error LNK2005: _main already defined in file1.obj Debug/HELLO.exe : fatal error LNK1169: one or more multiply defined symbols found 這個就是說&#xff0c;你的main函數重定義了。你看看是不是你的工程里面&#xff0c;包…

MySQL的Geometry數據處理之WKB方案

MySQL的Geometry數據處理之WKT方案&#xff1a;https://blog.csdn.net/qq_42402854/article/details/140134357 MySQL的Geometry數據處理之WKT方案中&#xff0c;介紹WTK方案的優點&#xff0c;也感受到它的繁瑣和缺陷。比如&#xff1a; 需要借助 ST_GeomFromText和 ST_AsTex…

Spring @Cacheable緩存注解用法說明

注解Cacheable 是 Spring 框架中用于緩存數據的方法或類的注解。通過使用這個注解&#xff0c;你可以避免重復計算和重復獲取數據&#xff0c;從而提高應用程序的性能。 基本用法 引入依賴 確保在你的項目中引入了 Spring Cache 相關的依賴。如果你使用的是 Spring Boot&…

中英雙語介紹中國的城市:上海市(Shanghai)

中文版 上海市是中國最大的城市之一&#xff0c;也是全球重要的金融、貿易和航運中心。作為一座現代化的國際大都市&#xff0c;上海以其繁華的商業區、豐富的文化遺產和多樣化的經濟結構而聞名。以下是對上海市的詳細介紹&#xff0c;包括其地理位置、人口、經濟、教育、文化…

qt結合vs2022安裝

進入清華大學開源軟件&#xff1a; 清華大學開源軟件鏡像站 | Tsinghua Open Source Mirror 下載完成后&#xff0c;雙擊進行安裝&#xff1a; 進入郵箱進行驗證&#xff1a; 可能是因為網絡問題&#xff0c;無法安裝。 重新安裝5.12.12版本。 安裝后啟動失敗&#xff0c;重新…

后端接口設計考慮要點

1. 接口參數校驗 入參校驗&#xff1a;確保必要參數不為空&#xff0c;限制長度和格式&#xff08;例如郵箱格式&#xff09;。返回值校驗&#xff1a;確定返回值不為空&#xff0c;為空時返回與前端協商的默認值。 2. 接口擴展性 設計通用接口而非僅針對特定業務流程的接口…

橫截面交易策略:概念與示例

數量技術宅團隊在CSDN學院推出了量化投資系列課程 歡迎有興趣系統學習量化投資的同學&#xff0c;點擊下方鏈接報名&#xff1a; 量化投資速成營&#xff08;入門課程&#xff09; Python股票量化投資 Python期貨量化投資 Python數字貨幣量化投資 C語言CTP期貨交易系統開…

數據結構--單鏈表實現

歡迎光顧我的homepage 前言 鏈表和順序表都是線性表的一種&#xff0c;但是順序表在物理結構和邏輯結構上都是連續的&#xff0c;但鏈表在邏輯結構上是連續的&#xff0c;而在物理結構上不一定連續&#xff1b;來看以下圖片來認識鏈表與順序表的差別 這里以動態順序表…

WGAN(Wassertein GAN)

WGAN E x ~ P g [ log ? ( 1 ? D ( x ) ) ] E x ~ P g [ ? log ? D ( x ) ] \begin{aligned} & \mathbb{E}_{x \sim P_g}[\log (1-D(x))] \\ & \mathbb{E}_{x \sim P_g}[-\log D(x)] \end{aligned} ?Ex~Pg??[log(1?D(x))]Ex~Pg??[?logD(x)]? 原始 GAN …

springboot基于Java的超市進銷存系統+ LW+ PPT+源碼+講解

第三章系統分析與設計 3.1 可行性分析 一個完整的系統&#xff0c;可行性分析是必須要有的&#xff0c;因為他關系到系統生存問題&#xff0c;對開發的意義進行分析&#xff0c;能否通過本網站來補充線下超市進銷存管理模式中的缺限&#xff0c;去解決其中的不足等&#xff0c…

6域名系統DNS

《計算機網絡》第7版&#xff0c;謝希仁 每次記不清楚的知識點&#xff0c;通過上網查找&#xff0c;總是只能看到很零碎的答案。最后還是最喜歡看這個版本的書&#xff0c;一看就回憶起來了&#xff0c;邏輯嚴謹&#xff0c;循循善誘&#xff0c;知識講解的全面又清晰&#xf…

架構師應該在團隊中發揮怎樣的作用?

架構師分為5種&#xff1a; 1.企業架構師EA(Enterprise Architect) EA的職責是決定整個公司的技術路線和技術發展方向。 2.基礎結構架構師IA(Infrastructure Architect) IA的工作就是提煉和優化技術方面積累和沉淀形成的基礎性的、公共的、可復用的框架和組件&#xff0c;這…

Qt 基礎組件速學 鼠標和鍵盤事件

學習目標&#xff1a; 鼠標事件和鍵盤事件應用 前置環境 運行環境:qt creator 4.12 學習內容和效果演示&#xff1a; 1.鼠標事件 根據鼠標的坐標位置&#xff0c;做出對應的事件。 2.鍵盤事件 根據鍵盤的輸入做出對應操作 詳細主要代碼 1.鼠標事件 #include "main…