2023新版Spring6全新讲解-核心内容之AOP

news2024/11/22 16:18:55

Spring核心之AOP

在这里插入图片描述

一、前置基础-代理模式

  在学习Spring的AOP之前我们需要补充下设计模式中的代理模式。这块是理解AOP的必备基础内容。

image.png

1. 静态代理

  若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下, 静态代理中的代理类和目标类会实现同一接口或是派生自相同的父类。

先定义公共接口

/**
 * 代理模式
 *    定义的公共接口
 */
public interface SomeService {
    String doSome();
}

然后定义目标对象:

/**
 * 代理模式
 *    目标对象:Target Object
 */
public class SomeServiceImpl implements SomeService{
    @Override
    public String doSome() {
        System.out.println("目标对象执行了。。。");
        return "Hello";
    }
}

然后定义我们的代理对象:

/**
 * 代理模式
 *    代理类:需要和目标对象实现相同的接口
 */
public class SomeProxy implements SomeService{

    // 代理对象持有的目标对象
    private SomeService target;

    public SomeProxy(SomeService target){
        this.target = target;
    }

    /**
     * 代理对象需要增强的方法
     * @return
     */
    @Override
    public String doSome() {
        System.out.println("目标对象执行之前");
        // 应该需要让目标对象来完成核心的业务
        String msg = target.doSome();
        System.out.println("目标对象执行之后");
        return msg.toUpperCase();
    }
}

最后做测试:

    /**
     * 静态代理的测试
     */
    @Test
    public void test1(){
        // 获取目标对象
        SomeService target = new SomeServiceImpl();
        // 获取代理对象
        SomeService proxy = new SomeProxy(target);
        // 通过代理对象执行方法
        System.out.println(proxy.doSome());
    }

测试结果:

目标对象执行之前
目标对象执行了。。。
目标对象执行之后
HELLO

2. 动态代理

  代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。
代理类型 使用场景:

  • JDK动态代理:如果目标对象实现了接口,采用JDK的动态代理
  • CGLIB动态代理:如果目标对象没有实现了接口,必须采用CGLIB动态代理

2.1 JDK动态代理

  如何目标对象实现了相关的接口。那么我们就可以通过JDK动态代理来完成代理类的动态生成。

