Spring_AOP

一、AOP简介

AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程0OP的升华。OOP是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面, 而用这种思维去设计编程的方式叫做面向切面编程。
在这里插入图片描述

二、AOP思想的实现方案

动态代理技术,在运行期间,对目标对象的方法进行增强,代理对象同名方法内可以执行原有逻辑的同时嵌入执行其他增强逻辑或其他对象的方法。
例如:
A对象为我们要增强的目标对象,B对象为增强方法所在的对象。在动态代理后,我们会获得一个A对象的
在这里插入图片描述
接下来我们通过这种方式来增强spring容器中的一个Bean
(项目创建成功之后的目录)不要忘了,每个bean都需要被spring容器管理,所以需要在类上添加注解@Component或者@Service
在这里插入图片描述
① 首先创建一个项目,创建Userservice接口,并实现它,这个UserServiceImpl对象就相当于A对象

public interface UserService {public void show1();public void show2();
}
@Service
public class UserServiceImpl implements UserService {@Overridepublic void show1() {System.out.println("UserServiceImpl show1");}@Overridepublic void show2() {System.out.println("UserServiceImpl show2");}
}

②然后创建增强类Advice,相当于B对象

@Component
public class MyAdvice {public void beforeAdvice(){System.out.println("前置增强...");}public void afterAdvice(){System.out.println("后置增强...");}
}

③最后创建一个Bean的后置处理类,这个后置处理类会返回一个被增强的bean,相当于A对象的Proxy对象

