当前位置: 首页 > news >正文

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>

相关文章:

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

Spring AOP 概念 AOP全称为Aspect Oriented Programming&#xff0c;表示面向切面编程。切面指的是将那些与业务无关&#xff0c;但业务模块都需要使用的功能封装起来的技术。 AOP基本术语 **连接点&#xff08;Joinpoint&#xff09;&#xff1a;**连接点就是被拦截到的程序执…...

grid布局下的展开/收缩过渡效果【vue/已验证可正常运行】

代码来自GPT4o&#xff1a;国内官方直连GPT4o <template><div class"container"><button class"butns" click"toggleShowMore">{{ showAll ? 收回 : 显示更多 }}</button><transition-group name"slide-fade&…...

Qt/C++编写地图应用/离线地图下载/路径规划/轨迹回放/海量点/坐标转换

一、前言说明 这个地图组件写了很多年了&#xff0c;最初设计的比较粗糙&#xff0c;最开始只是为了满足项目需要&#xff0c;并没有考虑太多拓展性&#xff0c;比如最初都是按照百度地图写死在代码中&#xff0c;经过这几年大量的现场实际应用&#xff0c;以及大量的用户提出…...

最新版Python安装教程

一、安装Python 1.下载Python 访问Python官网&#xff1a; https:/www.oython.orgl 点击downloads按钮&#xff0c;在下拉框中选择系统类型(windows/Mac OS./Linux等) 选择下载最新稳定版本的Python 以下内容以演示安装Windows操作系统64位的python 左边是稳定发布版本Stabl…...

1.3镜像管理

【必知必会】了解镜像的命名及导入导出镜像 1.3.1镜像的命名 一般情况下,镜像是按照如下格式命名的 服务器ip:端口/分类/镜像名:tag 如果不指明端口,默认是80,tag是latest,比如:192.168.26.101:5000,再比如hub.c.163.com/library/mysql:latest.分类也可以不写,比如d…...

黑马|最新AI+若依 |初识项目

本章主要内容是&#xff1a; 1.快速搭建了若依前后端项目在本地 2.实现了单表的增删改查快速生成 文章目录 介绍1.若依介绍2.若依的不同版本3.项目运行环境 初始化前后端项目1.下载若依项目2.初始化后端a.把表导入到数据库中b.更改application.yml文件 3.初始化前端a.安装依赖…...

ArrayList综合案例-模拟外卖中的商家系统

一案例要求&#xff1a; 二代码要求&#xff1a; package 重修;import java.util.ArrayList; import java.util.Random; import java.util.Scanner;import static java.lang.System.exit;public class first {public static void main(String[] args) {Scanner scnew Scanne…...

Postgres JSON字段怎么修改key的名称

场景 当你不小心将 key 的名称写错了&#xff08;人员类别&#xff1a;多了一个冒号&#xff09;&#xff0c;或者想把引文改为中文&#xff08;type改为类型&#xff09; 大致思路是添加一个新的 key&#xff0c;然后将旧的 key 删除 sql语句 假如 JSON 列为 extra&#x…...

GStreamer学习5----probe数据探测

参考资料&#xff1a; gstreamer中如何使用probe&#xff08;探针&#xff09;获取帧数据_gstreamer 视频编码时获取视频关键帧信息-CSDN博客 Gstreamer中可以使用AppSink作为一个分支来查看管线中的数据&#xff0c;还可以使用probe去处理。 在GStreamer中&#xff0c;probe…...

Open3D 点云的圆柱形邻域搜索

目录 一、概述 1.1原理 1.2应用 二、代码实现 2.1完整代码 2.2程序说明 三、实现效果 3.1原始点云 3.2搜索后点云 一、概述 1.1原理 圆柱邻域搜索的基本思想是确定点云中的哪些点位于给定圆柱的内部。一个圆柱可以由以下几个参数定义&#xff1a; 中心点&#xff1a;…...