// 调用目标对象的方法
                        //String msg = target.doSome();
                        Object res = method.invoke(target, args);    /**
     * 实现JDK动态代理:目标对象必须实现相关的接口
     *    我们就不需要显示的定义代理类
     */
    @Test
    public void test2(){
        // 1.获取目标对象
        SomeService target = new SomeServiceImpl();
        // 2.获取代理对象
        SomeService proxy = (SomeService) Proxy.newProxyInstance(
                Test02.class.getClassLoader(), // 获取类加载器
                target.getClass().getInterfaces(), // 获取目标对象实现的所有的接口
                new InvocationHandler() { // 提供一个 InvocationHandler的对象
                    /**
                     * 该方法是代理对象执行目标对象方法的回调方法
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println(method);
                        System.out.println(args);
                        System.out.println("----start------");
                        // 调用目标对象的方法
                        String msg = target.doSome();
                        System.out.println("----end------");
                        return msg.toUpperCase();
                    }
                }
        );
        // 3.通过代理对象来执行
        System.out.println("proxy.doSome() = " + proxy.doSome());
    }
}

执行结果:

image.png

2.2 CGLIB代理

  如果目标对象没有实现任何的接口。那么我们只能通过CGLIB代理的方式来实现了。同时我们需要单独的添加CGLIB的依赖。

<!-- https://mvnrepository.com/artifact/cglib/cglib-nodep -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>3.3.0</version>
</dependency>

定义目标对象:注意不实现任何接口

/**
 * 目标对象的定义
 *    改目标对象没有实现任何的接口
 */
public class SomeService {

    public String doSome(){
        System.out.println("目标对象执行了....");
        return "Hello Cglib";
    }
}

然后定义cglib的代理对象

/**
 * Cglib的代理类
 */
public class CglibProxy implements MethodInterceptor {

    // 目标对象
    private SomeService target;

    public CglibProxy(SomeService target){
        this.target = target;
    }

    /**
     * 对外提供代理对象的方法
     * @return
     */
    public SomeService createTarget(){
        // 创建cglib的增强器
        Enhancer enhancer = new Enhancer();
        // 需要指定父类
        enhancer.setSuperclass(SomeService.class);
        // 代理后的回调对象
        enhancer.setCallback(this);
        return (SomeService) enhancer.create();
    }

    /**
     * 这个就是对应的增强的方法
     * @param o
     * @param method
     * @param objects
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("------start-----");
        //String msg = target.doSome();
        Object res = method.invoke(target, objects);
        System.out.println("------end-----");
        return msg.toUpperCase();
    }
}

然后测试:

image.png

二、AOP-面向切面编程

1. AOP 概述及相关概念

1.1 AOP概述

  AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程OOP的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 日志、事务、安全检查等

1.2 AOP 术语

  在学习AOP中我们会涉及到如下的相关概念

术语说明
切面切面泛指交叉业务逻辑。比如事务处理、日志处理就可以理解为切面。常用的切面有通知与顾问。实际就是对主业务逻辑的一种增强
织入织入是指将切面代码插入到目标对象的过程。
连接点连接点指切面可以织入的位置。
切入点切入点指切面具体织入的位置。
通知(Advice)通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
顾问(Advisor)顾问是切面的另一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。 不仅指定了切入时间点,还可以指定具体的切入点

下面这个图会更加的形象些:

image.png

通知的类型:

通知类型说明
前置通知(MethodBeforeAdvice)目标方法执行之前调用
后置通知(AfterReturningAdvice)目标方法执行完成之后调用
环绕通知(MethodInterceptor)目标方法执行前后都会调用方法,且能增强结果
异常处理通知(ThrowsAdvice)目标方法出现异常调用
最终通知(final Advice)无论程序执行是否正常,该通知都会执行。类似于try…catch中finally代码块

image.png

2. 基于注解实现

2.1 基本介绍

  对于AOP这种编程思想,很多框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。然而,AspectJ也实现了AOP的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。在Spring中使用AOP开发时,一般使用AspectJ的实现方式.

image.png

image.png

相关说明:

  • 动态代理分为JDK动态代理和cglib动态代理
  • 当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理
  • JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口
  • cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类
  • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
  • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
  • AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

2.2 基本案例

首先定义对应的接口

public interface Calculator {

    int add(int i, int j);

    int sub(int i ,int j);

    int mul(int i , int j);

    int div(int i , int j);
}

然后创建该接口的实现

package com.boge.service.impl;

import com.boge.service.Calculator;
import org.springframework.stereotype.Component;

@Component
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        return i + j;
    }

    @Override
    public int sub(int i, int j) {
        return i - j;
    }

    @Override
    public int mul(int i, int j) {
        return i * j;
    }

    @Override
    public int div(int i, int j) {
        return i / j;
    }
}

创建对应的切面类

/**
 * 切面类
 */
@Aspect // 被该注解所修饰的Java类就是一个切面类
@Component
public class LogAspect {

    /**
     * 前置通知:@Before()
     */
    @Before("execution(public int com.boge.service.impl.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        System.out.println("前置通知执行了。。。。");
        String name = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("执行方法的相关信息:" + name + " 参数:" + args);
    }
}

然后做对应的测试

    @Test
    public void test1(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Calculator bean = ac.getBean(Calculator.class);
        System.out.println(bean.add(4, 7));
        System.out.println(bean.sub(4, 7));
        System.out.println(bean.mul(4, 7));
    }

结果打印:

image.png

2.3 其他通知

  • 前置通知
  • 后置通知
  • 环绕通知
  • 异常通知
  • 最终通知

相关的通知的案例:

@Aspect // 被该注解所修饰的Java类就是一个切面类
@Component
public class LogAspect {

    /**
     * 前置通知:@Before()
     */
    @Before("execution(public int com.boge.service.impl.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        System.out.println("前置通知执行了。。。。");
        String name = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("执行方法的相关信息:" + name + " 参数:" + args);
    }

    /**
     * 后置通知:可以获取目标方法的返回结果
     */
    @AfterReturning(value = "execution(* com.boge.service.impl.*.*(..))",returning = "res")
    public void afterReturningMethod(JoinPoint joinPoint,Object res){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("后置通知:" + methodName + "  返回结果:" + res);
    }

    /**
     * 环绕通知
     */
    @Around("execution(* com.boge.service.impl.*.*(..))")
    public Object  aroundMethod(ProceedingJoinPoint joinPoint){
        Object obj = null;
        try {
            System.out.println("环绕通知执行之前....");
            obj =joinPoint.proceed(); // 执行目标对象的方法
            System.out.println("环绕通知执行之后....");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知执行异常....");
        }finally {
            System.out.println("环绕通知执行....最终完成");
        }
        return obj;
    }

    /**
     * 异常通知
     */
    @AfterThrowing(value = "execution(* com.boge.service.impl.*.*(..))",throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("异常通知:" + methodName + " " + ex);
    }

    /**
     * 最终通知
     */
    @After(value = "execution(* com.boge.service.impl.*.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("最终通知执行了..." + methodName);
    }
}

2.4 切入点表达式

  切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中可以使用以下符号

image.png

语法要求:

image.png

作用:

image.png

细节介绍:

  • 用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
  • 在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
    • 例如:*.Hello匹配com.Hello,不匹配com.boge.Hello
  • 在包名的部分,使用“*…”表示包名任意、包的层次深度任意
  • 在类名的部分,类名部分整体用*号代替,表示类名任意
  • 在类名的部分,可以使用*号代替类名的一部分
    • *例如:Service匹配所有名称以Service结尾的类或接口
  • 在方法名部分,可以使用*号表示方法名任意
  • 在方法名部分,可以使用*号代替方法名的一部分
    • *例如:Operation匹配所有方法名以Operation结尾的方法
  • 在方法参数列表部分,使用(…)表示参数列表任意
  • 在方法参数列表部分,使用(int,…)表示参数列表以一个int类型的参数开头
  • 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
    • 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
  • 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
    • 例如:execution(public int *…Service.(…, int)) 正确
      例如:execution( int Service.(…, int)) 错误

如果一个切入点表达式需要被重复的复用。那么我们可以通过@Pointcut注解来定义表达式。然后我们在通知调用即可:

package com.boge.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 切面类
 */
@Aspect // 被该注解所修饰的Java类就是一个切面类
@Component
public class LogAspect2 {

    /**
     * 定义一个切入点表达式
     */
    @Pointcut("execution(public int com.boge.service.impl.CalculatorImpl.*(..))")
    public void ponitCut(){

    }

    /**
     * 前置通知:@Before()
     */
    @Before("ponitCut()")
    public void beforeMethod(JoinPoint joinPoint){
        System.out.println("前置通知执行了。。。。");
        String name = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("执行方法的相关信息:" + name + " 参数:" + args);
    }

    /**
     * 后置通知:可以获取目标方法的返回结果
     */
    @AfterReturning(value = "ponitCut()",returning = "res")
    public void afterReturningMethod(JoinPoint joinPoint,Object res){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("后置通知:" + methodName + "  返回结果:" + res);
    }

    /**
     * 环绕通知
     */
    @Around("ponitCut()")
    public Object  aroundMethod(ProceedingJoinPoint joinPoint){
        Object obj = null;
        try {
            System.out.println("环绕通知执行之前....");
            obj =joinPoint.proceed(); // 执行目标对象的方法
            System.out.println("环绕通知执行之后....");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知执行异常....");
        }finally {
            System.out.println("环绕通知执行....最终完成");
        }
        return obj;
    }

    /**
     * 异常通知
     */
    @AfterThrowing(value = "ponitCut()",throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("异常通知:" + methodName + " " + ex);
    }

    /**
     * 最终通知
     */
    @After(value = "ponitCut()")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("最终通知执行了22..." + methodName);
    }
}

3. 基于XML实现

  在Spring中AOP还有基于XML的实现方式。当然这种不是我们常用的方案。但是我们还是需要了解下

先定义对应的切面类:

/**
 * 切面类
 */
@Component
public class LogAspect3 {

    /**
     * 前置通知:@Before()
     */
    public void beforeMethod(JoinPoint joinPoint){
        System.out.println("前置通知执行了。。。。");
        String name = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("执行方法的相关信息:" + name + " 参数:" + args);
    }

    /**
     * 后置通知:可以获取目标方法的返回结果
     */
    public void afterReturningMethod(JoinPoint joinPoint,Object res){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("后置通知:" + methodName + "  返回结果:" + res);
    }

    /**
     * 环绕通知
     */
    public Object  aroundMethod(ProceedingJoinPoint joinPoint){
        Object obj = null;
        try {
            System.out.println("环绕通知执行之前....");
            obj =joinPoint.proceed(); // 执行目标对象的方法
            System.out.println("环绕通知执行之后....");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知执行异常....");
        }finally {
            System.out.println("环绕通知执行....最终完成");
        }
        return obj;
    }

    /**
     * 异常通知
     */
    public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("异常通知:" + methodName + " " + ex);
    }

    /**
     * 最终通知
     */
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("最终通知执行了..." + methodName);
    }
}

然后定义对应的配置文件

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 添加扫描路径 -->
    <context:component-scan base-package="com.boge.*"></context:component-scan>
    <!-- 基于XML的AOP实现 -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect ref="logAspect3">
            <!-- 定义切入点表达式 -->
            <aop:pointcut id="pointCut" expression="execution(* com.boge.service.impl.*.*(..))"/>
            <!-- 配置相关的通知 -->
            <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
            <aop:after-returning method="afterReturningMethod" pointcut-ref="pointCut" returning="res"></aop:after-returning>
            <aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
            <aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointCut" throwing="ex"></aop:after-throwing>
            <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

然后测试即可

image.png

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

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

相关文章

ESG成全球风潮,联想造了一个可持续的“进托邦”

不得不承认&#xff0c;全球经济前景仍然存在较大的不确定和挑战。全球经济疲软、地缘政治逆风、行业竞争加剧等多重压力让很多人都感受到了寒意。 在可预见的未来&#xff0c;我们将继续在一个复杂多变的全球环境中运营。 因此&#xff0c;著名的科技思想家凯文凯利提出&#…

PCB的层间结构、铜箔厚度选择、PCB纵横比和板厚的要求

PCB的层间结构 a) 原则上应该采用对称结构设计。对称的含义包括&#xff1a;介质层厚度及种类、铜箔厚度、图形分布类型&#xff08;大铜箔层、线路层&#xff09;的对称。 b) 考虑电压击穿问题&#xff0c;正常情况下推荐介质层厚度设计值为≥0.1mm。 铜箔厚度选择 选择铜箔…