@Component
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {private ApplicationContext applicationContext;@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {//目的:对UserServiceImpl中的show1和show2方法进行增强,增强方法存在于MyAdvice中//问题1:筛选service.impl包下的所有方法都可以进行增强,解决方法是if-else//问题2:如果获取MyAdvice,解决方案从Spring容器中获取//加一个条件判断,只有在"com.itheima.service.impl"包下的bean才能够被增强if(bean.getClass().getPackage().getName().equals("com.itheima.service.impl")){//使用Proxy类生成当前获取到的对象的代理对象Object beanProxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//获取增强方法所在的对象MyAdvice advice = applicationContext.getBean(MyAdvice.class);//执行前置增强方法advice.beforeAdvice();//执行目标方法Object result = method.invoke(bean,args);//执行bean的目标方法,携带参数//执行后置增强方法advice.afterAdvice();return result;}});return beanProxy;}return bean;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}

三、Spring_AOP的相关概念

在这里插入图片描述

四、基于xml配置的AOP

1.xml方式AOP快速入门

2.步骤

1.导入AOP相关坐标
2.准备目标类、准备增强类、并配置给Spring管理
3.配置切点表达式(哪些方法被增强)
4.配置织入(切点被哪些方法增强,是前置增强还是后置增强)

五、基于注解的方式配置的AOP

1.AOP的注解介绍

注解作用
@Aspect切面声明,标注在类、接口(包括注解类型)或枚举上。
@Pointcut切入点声明,即切入到哪些目标类的目标方法。既可以用 execution 切点表达式, 也可以是annotation 指定拦截拥有指定注解的方法。value 属性指定切入点表达式,默认为 “”,用于被通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式
@Before前置通知, 在目标方法(切入点)执行之前执行。value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式。注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知。
@After后置通知, 在目标方法(切入点)执行之后执行
@AfterReturning返回通知, 在目标方法(切入点)返回结果之后执行。pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 “”
@AfterThrowing异常通知, 在方法抛出异常之后执行, 意味着跳过返回通知,pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 " " 注意:如果目标方法自己 try-catch 了异常,而没有继续往外抛,则不会进入此回调函数
@Around环绕通知:目标方法执行前后分别执行一些代码,类似拦截器,可以控制目标方法是否继续执行。通常用于统计方法耗时,参数校验等等操作。

正常流程:【环绕通知-前】-> 【前置通知】-> 【返回通知】-> 【后置通知】->【环绕通知-后】。

2.AOP的切点表达式

切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强,语法如下:
execution ( [访问修饰符] 返回值类型 包名.类名.方法名(参数) )
其中:
1.访问修饰符可以省略不写
2.返回值类型、某一级的包名、类名、方法名可以使用*表示任意
3.包名与类型之间使用单点.表示该包下的类,使用双点 .. 表示该包及其子包下的所有类
4.参数列表可以使用两个点..表示任意参数
例如 "execution(* com.itheima.service.impl.*.*(..))"
表示权限修饰符默认、任意返回值类型、com.itheima.service.impl包下、所有类、所有方法、任意个参数

在这里插入图片描述

3.注解方式AOP快速入门

4.步骤

1.导入AOP相关坐标

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.准备目标类并配置给Spring管理

@Service
public class UserServiceImpl implements UserService {@Overridepublic void show1() {
//        int a = 1/0;System.out.println("UserServiceImpl show1");}@Overridepublic void show2() {System.out.println("UserServiceImpl show2");}
}

3.使用注解配置切点、切面类、通知类型等

@Component
@Aspect
public class MyAdvice2 {@Before("execution(* com.itheima.service.impl.*.*(..))")public void beforeAdvice(JoinPoint joinPoint){System.out.println("当前增强的对象是:"+joinPoint.getTarget());System.out.println("表达式:"+joinPoint.getStaticPart());System.out.println("注解配置的前置增强...");System.out.println("======================");}@AfterReturning("execution(* com.itheima.service.impl.*.*(..))")public void afterReturningAdvice(JoinPoint joinPoint){System.out.println("当前增强的对象是:"+joinPoint.getTarget());System.out.println("表达式:"+joinPoint.getStaticPart());System.out.println("注解配置的后置增强...");System.out.println("======================");}@Around("execution(* com.itheima.service.impl.*.*(..))")public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("当前增强的对象是:"+proceedingJoinPoint.getTarget());System.out.println("表达式:"+proceedingJoinPoint.getStaticPart());System.out.println("环绕的前置增强");Object proceed = proceedingJoinPoint.proceed();//执行目标方法System.out.println("环绕的后置增强");System.out.println("======================");return proceed;}@AfterThrowing(pointcut = "execution(* com.itheima.service.impl.*.*(..))",throwing = "e")public void afterThrowingAdvice(Throwable e){System.out.println("当前的异常信息为:"+e);System.out.println("异常抛出通知...报异常才执行");System.out.println("======================");}@After("execution(* com.itheima.service.impl.*.*(..))")public void afterAdvice(){System.out.println("最终的增强");System.out.println("======================");}
}

4.启动类注解@EnableAspectJAutoProxy开启Spring AOP

@SpringBootApplication
@EnableAspectJAutoProxy
public class SpringAopTest01Application {public static void main(String[] args) {SpringApplication.run(SpringAopTest01Application.class, args);}}

在第三步,使用注解配置通知类型和切点时,五种通知类型中都使用了"execution(* com.itheima.service.impl.*.*(..))"。这样显然是不够优雅的。
我们可以将切点抽取出来:

@Component
@Aspect
public class MyAdvice2 {//抽取切点出来@Pointcut("execution(* com.itheima.service.impl.*.*(..))")public void myPointCut(){}//    @Before("execution(* com.itheima.service.impl.*.*(..))")@Before("myPointCut()")public void beforeAdvice(JoinPoint joinPoint){System.out.println("当前增强的对象是:"+joinPoint.getTarget());System.out.println("表达式:"+joinPoint.getStaticPart());System.out.println("注解配置的前置增强...");System.out.println("======================");}//    @AfterReturning("execution(* com.itheima.service.impl.*.*(..))")@AfterReturning("myPointCut()")public void afterReturningAdvice(JoinPoint joinPoint){System.out.println("当前增强的对象是:"+joinPoint.getTarget());System.out.println("表达式:"+joinPoint.getStaticPart());System.out.println("注解配置的后置增强...");System.out.println("======================");}//    @Around("execution(* com.itheima.service.impl.*.*(..))")@Around("myPointCut()")public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("当前增强的对象是:"+proceedingJoinPoint.getTarget());System.out.println("表达式:"+proceedingJoinPoint.getStaticPart());System.out.println("环绕的前置增强");Object proceed = proceedingJoinPoint.proceed();//执行目标方法System.out.println("环绕的后置增强");System.out.println("======================");return proceed;}//    @AfterThrowing(pointcut = "execution(* com.itheima.service.impl.*.*(..))",throwing = "e")@AfterThrowing(value = "myPointCut()",throwing = "e")public void afterThrowingAdvice(Throwable e){System.out.println("当前的异常信息为:"+e);System.out.println("异常抛出通知...报异常才执行");System.out.println("======================");}//    @After("execution(* com.itheima.service.impl.*.*(..))")@After("myPointCut()")public void afterAdvice(){System.out.println("最终的增强");System.out.println("======================");}
}

这样基于注解方式的AOP就编写完成了,我们测试一下,调用com.itheima.service.impl包下的类的方法
在这里插入图片描述
在这里插入图片描述
运行结果:在这里插入图片描述
可以看到这些增强方法都执行了,而且我们并没有修改目标方法。一点原代码都没动,直接在切面类上配置需要被增强的方法的签名,就实现了方法增强,这就是AOP。

六、使用AOP实现日志功能

上面我们使用AOP实现了对方法的增强,这里我们继续使用AOP实现项目中常用的日志记录功能。

1.思路

在每次方法的之前记录当前时间,执行之后记录当前时间,获取方法执行耗时。
在方法执行之后记录方法所在的模块执行的操作方法的名称方法的参数请求发起的IP地址
使用环绕通知类型实现该功能。

2.步骤

  • 1.前提准备:一个IP地址获取的工具类、一个获取HttpServletRequest的工具类
package com.itheima.util;import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;/*** IP相关工具类** @author xiegege* @date 2021/02/22 16:08*/
@Slf4j
public class IpUtil {/*** 获取当前网络ip*/public static String getIpAddr(HttpServletRequest request) {String ipAddress = request.getHeader("x-forwarded-for");if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("WL-Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getRemoteAddr();if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) {// 根据网卡取本机配置的IPInetAddress inet = null;try {inet = InetAddress.getLocalHost();} catch (UnknownHostException e) {e.printStackTrace();}ipAddress = inet.getHostAddress();}}//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割  //"***.***.***.***".length() = 15if (ipAddress != null && ipAddress.length() > 15) {if (ipAddress.indexOf(",") > 0) {ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));}}return ipAddress;}/*** 获取真实IP*/public static String getRealIp(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");return checkIp(ip) ? ip : (checkIp(ip = request.getHeader("Proxy-Client-IP")) ? ip : (checkIp(ip = request.getHeader("WL-Proxy-Client-IP")) ? ip :request.getRemoteAddr()));}/*** 校验IP*/private static boolean checkIp(String ip) {return !StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip);}/*** 获取操作系统,浏览器及浏览器版本信息*/public static Map<String, String> getOsAndBrowserInfo(HttpServletRequest request) {String userAgent = request.getHeader("User-Agent");String user = userAgent.toLowerCase();String os;String browser = "";//=================OS Info=======================if (userAgent.toLowerCase().contains("windows")) {os = "Windows";} else if (userAgent.toLowerCase().contains("mac")) {os = "Mac";} else if (userAgent.toLowerCase().contains("x11")) {os = "Unix";} else if (userAgent.toLowerCase().contains("android")) {os = "Android";} else if (userAgent.toLowerCase().contains("iphone")) {os = "IPhone";} else {os = "UnKnown, More-Info: " + userAgent;}//===============Browser===========================try {if (user.contains("edge")) {browser = (userAgent.substring(userAgent.indexOf("Edge")).split(" ")[0]).replace("/", "-");} else if (user.contains("msie")) {String substring = userAgent.substring(userAgent.indexOf("MSIE")).split(";")[0];browser = substring.split(" ")[0].replace("MSIE", "IE") + "-" + substring.split(" ")[1];} else if (user.contains("safari") && user.contains("version")) {browser = (userAgent.substring(userAgent.indexOf("Safari")).split(" ")[0]).split("/")[0]+ "-" + (userAgent.substring(userAgent.indexOf("Version")).split(" ")[0]).split("/")[1];} else if (user.contains("opr") || user.contains("opera")) {if (user.contains("opera")) {browser = (userAgent.substring(userAgent.indexOf("Opera")).split(" ")[0]).split("/")[0]+ "-" + (userAgent.substring(userAgent.indexOf("Version")).split(" ")[0]).split("/")[1];} else if (user.contains("opr")) {browser = ((userAgent.substring(userAgent.indexOf("OPR")).split(" ")[0]).replace("/", "-")).replace("OPR", "Opera");}} else if (user.contains("chrome")) {browser = (userAgent.substring(userAgent.indexOf("Chrome")).split(" ")[0]).replace("/", "-");} else if ((user.contains("mozilla/7.0")) || (user.contains("netscape6")) ||(user.contains("mozilla/4.7")) || (user.contains("mozilla/4.78")) ||(user.contains("mozilla/4.08")) || (user.contains("mozilla/3"))) {browser = "Netscape-?";} else if (user.contains("firefox")) {browser = (userAgent.substring(userAgent.indexOf("Firefox")).split(" ")[0]).replace("/", "-");} else if (user.contains("rv")) {String ieVersion = (userAgent.substring(userAgent.indexOf("rv")).split(" ")[0]).replace("rv:", "-");browser = "IE" + ieVersion.substring(0, ieVersion.length() - 1);} else {browser = "UnKnown";}} catch (Exception e) {log.error("获取浏览器版本失败");log.error(e.getMessage());browser = "UnKnown";}Map<String, String> result = new HashMap<>(2);result.put("OS", os);result.put("BROWSER", browser);return result;}/*** 判断是否是内网IP*/public static boolean internalIp(String ip) {byte[] addr = textToNumericFormatV4(ip);return internalIp(addr) || "127.0.0.1".equals(ip);}private static boolean internalIp(byte[] addr) {if (StringUtils.isEmpty(addr) || addr.length < 2) {return true;}final byte b0 = addr[0];final byte b1 = addr[1];// 10.x.x.x/8final byte SECTION_1 = 0x0A;// 172.16.x.x/12final byte SECTION_2 = (byte) 0xAC;final byte SECTION_3 = (byte) 0x10;final byte SECTION_4 = (byte) 0x1F;// 192.168.x.x/16final byte SECTION_5 = (byte) 0xC0;final byte SECTION_6 = (byte) 0xA8;switch (b0) {case SECTION_1:return true;case SECTION_2:if (b1 >= SECTION_3 && b1 <= SECTION_4) {return true;}case SECTION_5:if (b1 == SECTION_6) {return true;}default:return false;}}/*** 将IPv4地址转换成字节** @param text IPv4地址* @return byte 字节*/public static byte[] textToNumericFormatV4(String text) {if (text.length() == 0) {return null;}byte[] bytes = new byte[4];String[] elements = text.split("\\.", -1);try {long l;int i;switch (elements.length) {case 1:l = Long.parseLong(elements[0]);if ((l < 0L) || (l > 4294967295L)) {return null;}bytes[0] = (byte) (int) (l >> 24 & 0xFF);bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);bytes[3] = (byte) (int) (l & 0xFF);break;case 2:l = Integer.parseInt(elements[0]);if ((l < 0L) || (l > 255L)) {return null;}bytes[0] = (byte) (int) (l & 0xFF);l = Integer.parseInt(elements[1]);if ((l < 0L) || (l > 16777215L)) {return null;}bytes[1] = (byte) (int) (l >> 16 & 0xFF);bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);bytes[3] = (byte) (int) (l & 0xFF);break;case 3:for (i = 0; i < 2; ++i) {l = Integer.parseInt(elements[i]);if ((l < 0L) || (l > 255L)) {return null;}bytes[i] = (byte) (int) (l & 0xFF);}l = Integer.parseInt(elements[2]);if ((l < 0L) || (l > 65535L)) {return null;}bytes[2] = (byte) (int) (l >> 8 & 0xFF);bytes[3] = (byte) (int) (l & 0xFF);break;case 4:for (i = 0; i < 4; ++i) {l = Integer.parseInt(elements[i]);if ((l < 0L) || (l > 255L)) {return null;}bytes[i] = (byte) (int) (l & 0xFF);}break;default:return null;}} catch (NumberFormatException e) {return null;}return bytes;}/*** 获取IP*/public static String getHostIp() {try {return InetAddress.getLocalHost().getHostAddress();} catch (UnknownHostException e) {e.printStackTrace();}return "127.0.0.1";}/*** 获取主机名*/public static String getHostName() {try {return InetAddress.getLocalHost().getHostName();} catch (UnknownHostException e) {e.printStackTrace();}return "未知";}
}

这里使用了RequestContextHolder的静态方法来获取HttpServletRequestRequestContextHolder的获取需要引入javaweb相关的依赖,springboot可以直接引入spring-boot-starter-web

package com.itheima.util;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/*** @author MrBird*/
public class HttpContextUtil {private HttpContextUtil(){}//获取HttpServletRequest请求public static HttpServletRequest getHttpServletRequest() {return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();}
}
  • 2.编写自定义注解

该注解用于标记方法,被标记的方法会被增强

package com.itheima.myAnnotation;
import java.lang.annotation.*;/*** @author Watching* * @date 2023/8/14* * Describe:标记 方法需要被记录日志*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {//方法所属模块String module() default "";//方法执行的操作String operation() default "";
}
  • 3.编写一个TestLogService接口并实现它,并在实现类的方法上面添加自定义注

TestLogService接口

public interface TestLogService {public void func1();public void func2();public void func3();
}

TestLogServiceImpl实现类

@Service
public class TestLogServiceImpl implements TestLogService {@Override@LogAnnotation(module = "测试模块",operation = "存钱")public void func1() {//TODO 假如这个方法是存钱方法}@Override@LogAnnotation(module = "测试模块",operation = "取钱")public void func2() {//TODO 假如这个方法是取钱方法}@Override@LogAnnotation(module = "测试模块",operation = "查询余额")public void func3() {//TODO 假如这个方法是查看多少钱方法}
}
  • 4.编写切面类
package com.itheima.advice;import com.alibaba.fastjson.JSON;
import com.itheima.myAnnotation.LogAnnotation;
import com.itheima.util.HttpContextUtil;
import com.itheima.util.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;/*** @author Watching* * @date 2023/8/14* * Describe:切面类,实现日志记录*/
@Component
@Aspect
@Slf4j
public class MyAdvice3 {//定义切点@Pointcut("@annotation(com.itheima.myAnnotation.LogAnnotation)")public void pt() {}@AfterThrowing(value = "pt()",throwing = "e")public void ifError(Throwable e){System.out.println("当前的异常信息为:"+e);System.out.println("异常抛出通知...报异常才执行");System.out.println("======================");e.printStackTrace();//打印异常的堆栈轨迹信息}//编写环绕通知@Around("pt()")public Object log(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {long start = System.currentTimeMillis();Object proceed = proceedingJoinPoint.proceed();long end = System.currentTimeMillis();long time = end - start;//记录日志recordLog(proceedingJoinPoint, time);return proceed;}//编写日志记录函数private void recordLog(ProceedingJoinPoint joinPoint, long time) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);log.info("=================log start==========================");//方法所在模块,执行的操作log.info("module: {}", logAnnotation.module());log.info("operation: {}", logAnnotation.operation());//请求的方法名String className = joinPoint.getTarget().getClass().getName();String methodName = signature.getName();log.info(" request method:{}", className + "." + methodName + "()");//请求的参数Object[] args = joinPoint.getArgs();if (args.length == 0) {log.info("params:{}", "null");} else {String params = JSON.toJSONString(args[0]);log.info("params:{}", params);}//获取request设置IP地址HttpServletRequest request = HttpContextUtil.getHttpServletRequest();log.info("ip:{}", IpUtil.getIpAddr(request));log.info("excute time : {} ms", time);log.info("=================log end=======================");}
}

编写完成,写个测试类测试一下

package com.itheima;
import com.itheima.service.TestLogService;
import com.itheima.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
class SpringAopTest01ApplicationTests {@ResourceUserService userService;@ResourceTestLogService logService;
//    @Test
//    void contextLoads() {
//        userService.show1();
//    }@Testvoid test(){logService.func1();logService.func2();logService.func3();}
}

测试结果:完成、每个方法执行后都输出了日志,当我们的某个方法出问题时,就可以根据这个日志快速定位
在这里插入图片描述

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

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

相关文章

算法训练营题目day17

110. 平衡二叉树 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a; 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 func isBalanced(root *TreeNode) bool {h:getHeight(root)if h -1{r…

Vue 安装开发者工具

1.下载开发者工具&#xff0c;下载地址&#xff1a;http://book.wiyp.top/App/Vue3开发者工具-谷歌/Vue3.crx 2.打开谷歌浏览器&#xff0c;点击扩展&#xff0c;点击管理扩展程序。 3.开启开发者模式&#xff0c;将 Vue3 开发者工具文件拖拽到浏览器中进行安装。 注&#xff…

chatGPT小白快速入门培训课程-001

一、前言 本文是《chatGPT小白快速入门培训课程》的第001篇文章&#xff0c;全部内容采用chatGPT和chatGPT开源平替软件生成。完整内容大纲详见&#xff1a;《chatGPT小白快速入门课程大纲》。 本系列文章&#xff0c;参与&#xff1a; AIGC征文活动 #AIGC技术创作内容征文# …

使用pymupdf实现PDF内容搜索并显示功能

简介&#xff1a; 在日常工作和学习中&#xff0c;我们可能需要查找和提取PDF文件中的特定内容。本文将介绍如何使用Python编程语言和wxPython图形用户界面库来实现一个简单的PDF内容搜索工具。我们将使用PyMuPDF模块来处理PDF文件&#xff0c;并结合wxPython构建一个用户友好的…

动态HTTP代理与竞争情报收集的关联

Hey&#xff0c;各位爬友们&#xff01;作为一名专业的爬虫HTTP代理提供者&#xff0c;今天我要和大家聊一聊动态HTTP代理与竞争情报收集之间的关联。在这篇文章中&#xff0c;我将向大家解释怎么使用动态HTTP代理完成在竞争中的情报收集&#xff0c;并分享一些实用的技巧。 首…

虹科方案 | 汽车总线协议转换解决方案(二)

上期说到&#xff0c;虹科的PCAN-LIN网关在CAN、LIN总线转换方面有显著的作用&#xff0c;尤其是为BMS电池通信的测试提供了优秀的解决方案。假如您感兴趣&#xff0c;可以点击文末相关链接进行回顾&#xff01; 而今天&#xff0c;虹科将继续给大家带来Router系列在各个领域的…

Netty:判断ByteBuf底层是否被NIO direct buffer支撑

说明 io.netty.buffer.ByteBuf的函数isDirect()可以判断该ByteBuf底层是否被NIO direct buffer支撑。如果结果返回true&#xff0c;表示底层被NIO direct buffer支撑。 示例 package com.thb;import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; imp…

elasticsearch 基础

ES 搜索技术历史 今天看的是《Elasticsearch实战与原理解析》 第一章 搜索技术发展史 1、搜索技术发展史 宏观而言&#xff0c;搜索引擎的发展经历了五个尖端和两大分类。五个阶段分别是ftp文件检索阶段、分类目录阶段、文本相关性检索阶段、网页链接分析阶段和用户意图识别…

算法leetcode|69. x 的平方根(rust重拳出击)

文章目录 69. x 的平方根&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 69. x 的平方根&#xff1a; 给你一个非负整数 x &#xff0c…

win10电脑npm run dev报错解决

npm run dev报错解决 出现错误前的操作步骤错误日志解决步骤 出现错误前的操作步骤 初始化Vue项目 $ npm create vue3.6.1创建项目文件夹client Vue.js - The Progressive JavaScript Framework✔ Project name: › client ✔ Add TypeScript? › No ✔ Add JSX Support? …

【Pytorch:nn.Embedding】简介以及使用方法:用于生成固定数量的具有指定维度的嵌入向量embedding vector

文章目录 1、nn.Embedding2、使用场景 1、nn.Embedding 首先我们讲解一下关于嵌入向量embedding vector的概念 1&#xff09;在自然语言处理NLP领域&#xff0c;是将单词、短语或其他文本单位映射到一个固定长度的实数向量空间中。嵌入向量具有较低的维度&#xff0c;通常在几…

计算机网络中速率和带宽的区别

速率&#xff0c;指的是连接在计算机网络上的主机在数字信道上传送数据的速率&#xff0c;它也称为数据率或比特率&#xff0c;单位是bps。速率往往指的是额定速率或者标称速率&#xff0c;意思也就是在非常理想的情况下才能达到的数据传送的速率&#xff0c;然而在现实生活中是…

[Mongodb 5.0]单机启动

安装完mongodb后&#xff0c;会自动生成下面两个目录(mongod.conf中设定的)&#xff0c;用来存放日志和数据 /var/lib/mongo (数据目录) /var/log/mongodb (日志目录) 要启动一个单机版的mongodb&#xff0c;一般有两种方式&#xff1a; 第一种启动方式&#xff1a;直接使用…

第5章:神经网络

神经元模型 上述定义的简单单元即为神经元模型。 多层网络 误差逆传播算法 标准BP算法&#xff1a;参数更新非常频繁&#xff0c;可能出现抵消现象。积累BP算法&#xff1a;下降到一定程度上&#xff0c;进行下一步会非常缓慢。 过拟合 早停&#xff1a;划分训练集和验证集…

Java bean 是个什么概念?

Java bean可以把它比作一个"智能的容器"&#xff0c;它具备封装数据的能力。 Java bean是一种可重用的软件组件&#xff0c;它主要用于在Java应用程序中存储和传递数据。它是一种符合特定规范的Java类&#xff0c;通过封装数据和提供访问方法&#xff0c;使数据的管…

vue3+ts使用antv/x6

使用 2.x 版本 x6.antv 新官网: 安装 npm install antv/x6 //"antv/x6": "^2.1.6",项目结构 1、初始化画布 index.vue <template><div id"container"></div> </template><script setup langts> import { onM…

redis — 基于Spring Boot实现redis延迟队列

1. 业务场景 延时队列场景在我们日常业务开发中经常遇到&#xff0c;它是一种特殊类型的消息队列&#xff0c;它允许把消息发送到队列中&#xff0c;但不立即投递给消费者&#xff0c;而是在一定时间后再将消息投递给消费者。延迟队列的常见使用场景有以下几种&#xff1a; 在…

HoudiniVex笔记_P23_SDFBasics有向距离场

原视频&#xff1a;https://www.youtube.com/playlist?listPLzRzqTjuGIDhiXsP0hN3qBxAZ6lkVfGDI Bili&#xff1a;Houdini最强VEX算法教程 - VEX for Algorithmic Design_哔哩哔哩_bilibili Houdini版本&#xff1a;19.5 1、什么是SDF Houdini支持两种体积类型&#xff0c;…

使用wxPython和PyMuPDF提取PDF页面指定页数的内容的应用程序

在本篇博客中&#xff0c;我们将探讨如何使用wxPython和PyMuPDF库创建一个简单的Bokeh应用程序&#xff0c;用于选择PDF文件并提取指定页面的内容&#xff0c;并将提取的内容显示在文本框中。 C:\pythoncode\new\pdfgetcontent.py 准备工作 首先&#xff0c;确保你已经安装了…

44 | 酒店预订及取消的数据分析

1.背景介绍 数据集来自Kaggle网站上公开的Hotel booking demand项目 该数据集包含了一家城市酒店和一家度假酒店的预订信息,包括预订时间、入住时间、成人、儿童或婴儿数量、可用停车位数量等信息。 数据集容量约为12万32 本次数据分析主要包含如下内容: 总览数据,完成对…