python如何设计窗口

PyQt是一个基于Qt的接口包&#xff0c;可以直接拖拽控件设计UI界面&#xff0c;下面我简单介绍一下这个包的安装和使用&#xff0c;感兴趣的朋友可以自己尝试一下&#xff1a; 1、首先&#xff0c;安装PyQt模块&#xff0c;这个直接在cmd窗口输入命令“pip install pyqt5”就行…...

C语言获取当前时间

一共有两段代码&#xff0c;一个是获取当前时间&#xff0c;一个是获取到现在的总毫秒数 求关注&#x1f604; 互粉必回 获取当前时间 #include <stdio.h> #include <time.h> int main() { time_t rawtime; struct tm * timeinfo; char buffer[20]; // 获取当前…...

【每日一练】python三目运算符的用法

""" 三目运算符与基础运算的对比 """ a 1 b 2#1.基础if运算判断写法&#xff1a; if a > b:print("基础判断输出&#xff1a;a大于b") else:print("基础判断输出&#xff1a; a不大于b")#2.三目运算法判断&#xff1a;…...

CentOS 7.9 停止维护(2024-6-30)后可用在线yum源 —— 筑梦之路

众所周知&#xff0c;centos 7 在2024年6月30日&#xff0c;生命周期结束&#xff0c;官方不再进行支持维护&#xff0c;而很多环境一时之间无法完全更新替换操作系统&#xff0c;因此对于yum源还是需要的&#xff0c;特别是对于互联网环境来说&#xff0c;在线yum源使用方便很…...

Git 常用命令备忘

1、删除 (1)、git push origin --delete dev 删除远程分支 (2)、git branch -d dev 删除本地分支 git branch -D dev 强制删除本地分支 2、创建分支 (1)、git checkout -b dev 创建本地分支 (2)、git push origin dev 创建远程分支&#xff0c;此时本地分支与远程…...

Ubuntu24.04安装Skynet环境

安装依赖 sudo apt-get -y install gcc sudo apt-get -y install g sudo apt-get -y install make sudo apt-get install -y autoconf automake libtool sudo apt-get install -y git 或者可以用&#xff1a; sudo apt-get -y install gcc g make autoconf automake libtool…...

【C++】cout.self()函数

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文作为 JohnKi 学习笔记&#xff0c;借鉴了部分大佬案例 &#x1f4e2;未来很长&#…...

VueQuill 富文本编辑器技术文档快速上手

VueQuill 富文本编辑器技术文档 1. 安装 VueQuill2. 配置 VueQuill3. 在组件中使用 VueQuill4. 配置选项5. 事件处理6. 数据格式7. 自定义工具栏8. 示例项目结构9. 常见问题如何添加图片上传功能&#xff1f;如何自定义编辑器主题&#xff1f; 在此之前&#xff0c;我讲解过关于…...

链式二叉树oj题

1.输入k &#xff0c;找第k层节点个数 int TreeKlevel(BTNode*root,int k) {if (root NULL) {return 0;}if (k 1) {return 1;}return TreeKlevel(root->left, k - 1)TreeKlevel(root->right, k - 1); } 在这里我们要确定递归子问题&#xff0c;第一个就是NULL时返回&…...

Curator 是一个开源工具为 Elasticsearch 集群设计,用于自动化索引的维护任务。

Elasticsearch 使用 Curator 进行索引生命周期管理是一种常见的做法&#xff0c;Curator 是一个开源的 Python 工具&#xff0c;专为 Elasticsearch 集群设计&#xff0c;用于自动化索引的维护任务。以下是使用 Curator 进行索引生命周期管理的一些关键步骤和概念&#xff1a; …...

STM32-PWR和WDG看门狗