javascript基础六:说说你对闭包的理解?闭包使用场景?

一、是什么 一个函数和对其周围状态&#xff08;lexical environment&#xff0c;词法环境&#xff09;的引用捆绑在一起&#xff08;或者说函数被引用包围&#xff09;&#xff0c;这样的组合就是闭包&#xff08;closure&#xff09; 也就是说&#xff0c;闭包让你可以在一个…

机器学习-Kmeans

K-means是一种经典的无监督学习算法&#xff0c;用于对数据进行聚类。K-means算法将数据集视为具有n个特征的n维空间&#xff0c;并尝试通过最小化簇内平方误差的总和来将数据点划分为簇。本文将介绍K-means算法的原理、实现和应用。 定义 K-means是一种无监督学习算法&#…

MongoDB基础到入门(一篇就够了)

文章目录 文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持✨前言MongoDBMongoDB体系结构MongoDB数据模型MongoDB部署安装服务器启动服务器 Shell连接(mongo命令)MongoDB可视化工具MongoDB命令基本常用⭐权限数据库⭐辅助命令⭐集合⭐…

vite的使用

私人博客 许小墨のBlog —— 菜鸡博客直通车 系列文章完整版&#xff0c;配图更多&#xff0c;CSDN博文图片需要手动上传&#xff0c;因此文章配图较少&#xff0c;看不懂的可以去菜鸡博客参考一下配图&#xff01; 系列文章目录 前端系列文章——传送门 后端系列文章——传送…

