Spring AOP基础、快速入门
介绍
AOP,面向切面编程,作为面向对象的一种补充,将公共逻辑(事务管理、日志、缓存、权限控制、限流等)封装成切面,跟业务代码进行分离,可以减少系统的重复代码和降低模块之间的耦合度。切面就是那些与业务无关,但所有业务模块都会调用的公共逻辑。
先看一个例子:如何给如下UserServiceImpl中所有方法添加进入方法的日志,
public class UserServiceImpl implements IUserService {@Overridepublic List<User> findUserList() {System.out.println("execute method: findUserList");return Collections.singletonList(new User("seven", 18));}@Overridepublic void addUser() {System.out.println("execute method: addUser");// do something}}
将记录日志功能解耦为日志切面,它的目标是解耦。进而引出AOP的理念:就是将分散在各个业务逻辑代码中相同的代码通过横向切割的方式抽取到一个独立的模块中!
OOP面向对象编程,针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程的某个步骤或阶段,以获得逻辑过程的中各部分之间低耦合的隔离效果。这两种设计思想在目标上有着本质的差异
AOP相关术语
首先要知道,aop不是spring所特有的,同样的,这些术语也不是spring所特有的。是由AOP联盟定义的
切面(Aspect):切面是增强和切点的结合,增强和切点共同定义了切面的全部内容。多个切面之间的执行顺序如何控制?首先要明确,在“进入”连接点的情况下,最高优先级的增强会先执行;在“退出”连接点的情况下,最高优先级的增强会最后执行。
通常使用@Order 注解直接定义切面顺序
实现Ordered 接口重写 getOrder 方法。Ordered.getValue()方法返回值(或者注解值)较低的那个有更高的优先级。
连接点(Join point):一般指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。当然,连接点也可能是类初始化、方法执行、方法调用、字段调用或处理异常等
增强(或称为通知)(Advice):在AOP术语中,切面的工作被称为增强。知实际上是程序运行时要通过Spring AOP框架来触发的代码段。
前置增强(Before):在目标方法被调用之前调用增强功能;
后置增强(After):在目标方法完成之后调用增强,此时不会关心方法的输出是什么;
返回增强(After-returning ):在目标方法成功执行之后调用增强;
异常增强(After-throwing):在目标方法抛出异常后调用增强;
环绕增强(Around):增强包裹了被增强的方法,在被增强的方法调用之前和调用之后执行自定义的逻辑
切点(Pointcut):切点的定义会匹配增强所要织入的一个或多个连接点。通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。以AspectJ举例,说白了就可以理解为是execution表达式
引入(Introduction):引入允许我们向现有类添加新方法或属性。 在AOP中表示为干什么(引入什么);
目标对象(Target Object): 被一个或者多个切面(aspect)所增强(advise)的对象。它通常是一个代理对象。
织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在AOP中表示为怎么实现的;织入分为编译期织入、类加载期织入、运行期织入;SpringAOP是在运行期织入
execution表达式格式:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
-
ret-type-pattern 返回类型模式, name-pattern名字模式和param-pattern参数模式是必选的, 其它部分都是可选的。返回类型模式决定了方法的返回类型必须依次匹配一个连接点。 使用的最频繁的返回类型模式是
*
,它代表了匹配任意的返回类型。 -
declaring-type-pattern, 一个全限定的类型名将只会匹配返回给定类型的方法。
-
name-pattern 名字模式匹配的是方法名。 可以使用
*
通配符作为所有或者部分命名模式。 -
param-pattern 参数模式稍微有点复杂:()匹配了一个不接受任何参数的方法, 而(..)匹配了一个接受任意数量参数的方法(零或者更多)。 模式(
*
)匹配了一个接受一个任何类型的参数的方法。 模式(*,String)匹配了一个接受两个参数的方法,第一个可以是任意类型, 第二个则必须是String类型。
例如:
execution(* com.seven.springframeworkaopannojdk.service.*.*(..))
Spring AOP和AspectJ的关系
AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器)。可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易。
AspectJ是更强的AOP框架,是实际意义的AOP标准;
Spring为何不写类似AspectJ的框架? Spring AOP使用纯Java实现, 它不需要专门的编译过程, 它一个重要的原则就是无侵入性(non-invasiveness); Spring 小组完全有能力写类似的框架,只是Spring AOP从来没有打算通过提供一种全面的AOP解决方案来与AspectJ竞争。Spring的开发小组相信无论是基于代理(proxy-based)的框架如Spring AOP或者是成熟的框架如AspectJ都是很有价值的,他们之间应该是互补的而不是竞争的关系。
Spring小组喜欢@AspectJ注解风格更胜于Spring XML配置; 所以在Spring 2.0使用了和AspectJ 5一样的注解,并使用AspectJ来做切入点解析和匹配。但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器(weaver)。
Spring 2.5对AspectJ的支持:在一些环境下,增加了对AspectJ的装载时编织支持,同时提供了一个新的bean切入点。
下表总结了 Spring AOP 和 AspectJ 之间的关键区别:
AOP的实现原理
AOP有两种实现方式:静态代理和动态代理。
静态代理
静态代理分为:编译时织入(特殊编译器实现)、类加载时织入(特殊的类加载器实现)。
代理类在编译阶段生成,在编译阶段将增强织入Java字节码中,也称编译时增强。AspectJ使用的是静态代理。
缺点:代理对象需要与目标对象实现一样的接口,并且实现接口的方法,会有冗余代码。同时,一旦接口增加方法,目标对象与代理对象都要维护。
动态代理
动态代理:代理类在程序运行时创建,AOP框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类
Spring的AOP实现原理
而Spring的AOP的实现就是通过动态代理实现的。
如果为Spring的某个bean配置了切面,那么Spring在创建这个bean的时候,实际上创建的是这个bean的一个代理对象,后续对bean中方法的调用,实际上调用的是代理类重写的代理方法。而Spring的AOP使用了两种动态代理,分别是JDK的动态代理,以及CGLib的动态代理。
-
如果目标类实现了接口,Spring AOP会选择使用JDK动态代理目标类。代理类根据目标类实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口。JDK动态代理的核心是
InvocationHandler
接口和Proxy
类。
-
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library)可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类。CGLIB是通过继承的方式做的动态代理,因此CGLIB存在的束:类是final的,或是方法是final的,或是方法是private,或是静态方法,也就是无法被子类实现的方法都无法使用CGLIB实现代理。
那么什么时候采用哪种动态代理呢?
-
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
-
如果目标对象实现了接口,可以强制使用CGLIB实现AOP
-
如果目标对象没有实现了接口,必须采用CGLIB库
AOP的配置方式
基于XML
Spring提供了使用"aop"命名空间来定义一个切面,我们来看个例子
-
定义目标类
public class AopDemoServiceImpl {public void doMethod1() {System.out.println("AopDemoServiceImpl.doMethod1()");}public String doMethod2() {System.out.println("AopDemoServiceImpl.doMethod2()");return "hello world";}public String doMethod3() throws Exception {System.out.println("AopDemoServiceImpl.doMethod3()");throw new Exception("some exception");}
}
-
定义切面类
public class LogAspect {public Object doAround(ProceedingJoinPoint pjp) throws Throwable {System.out.println("-----------------------");System.out.println("环绕通知: 进入方法");Object o = pjp.proceed();System.out.println("环绕通知: 退出方法");return o;}public void doBefore() {System.out.println("前置通知");}public void doAfterReturning(String result) {System.out.println("后置通知, 返回值: " + result);}public void doAfterThrowing(Exception e) {System.out.println("异常通知, 异常: " + e.getMessage());}public void doAfter() {System.out.println("最终通知");}}
-
XML配置AOP
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
"><context:component-scan base-package="com.seven.springframeworkaopxml" /><aop:aspectj-autoproxy/><!-- 目标类 --><bean id="demoService" class="com.seven.springframeworkaopxml.service.AopDemoServiceImpl"><!-- configure properties of bean here as normal --></bean><!-- 切面 --><bean id="logAspect" class="com.seven.springframeworkaopxml.aspect.LogAspect"><!-- configure properties of aspect here as normal --></bean><aop:config><!-- 配置切面 --><aop:aspect ref="logAspect"><!-- 配置切入点 --><aop:pointcut id="pointCutMethod" expression="execution(* com.seven.springframeworkaopxml.service.*.*(..))"/><!-- 环绕通知 --><aop:around method="doAround" pointcut-ref="pointCutMethod"/><!-- 前置通知 --><aop:before method="doBefore" pointcut-ref="pointCutMethod"/><!-- 后置通知;returning属性:用于设置后置通知的第二个参数的名称,类型是Object --><aop:after-returning method="doAfterReturning" pointcut-ref="pointCutMethod" returning="result"/><!-- 异常通知:如果没有异常,将不会执行增强;throwing属性:用于设置通知第二个参数的的名称、类型--><aop:after-throwing method="doAfterThrowing" pointcut-ref="pointCutMethod" throwing="e"/><!-- 最终通知 --><aop:after method="doAfter" pointcut-ref="pointCutMethod"/></aop:aspect></aop:config></beans>
-
测试类
public static void main(String[] args) {// create and configure beansApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml");// retrieve configured instanceAopDemoServiceImpl service = context.getBean("demoService", AopDemoServiceImpl.class);// use configured instanceservice.doMethod1();service.doMethod2();try {service.doMethod3();} catch (Exception e) {// e.printStackTrace();}
}
基于AspectJ注解(直接写表达式)
基于XML的声明式AspectJ存在一些不足,需要在Spring配置文件配置大量的代码信息,为了解决这个问题,Spring 使用了@AspectJ框架为AOP的实现提供了一套注解。
基于JDK动态代理
基于JDK动态代理例子源码点这里
-
定义接口
public interface IJdkProxyService {void doMethod1();String doMethod2();String doMethod3() throws Exception;
}
-
实现类
@Service
public class JdkProxyDemoServiceImpl implements IJdkProxyService {@Overridepublic void doMethod1() {System.out.println("JdkProxyServiceImpl.doMethod1()");}@Overridepublic String doMethod2() {System.out.println("JdkProxyServiceImpl.doMethod2()");return "hello world";}@Overridepublic String doMethod3() throws Exception {System.out.println("JdkProxyServiceImpl.doMethod3()");throw new Exception("some exception");}
}
-
定义切面
@EnableAspectJAutoProxy
@Component
@Aspect
public class LogAspect {/*** define point cut.*/@Pointcut("execution(* com.seven.springframeworkaopannojdk.service.*.*(..))")private void pointCutMethod() {}/*** 环绕通知.** @param pjp pjp* @return obj* @throws Throwable exception*/@Around("pointCutMethod()")public Object doAround(ProceedingJoinPoint pjp) throws Throwable {System.out.println("-----------------------");System.out.println("环绕通知: 进入方法");Object o = pjp.proceed();System.out.println("环绕通知: 退出方法");return o;}/*** 前置通知.*/@Before("pointCutMethod()")public void doBefore() {System.out.println("前置通知");}/*** 后置通知.** @param result return val*/@AfterReturning(pointcut = "pointCutMethod()", returning = "result")public void doAfterReturning(String result) {System.out.println("后置通知, 返回值: " + result);}/*** 异常通知.** @param e exception*/@AfterThrowing(pointcut = "pointCutMethod()", throwing = "e")public void doAfterThrowing(Exception e) {System.out.println("异常通知, 异常: " + e.getMessage());}/*** 最终通知.*/@After("pointCutMethod()")public void doAfter() {System.out.println("最终通知");}}
-
APP启动
public class App {public static void main(String[] args) {// create and configure beansApplicationContext context = new AnnotationConfigApplicationContext("com.seven.springframeworkaopannojdk");// retrieve configured instanceIJdkProxyService service = context.getBean(IJdkProxyService.class);// use configured instanceservice.doMethod1();service.doMethod2();try {service.doMethod3();} catch (Exception e) {// e.printStackTrace();}}
}
非接口使用Cglib代理
基于Cglib代理例子源码点这里
-
类定义
@Service
public class CglibProxyDemoServiceImpl {public void doMethod1() {System.out.println("CglibProxyDemoServiceImpl.doMethod1()");}public String doMethod2() {System.out.println("CglibProxyDemoServiceImpl.doMethod2()");return "hello world";}public String doMethod3() throws Exception {System.out.println("CglibProxyDemoServiceImpl.doMethod3()");throw new Exception("some exception");}
}
-
切面定义
和上面相同
-
APP启动
public class App {public static void main(String[] args) {// create and configure beansApplicationContext context = new AnnotationConfigApplicationContext("com.seven.springframeworkaopannocglib");// cglib proxy demoCglibProxyDemoServiceImpl service = context.getBean(CglibProxyDemoServiceImpl.class);service.doMethod1();service.doMethod2();try {service.doMethod3();} catch (Exception e) {// e.printStackTrace();}}
}
使用注解装配AOP
上面使用AspectJ的注解,并配合一个复杂的execution(* com.seven.springframeworkaopannojdk.service.*.*(..))
语法来定义应该如何装配AOP。还有另一种方式,则是使用注解来装配AOP,这两者一般存在与不同的应用场景中:
-
对于业务开发来说,一般使用 注解的方式来装配AOP,因为如果要使用AOP进行增强,业务开发就需要配置注解,业务能够很好的感知到这个方法(这个类)进行了增强。如果使用 表达式来装配AOP,当后续新增Bean,如果不清楚现有的AOP装配规则,容易被强迫装配,而在开发时未感知到,导致出现线上故障。例如,Spring提供的
@Transactional
就是一个非常好的例子。如果自己写的Bean希望在一个数据库事务中被调用,就标注上@Transactional
。 -
对于基础架构开发来说,无需业务感知到增强了什么方法,则可以使用表达式的方式来装配AOP。需要记录所有接口的耗时时长,直接写表达式,对业务无侵入
-
定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAspectAnno {}
-
修改切面类,使用注解的方式定义
@EnableAspectJAutoProxy
@Component
@Aspect
public class LogAspect {@Around("@annotation(logaspectanno)") //注意,括号里为logaspectanno,而不是LogAspectAnnopublic Object doAround(ProceedingJoinPoint pjp, LogAspectAnno logaspectanno) throws Throwable {System.out.println("-----------------------");System.out.println("环绕通知: 进入方法");Object o = pjp.proceed();System.out.println("环绕通知: 退出方法");return o;}}
-
修改实现类,这里只对 doMethod1 方法装配AOP
@Service
public class CglibProxyDemoServiceImpl {@LogAspectAnno()public void doMethod1() {System.out.println("CglibProxyDemoServiceImpl.doMethod1()");}public String doMethod2() {System.out.println("CglibProxyDemoServiceImpl.doMethod2()");return "hello world";}
}@Service
public class JdkProxyDemoServiceImpl implements IJdkProxyService {@LogAspectAnno@Overridepublic void doMethod1() {System.out.println("JdkProxyServiceImpl.doMethod1()");}@Overridepublic String doMethod2() {System.out.println("JdkProxyServiceImpl.doMethod2()");return "hello world";}
}
-
APP类
// create and configure beans
ApplicationContext context = new AnnotationConfigApplicationContext("com.seven.springframeworkaopannotation");// cglib proxy demo
CglibProxyDemoServiceImpl service1 = context.getBean(CglibProxyDemoServiceImpl.class);
service1.doMethod1();
service1.doMethod2();IJdkProxyService service2 = context.getBean(IJdkProxyService.class);
service2.doMethod1();
service2.doMethod2();
-
输出:
-----------------------
环绕通知: 进入方法
CglibProxyDemoServiceImpl.doMethod1()
环绕通知: 退出方法
CglibProxyDemoServiceImpl.doMethod2()
-----------------------
环绕通知: 进入方法
JdkProxyServiceImpl.doMethod1()
环绕通知: 退出方法
JdkProxyServiceImpl.doMethod2()
可以看到,只有doMethod1方法被增强了,doMethod2没有被增强,就是因为@LogAspectAnno 只注解了 doMethod1() 方法,从而实现更精细化的控制,是业务感知到这个方法是被增强了。
应用场景
我们知道AO能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,提高系统可拓展性和可维护性。
-
基于 AOP 实现统一的日志管理。
-
基于 Redisson + AOP 实现了接口防刷,一个注解即可限制接口指定时间内单个用户可以请求的次数。
-
基于 Spring Security 提供的
@PreAuthorize
实现权限控制,其底层也是基于 AOP。
日志记录
利用 AOP 方式记录日志,只需要在 Controller
的方法上使用自定义 @Log
日志注解,就可以将用户操作记录到数据库。
@Log(description = "新增用户")
@PostMapping(value = "/users")
public ResponseEntity create(@Validated @RequestBody User resources){checkLevel(resources);return new ResponseEntity(userService.create(resources),HttpStatus.CREATED);
}
AOP 切面类 LogAspect
用来拦截带有 @Log
注解的方法并处理:
@Aspect
@Component
public class LogAspect {private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);// 定义切点,拦截带有 @Log 注解的方法@Pointcut("@annotation(com.example.annotation.Log)") // 这里需要根据你的实际包名修改public void logPointcut() {}// 环绕通知,用于记录日志@Around("logPointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {//...}
}
限流
利用 AOP 方式对接口进行限流,只需要在 Controller
的方法上使用自定义的 @RateLimit
限流注解即可。
/*** 该接口 60 秒内最多只能访问 10 次,保存到 redis 的键名为 limit_test,*/
@RateLimit(key = "test", period = 60, count = 10, name = "testLimit", prefix = "limit")
public int test() {return ATOMIC_INTEGER.incrementAndGet();
}
AOP 切面类 RateLimitAspect
用来拦截带有 @RateLimit
注解的方法并处理:
@Slf4j
@Aspect
public class RateLimitAspect {// 拦截所有带有 @RateLimit 注解的方法@Around("@annotation(rateLimit)")public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {//...}
}
关于限流实现这里多说一句,这里并没有自己写 Redis Lua 限流脚本,而是利用 Redisson 中的 RRateLimiter
来实现分布式限流,其底层实现就是基于 Lua 代码+令牌桶算法。
权限控制
Spring Security 使用 AOP 进行方法拦截。在实际调用 update 方法之前,Spring 会检查当前用户的权限,只有用户权限满足对应的条件才能执行。
@Log(description = "修改菜单")
@PutMapping(value = "/menus")
// 用户拥有 `admin`、`menu:edit` 权限中的任意一个就能能访问`update`方法
@PreAuthorize("hasAnyRole('admin','menu:edit')")
public ResponseEntity update(@Validated @RequestBody Menu resources){//...
}
文章转载自:Seven
原文链接:https://www.cnblogs.com/seven97-top/p/18596202
体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构
相关文章:
Spring AOP基础、快速入门
介绍 AOP,面向切面编程,作为面向对象的一种补充,将公共逻辑(事务管理、日志、缓存、权限控制、限流等)封装成切面,跟业务代码进行分离,可以减少系统的重复代码和降低模块之间的耦合度。切面就是…...
Golang使用etcd构建分布式锁案例
在本教程中,我们将学习如何使用Go和etcd构建分布式锁系统。分布式锁系统对于管理对分布式系统中共享资源的并发访问至关重要。它有助于维护一致性,防止竞争条件,并确保在任何给定时间只有一个进程独占访问资源。 我们将使用Go作为编程语言&am…...
深度学习:基于MindSpore的极简风大模型微调
什么是PEFT?What is PEFT? PEFT(Parameter Efficient Fine-Tuning)是一系列让大规模预训练模型高效适应于新任务或新数据集的技术。 PEFT在保持大部分模型权重冻结,只修改或添加一小部份参数。这种方法极大得减少了计算量和存储开销&#x…...
如何在 Android 项目中实现跨库传值
背景介绍 在一个复杂的 Android 项目中,我们通常会有多个库(lib),而主应用程序(app)依赖所有这些库。目前遇到的问题是,在这些库中,libAd 需要获取 libVip 的 VIP 等级状态…...
HTML:表格重点
用表格就用table caption为该表上部信息,用来说明表的作用 thead为表头主要信息,效果加粗 tbody为表格中的主体内容 tr是 table row 表格的行 td是table data th是table heading表格标题 ,一般表格第一行的数据都是table heading...
STM32 出租车计价器系统设计(一) 江科大源码改写
STM32 出租车计价器系统设计 功能目标 驱动步进电机模拟车轮旋转,并实现调速功能。 设置车轮周长和单价,检测车轮转速和运转时间。 计算并显示行驶里程和价格。 硬件材料 28BYJ48 五线四相步进电机和 ULN2003 驱动板模块 测速传感器模块 嵌入式小系统…...
Git基础操作快速入门
Git是一个免费开源分布式版本控制工具,是由Linux的作者Linus开发的第二个伟大作品。2005年由于BitKeeper软件公司对Linux社区停止了免费使用权。Linus迫不得己自己开发了一个分布式版本控制工具,从而Git诞生了 目前使用Git作为版本控制的开源软件&#…...
vue‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。
在Windows操作系统中,安装了nodeJs之后,并且也安装了vue依赖包,但是在cmd控制台运行vue的时候,会报错:vue‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。针对这个问题我提供如下解决办法,并且是有效的。 一、原因分析 关于尝试这个问题的主要原因,我分析主要…...
JAVA安全—SpringBoot框架MyBatis注入Thymeleaf模板注入
前言 之前我们讲了JAVA的一些组件安全,比如Log4j,fastjson。今天讲一下框架安全,就是这个也是比较常见的SpringBoot框架。 SpringBoot框架 Spring Boot是由Pivotal团队提供的一套开源框架,可以简化spring应用的创建及部署。它提…...
Milvus向量数据库05-常见问题整理
Milvus向量数据库05-常见问题整理 1-什么是PipeLine 这张图展示了一个文档处理和搜索系统的架构,主要分为两个部分:Ingestion Pipeline(摄取管道)和 Search Pipeline(搜索管道)。下面是对图中各部分的详细…...
strncpy在复制含有多个\0的字符串时遇到的问题
strncpy在复制含有多个\0的字符串的时候,会产生截断,因为strncpy在读取源字符串的时候,遇到了\0,函数会认为该字符串已经结束了,然后会向目标字符串内填充\0。 char buffer[100] "ak\0jl";for (int i 0; i…...
C++作业3
作业1: 1.定义一个矩形类Rec,包含私有属性length、width,包含公有成员方法: void set_length(int l);//设置长度 Void set_width(int w);//设置宽度 Int get_length();//获取长度,将长度的值返回给调用处 Int get_widt…...
重生之我在学Vue--第1天 Vue 3 基础与开发环境搭建
重生之我在学Vue–第1天 Vue 3 基础与开发环境搭建 文章目录 重生之我在学Vue--第1天 Vue 3 基础与开发环境搭建前言一、Vue 3 的特点与核心概念二、搭建开发环境1. 安装 Node.js2. 使用 Vite 创建 Vue 3 项目创建项目进入项目目录并安装依赖启动开发服务器 3. 理解项目结构 三…...
企业经营数据分析系统:提升决策能力的利器
搭建企业经营数据分析系统是当今企业绕不开的话题,企业想要在竞争激烈的市场当中突围而出,需要对于企业内部的各种数据了然于胸,同时对于外部的数据也有敏锐的把握能力,因此企业构建自身的经营性数据分析系统就显得尤其重要。作为…...
Linux笔记9 DNS域名解析服务器
简介 DNS(Domain Name System)是互联网上的一项服务,它作为将域名和IP地址相互映射的一个分 布式数据库,能够使人更方便的访问互联网。 DNS使用的是53端口, 通常DNS是以UDP这个较快速的数据传输协议来查询的&#x…...
鸿蒙高级开发者认证的主观题试题及答案
以下是一份鸿蒙高级开发者认证的主观题试题及答案示例,涵盖了鸿蒙开发中的多个关键技术和应用场景相关内容,希望对你有所帮助: 一、论述题(每题 20 分,共 60 分) 1. 阐述鸿蒙操作系统中分布式软总线的工作原理、核心优势以及在多设备协同应用开发场景下的应用方式,并举…...
leetcode_547 省份数量
该题主要运用了图的连通性 接着使用染色法解决该问题 染色法:标记所有节点为false 访问后 将其标记位true class Solution {int n; // 代表n个数据bool colors[201]; // 标记是否访问到void dfs(vector<vector<int>>& isConnected, int u) { // …...
【开源】一款基于SpringBoot 的全开源充电桩平台
一、下载项目文件 下载源码项目文件口令:动作璆璜量子屏多好/~d1b8356ox2~:/复制口令后,进入夸克网盘app即可保存(如果复制到夸克app没有跳转资源,可以复制粘贴口令到夸克app的搜索框也可以打开(不用点搜索按钮&#…...
react antd tabs router 基础管理后台模版
在构建 React 后台管理系统时,使用标签页的方式展示路由是一种高效且用户友好的设计模式。这种实现方式通常允许用户在多个页面之间快速切换,并保留页面的状态,类似于浏览器的多标签页功能。 需求分析 1.动态标签页:根据用户的导…...
uniapp uni-table最简单固定表头
需求:固定表头数据,在网上找了半天,啥都有,就是一直实现不了,最后更改代码实现 1.效果 2.主要代码讲解完整代码 表格的父级一定要设置高度,不然会错位,我看网上说设置position:fixed…...
从0到1实现项目Docker编排部署
在深入讨论 Docker 编排之前,首先让我们了解一下 Docker 技术本身。Docker 是一个开源平台,旨在帮助开发者自动化应用程序的部署、扩展和管理。自 2013 年推出以来,Docker 迅速发展成为现代软件开发和运维领域不可或缺的重要工具。 Docker 采…...
Mac软件推荐
Mac软件推荐 截图SnipasteXnipBob 快捷启动Raycast 系统检测Stats 解压缩The UnarchiverKeka(付费) 视频播放IINA 视频下载Downie(付费) 屏幕刘海TopNotchMediaMate(付费)NotchDrop(付费&#x…...
No.4 笔记 探索网络安全:揭开Web世界的隐秘防线
在这个数字时代,网络安全无处不在。了解Web安全的基本知识,不仅能保护我们自己,也能帮助我们在技术上更进一步。让我们一起深入探索Web安全的世界,掌握那些必备的安全知识! 1. 客户端与WEB应用安全 前端漏洞࿱…...
Unity-Webview 使用指南
Unity-Webview 使用指南 Unity-Webview 主に gree/unity-webview のリファクタリング。本家を元に改良してく! [这里是图片001] 项目地址: https://gitcode.com/gh_mirrors/uni/Unity-Webview Unity-Webview 是一个专为 Unity 开发的 WebView 插件,使开…...
【Vue】自定义指令、插槽
目录 自定义指令 是什么 作用 使用方法 定义 使用 自定义指令配合绑定数据 语法 自定义指令的简写 语法 使用时机 插槽 什么是插槽 默认(匿名)插槽 编辑插槽的默认值 具名插槽 使用方法 简写 使用示例 作用域插槽 自定义指令 是什…...
AI - RAG中的状态化管理聊天记录
AI - RAG中的状态化管理聊天记录 大家好,今天我们来聊聊LangChain和LLM中一个重要的话题——状态化管理聊天记录。在使用大语言模型(LLM)的时候,聊天记录(History)和状态(State)管理是非常关键的。那我们先…...
微服务网关SpringCloudGateway、Kong比较
网关产品 1. Spring Cloud Gateway 基本信息 Spring Cloud Gateway是Spring Cloud生态系统中的一个组件,基于Spring 5、Project Reactor和Spring Boot 2构建。它旨在为微服务架构提供一种简单而有效的API网关解决方案。 功能特点 路由功能强大:使用Rou…...
MVC基础语法
文章目录 项目地址一、MVC的传值方式1.1 ViewBag和ViewData传值1.1.1 ViewBag1.1.2 ViewData 1.2 视图模型传值(ViewModel) 二、HttpConntext上下文三、中间件 项目地址 教程作者:誉尚学教育教程地址: https://www.bilibili.com…...
Web day09 会话技术 JWT令牌 Filter Interceptor
目录 会话技术: 1.Cookie: 2.Session: 3.令牌技术: JWT令牌: 生成JWT令牌: 校验JWT令牌(解析生成的令牌) 登陆时下发令牌: 过滤器Filter: 拦截器Inte…...
OpenCV相机标定与3D重建(14)用于组合两个旋转和平移(R|T)变换函数composeRT()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::composeRT 是 OpenCV 库中的一个函数,用于组合两个旋转和平移(R|T)变换。这个函数可以将两个连续的刚体变…...
leetcode33.搜索旋转排序数组
整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k1], ..., nums[n-1], nums[0], nums[1], ..…...
TMS Software:TMS BIZ产品——TMS XData
TMS XData 用于多层REST/JSON HTTP/HTTPS应用服务器开发和ORM远程处理的Delphi框架。 TMS扩展数据可同时用于以下框架: VCLWEBFMX TMS XData可同时用于以下操作系统/浏览器: TMS XData可同时用于以下IDE: 功能概述 基于REST/JSON架构风格的…...
leecode中的面试100题
isalnum函数用于检查一个字符是否为字母或数字。它的参数是一个int类型 isupper() 是大写 islower() 是小写 toupper() 变成大写 tolower() 变成小写 do while 首先不管怎么 先执行do一次 然后执行完了之后 在判断while如果符合while里面就继续执行do vector容器中insert…...
不同类型的集成技术——Bagging、Boosting、Stacking、Voting、Blending简述
目录 一、说明 二、堆叠 2.1 堆叠的工作原理: 2.2 例子: 2.3 堆叠的优点: 三、投票(简单投票) 3.1 例子: 3.2 投票的优点: 四、装袋和投票之间的区别 五、混合 6.1 混合的主要特征: …...
【从零开始入门unity游戏开发之——C#篇01】理论开篇
文章目录 前言前置条件什么是编程?什么是代码?什么是编程语言?常见的编程语言什么是C#?学习Unity为什么要先学习C#?选择适合自己的IDE集成开发环境VSCode安装和环境配置VSCode调试模式专栏推荐完结 前言 这个系列我想…...
TCP的“可靠性”(上)
目录 TCP的“可靠性”(上)确认应答(可靠性传输的基础)超时重传连接管理(三次握手,四次挥手) TCP的“可靠性”(上) 想必大家都或多或少的听说过TCP的特性:有连…...
Windows平台Unity3D下如何低延迟低资源占用播放RTMP或RTSP流?
技术探讨 自2017年我们发布跨平台的低延迟Unity下的RTSP|RTMP直播播放器后,Unity下的直播体验有了质的提升,特别是RTMP,从大家认知里面的几秒钟,直接缩减到100-300ms,满足了绝大多数场景下低延迟的技术诉求。今天就Un…...
burp的编解码,日志,比较器
声明! 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关&a…...
Vercel部署前端部署
Vercel 部署 今天要讲的是如何对别人向自己的开源仓库提的PR进行自动代码审核 1. 注册并登录Vercel 访问 Vercel官网点击右上角的"Sign Up"选择使用GitHub、GitLab、Bitbucket或邮箱注册完成注册流程并登录 2. 连接代码仓库 在Vercel仪表板,点击"New Proje…...
Jenkins相关的Api接口调用详解
Jenkins API是Jenkins持续集成和持续部署(CI/CD)平台提供的一组接口,允许外部程序通过HTTP请求与Jenkins进行交互。以下是对Jenkins API使用的简介: 一、Jenkins API的主要功能 作业管理:通过API,可以创建、配置、删除以及查询作业(Job)。构建触发:可以远程触发新的构…...
HBU深度学习实验15-循环神经网络(2)
LSTM的记忆能力实验 飞桨AI Studio星河社区-人工智能学习与实训社区 (baidu.com) 长短期记忆网络(Long Short-Term Memory Network,LSTM)是一种可以有效缓解长程依赖问题的循环神经网络.LSTM 的特点是引入了一个新的内部状态&am…...
洛谷P1364 医院设置(c嘎嘎)
题目链接:P1364 医院设置 - 洛谷 | 计算机科学教育新生态 题目难度:普及/提高 数据规模与约定: 对于 100%100% 的数据,保证 1≤n≤1001≤n≤100,0≤u,v≤n0≤u,v≤n,1≤w≤1051≤w≤105。 解题思路&…...
Java死锁问题如何解决?
大家好,我是锋哥。今天分享关于【Java死锁问题如何解决?】面试题。希望对大家有帮助; Java死锁问题如何解决? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Java中的死锁(Deadlock)是一种并发…...
go锁与chan的性能对比
锁的作用chan 的作用golang的数据并不是并发安全的为什么锁的性能更加优秀?如何选择? 锁的作用 解决并发安全问题,流程控制等 chan 的作用 线程通信(数据传输), 并发安全,流程控制 golang的数据并不是并发安全的 golang的变量并不是并发安全的锁与chan都可以解决并发安全…...
最小二乘法拟合出二阶响应面近似模型
背景:根据样本试验数据拟合出二阶响应面近似模型(正交二次型),并使用决定系数R和调整的决定系数R_adj来判断二阶响应面模型的拟合精度。 1、样本数据(来源:硕士论文《航空发动机用W形金属密封环密封性能分析…...
Scala的隐式转换
package hfdobject Test37 { //复习隐式转换//隐式转换:编译器 偷偷地,自动的帮我们把一种数据类型转换为另外一种类型//列如:int -->double//它有失败的时候(double -->int),有成功的时候//当它转换失败的时候,…...
vue中父组件接收子组件的多个参数的方法:$emit或事件总线
方法一:使用 $emit 方法 原理 子组件通过 $emit 方法向父组件发送事件,同时可以传递多个参数,父组件通过事件监听来接收这些参数。 示例 子组件代码 <template><div><button click"sendData">发送数据</…...
网络安全法-网络安全支持与促进
第二章 网络安全支持与促进 第十五条 国家建立和完善网络安全标准体系。国务院标准化行政主管部门和国务院其他有关部门根据各自的职责,组织制定并适时修订有关网络安全管理以及网络产品、服务和运行安全的国家标准、行业标准。 国家支持企业、研究机构、高等学…...
prometheusgrafana实现监控告警
Prometheus负责集群数据的监控和采集,然后传递给grafana进行可视化,集成睿象云可实现监控报警,为了方便操作,可以通过iframe嵌套grafana到指定的页面。 文章目录 1.Grafana集成Prometheus2.iframe内嵌grafana3.监控告警 1.Grafana…...
php:完整部署Grid++Report到php项目,并实现模板打印
一、下载Grid++Report软件 路径:开发者安装包下载 - 锐浪报表工具 二、 安装软件 1、对下载的压缩包运行内部的exe文件 2、选择语言 3、 完成安装引导 下一步即可 4、接收许可协议 点击“我接受” 5、选择安装路径 “浏览”选择安装路径,点击"安装" 6、完成…...