本内容基于江协科技STM32视频学习之后整理而得。 文章目录 1. PWR1.1 PWR简介1.2 电源框图1.3 上电复位和掉电复位1.4 可编程电压监测器1.5 低功耗模式1.6 模式选择1.7 睡眠模式1.8 停止模式1.9 待机模式1.10 库函数 2. WDG看门狗2.1 WDG简介2.2 IWDG框图2.3 IWDG键寄存器2.4 …...

C++循环队列 经典示例

循环队列&#xff08;Circular Queue&#xff09;&#xff0c;又称环形缓冲区&#xff0c;是一种常用的数据结构&#xff0c;特别适用于资源有限的场合&#xff0c;比如操作系统中的任务调度、网络数据缓冲等。循环队列在数组的基础上实现&#xff0c;逻辑上首尾相连&#xff0…...

【程序大侠传】大表分库分表切换数据库类型导致pagehelper生成sql语法报错

前序 代码剑宗等级分明&#xff0c;其门下弟子等级划分如下&#xff1a; 入门弟子 刚刚拜入代码剑宗&#xff0c;学习基础编程语言和基本剑法&#xff08;语法和基础概念&#xff09;。他们的代码还显得生涩&#xff0c;但已经开始展现出对优雅代码的追求。 江湖小虾 初步掌握…...

7、Redis 队列与 Stream

引言 Redis 自 5.0 版本起引入了一种新的数据结构——Stream。这种数据结构不仅增加了 Redis 的数据处理能力&#xff0c;还使其在消息队列和数据流处理方面更具竞争力。Stream 提供了持久化、多播、消费组等功能&#xff0c;可以满足多种复杂的数据处理需求。 1. Redis Stre…...

FFT剖析

快速傅里叶变换 (fast Fourier transform) xn{x0,x1,…xn-1} (num:N) 旋转因子系数&#xff1a; d2pik/N 旋转因子 wk(n)(cos(dn)isin(dn)) n[0,N-1] y(k) sum(x(n)wk(n),0,N-1) y(k){y(0),y(1),…y(N-1)} 傅里叶级数 x(n)wk(n)的级数是&#xff1a; 1.d2pik/N 这个系数决…...

git clone报错RPC failed; curl 92 HTTP/2 stream 7 was not closed cleanly

问题描述 git clone github上的项目报错: RPC failed; curl 92 HTTP/2 stream 7 was not closed cleanly: CANCEL (err 8) 4796 bytes of body are still expected fetch-pack: unexpected disconnect while reading sideband packet early EOF fetch-pack: invalid index-pac…...

Apispec,一个用于生成 OpenAPI(Swagger)规范的 Python 库

目录 01什么是 Apispec&#xff1f; 为什么选择 Apispec&#xff1f; 安装与配置 02Apispec 的基本用法 生成简单的 API 文档 1、创建 Apispec 实例 2、定义 API 路由和视图 3、添加路径到 Apispec 集成 Flask 和 Apispec 1、安装…...

SpringBoot 自定义异常返回数据格式

Spring Boot 默认异常处理 当我们用 spring boot 开发接口是&#xff0c;当遇到异常时返回的数据格式是如下形式的 {"timestamp": "2024-07-06T02:48:55.79100:00","status": 404,"error": "Not Found","path":…...

【xinference】(15):在compshare上,使用docker-compose运行xinference和chatgpt-web项目,配置成功!!!

视频演示 【xinference】&#xff08;15&#xff09;&#xff1a;在compshare上&#xff0c;使用docker-compose运行xinference和chatgpt-web项目&#xff0c;配置成功&#xff01;&#xff01;&#xff01; 1&#xff0c;安装docker方法&#xff1a; #!/bin/shdistribution$(…...

【Unity 3D角色移动】

【Unity 3D角色移动】 在Unity 3D中实现角色移动通常涉及到几个关键步骤&#xff0c;包括设置角色的物理属性、处理输入、更新角色的位置以及动画同步。下面是实现基本3D角色移动的步骤和示例代码&#xff1a; 步骤1&#xff1a;设置角色的物理属性 角色通常使用Character Co…...