抖音seo源代码分享(前端+后端)

后端代码展示&#xff1a; $where [ [name > dvtv_s_id, oper > , value > $this->sid], [name > dvtv_dv_id, oper > , value > $dv_id], ]; $cache_model new App_Model_Douyin_MysqlVideoTempVideoStora…

挂耳式耳机推荐,这几个蓝牙耳机品牌不容错过!

办公的同时享受音乐是释放工作压力的不错途径&#xff0c;对于成为打工人日常配饰的耳机随着无线技术的不断进步也在不断开发新产品。开放式耳机不入耳佩戴有效的降低对耳朵的负担&#xff0c;在不打扰旁人的同时&#xff0c;长时间的佩戴也是对耳朵的考验&#xff0c;不知该如…

29 Vue 中 v-if/show/for 的实现

前言 这是最近的碰到的那个 和响应式相关的问题 特定的操作之后响应式对象不“响应“了 引起的一系列的文章 主要记录的是 vue 的相关实现机制 呵呵 理解本文需要 vue 的使用基础, js 的使用基础 v-if 测试用例 测试用例如下, 主要是一个 if 的使用 这里我们仅仅跟进到…

chatgpt赋能python:Python桌面应用程序:在SEO中的重要性和应用

Python桌面应用程序&#xff1a;在SEO中的重要性和应用 在当今数字时代&#xff0c;拥有一个桌面应用程序成为了非常重要的一件事情&#xff0c;特别是对于那些需要使用软件来完成日常任务的工作人员。而Python作为一种跨平台编程语言&#xff0c;可以帮助开发者编写适用于Win…

chatgpt赋能python:Python换行连接介绍

Python 换行连接介绍 如果你是一个Python初学者&#xff0c;你可能会想知道如何在程序中使用换行连接来使代码更加易读和易于理解。 换行连接是Python编程中一个很重要的概念&#xff0c;也是一个很简单的概念&#xff0c;但是许多初学者经常会犯错误。 在本文中&#xff0c;我…

( 链表) 142. 环形链表 II——【Leetcode每日一题】

❓142. 环形链表 II 难度&#xff1a;中等 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定…

【测试】软件测试基本概念、软件测试分类、测试工具

文章目录 软件测试的定义概念、目的什么是软件&#xff1f;软件与程序的区别软件测试的定义软件测试的目的软件测试的流程软件测试执行 软件测试分类按照测试阶段分类单元测试集成测试确认测试系统测试验收测试 按照测试方法分类静态测试动态测试黑盒测试白盒测试灰盒测试冒烟测…

算法第四版 Algorithms Part 1动态联通性

联通性检测用途 照片中的像素网络中的计算机社交网络中的朋友计算机芯片中的电路元器件数学集合中的元素Fortan程序中的变量复合体系中的金属位 假定已连接等价于 反身的: p与p本身是连接的.对称的: 如果p连接到q,那么q也链接到p传递的: 如果p连接到q并且q连接到r,那么p连接…

港科夜闻|香港科大近百名创新企业家回归母校庆祝大学首个「独角兽日」

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大近百名创新企业家回归母校庆祝大学首个「独角兽日」。这些香港科大毕业的创业者&#xff0c;参与创立了五间独角兽企业或上市公司&#xff0c;以及近90间初创企业&#xff0c;包括现正快速崛起、有潜力成为下一间…

【Linux】守护进程(附终端、进程组、会话的介绍)

目录 1、终端2、进程组3、会话4、守护进程 橙色 1、终端 echo $$ 可以查看当前终端进程的id 默认情况下(没有重定向)&#xff0c;每个进程的标准输入、标准输出和标准错误输出都指向控制终端、进程从标准输入读也就是读用户的键盘输入&#xff0c;进程往标准输出或标准错误…

PGXC GaussDB

PGXCA PGXC&#xff08;PostgreSQL eXtended Coordinator&#xff09;是一个基于 PostgreSQL 架构的分布式数据库解决方案。它扩展了 PostgreSQL&#xff0c;为用户提供了在多个节点上分布式存储和处理数据的能力。 PGXC 的设计目标是将 PostgreSQL 扩展为能够处理大规模数据…

2023 年互联网上 10个最佳联盟营销论坛(付费和免费)

2023 年互联网上 10个最佳联盟营销论坛&#xff08;付费和免费&#xff09; 在文章中&#xff0c;我将分享 2023 年的 10 个最佳联盟营销论坛。 您是联盟营销的新手并正在寻找向专业人士学习的地方吗&#xff1f; 您来对地方了&#xff0c;我们赞赏您加入联盟营销论坛的决定…

【C++初阶】9. string类的模拟实现

string类的完整实现放这里啦&#xff01;快来看看吧 1. string类的成员 string类的作用就是将字符串类型实现更多功能&#xff0c;运算符重载&#xff0c;增删改查等等操作&#xff0c;所以其成员就包含char*的字符串 private:char* _str;size_t _capacity;size_t _size;2. …

三对角矩阵原理及C++实现

一、三对角矩阵 1.三对角矩阵概念 2.三对角矩阵元素数量 对于给定n阶方阵M&#xff0c;若其为三对角矩阵&#xff0c;则元素个数N为&#xff1a; 若n1&#xff0c;此时方阵只有一个元素M[0][0]&#xff0c;由定义知该元素也在三对角线上。故N1。若n>1&#xff0c;由三对角…