Spring-AOP(面向切面)

Spring-AOP(面向切面)

场景模拟(计算器)

功能接口

public interface Calculator {
    int add(int i, int j);
    int minus(int i, int j);
    int multiply(int i, int j);
    int div(int i, int j);
}

实现类

public class CalculateLogImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        System.out.println("[日志]add方法开始,参数是"+ i+" " + j);
        int result = i + j;
        System.out.println("方法内部:resultAdd = " + result);
        System.out.println("[日志]add方法结束,结果是:" + result);
        return result;
    }

    @Override
    public int minus(int i, int j) {
        System.out.println("[日志]minus 方法开始,参数是"+ i+" " + j);
        int result = i - j;
        System.out.println("方法内部:resultMinus = " + result);
        System.out.println("[日志]minus 方法结束,结果是:" + result);
        return result;

    }

    @Override
    public int multiply(int i, int j) {
        System.out.println("[日志]multiply 方法开始,参数是"+ i+" " + j);
        int result = i * j;
        System.out.println("方法内部:resultMultiply = " + result);
        System.out.println("[日志]multiply 方法结束,结果是:" + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("[日志]div 方法开始,参数是"+ i+" " + j);
        int result = i / j;
        System.out.println("方法内部:resultDiv = " + result);
        System.out.println("[日志]div 方法结束,结果是:" + result);
        return result;
    }
}

在含有日志输出的实现类中可以了解到:与核心业务功能没有关系的日志输出加杂在模块中,对核心业务功能有干扰。

思路:解耦将附加功能从业务功能模块中抽取出来

代理模式

概念

二十三种设计模式中的一种,属于结构型模式,它的作用就是通过提供一个代理类,让我们在调用目标方法时,不再直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来(解耦)。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中起来便于管理。

静态代理

目标对象

接口
public interface Calculator {
    int add(int i, int j);
    int minus(int i, int j);
    int multiply(int i, int j);
    int div(int i, int j);
}
实现类
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部:resultAdd = " + result);
        return result;
    }

    @Override
    public int minus(int i, int j) {
        int result = i - j;
        System.out.println("方法内部:resultMinus = " + result);
        return result;

    }

    @Override
    public int multiply(int i, int j) {
        int result = i * j;
        System.out.println("方法内部:resultMultiply = " + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部:resultDiv = " + result);
        return result;
    }
}

代理类

public class CalculatorStaticProxy implements Calculator {
    //被代理的目标对象传递过来
    private Calculator calculator;

    public CalculatorStaticProxy(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public int add(int i, int j) {
        System.out.println("[日志]add方法开始,参数是"+ i+" " + j);
        //调用目标对象的方法实现核心业务
        int result = calculator.add(i, j);
        System.out.println("[日志]add方法结束,结果是:" + result);
        return result;
    }

    @Override
    public int minus(int i, int j) {
        System.out.println("[日志]minus 方法开始,参数是"+ i+" " + j);
        int result = calculator.minus(i, j);
        System.out.println("[日志]minus 方法结束,结果是:" + result);
        return result;
    }

    @Override
    public int multiply(int i, int j) {
        System.out.println("[日志]multiply 方法开始,参数是"+ i+" " + j);
        int result = calculator.multiply(i,j);
        System.out.println("[日志]multiply 方法结束,结果是:" + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("[日志]div 方法开始,参数是"+ i+" " + j);
        int result = calculator.div(i, j);
        System.out.println("[日志]div 方法结束,结果是:" + result);
        return result;
    }
}

测试

@Test
public void testStaticProxy(){
    Calculator calculator = new CalculatorStaticProxy(new CalculatorImpl());
    calculator.add(10, 5);
    System.out.println("-------------------");
    calculator.minus(10, 5);
    System.out.println("-------------------");
    calculator.multiply(10, 5);
    System.out.println("-------------------");
    calculator.div(10, 5);
}

/*
*   [日志]add方法开始,参数是10 5
    方法内部:resultAdd = 15
    [日志]add方法结束,结果是:15
    -------------------
    [日志]minus 方法开始,参数是10 5
    方法内部:resultMinus = 5
    [日志]minus 方法结束,结果是:5
    -------------------
    [日志]multiply 方法开始,参数是10 5
    方法内部:resultMultiply = 50
    [日志]multiply 方法结束,结果是:50
    -------------------
    [日志]div 方法开始,参数是10 5
    方法内部:resultDiv = 2
    [日志]div 方法结束,结果是:2
* */

静态代理类实现了解耦,但是由于代理类的代码都是写死的,就不具备复用的功能,在其他类需要相同功能的类需要进行代理时,也需要重新写代理类,增加了代码冗余,解决这一问题需要使用到动态代理方法。

动态代理

在这里插入图片描述

Proxy工具类

public class ProxyUtil {
    //目标对象
    private Object target;

    public ProxyUtil(Object target) {
        this.target = target;
    }

    //反回代理对象
    public Object getProxy(){
        //使用Proxy中的newProxyInstance()
        /**
         * Proxy.newProxyInstance()
         * ClassLoader:加载动态生成代理类的类加载器
         * Class<?>[] interfaces:目标对象实现的所有接口的class类型数组
         * InvocationHandler:设置代理对象实现目标对象方法的过程
         */
        //ClassLoader:加载动态生成代理类的类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        //Class<?>[] interfaces:目标对象实现的所有接口的class类型数组
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //InvocationHandler:设置代理对象实现目标对象方法的过程
        InvocationHandler invocationHandler = new InvocationHandler(){
            /**
             *
             * @param proxy :代理对象
             * @param method :需要重写目标对象的方法
             * @param args:method方法中的参数
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy,
                                 Method method,
                                 Object[] args) throws Throwable {
                System.out.println("[动态代理][日志]" + method.getName() + ", 参数:" + Arrays.toString(args));
                //调用目标的方法
                Object result = method.invoke(target, args);
                System.out.println("[动态代理][日志]" + method.getName() + ", 结果:" + result);
                return result;
            }
        };
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

测试类

@Test
public void testProxy(){
    //动态创建代理对象
    ProxyUtil proxyUtil = new ProxyUtil(new CalculatorImpl());
    Calculator proxy = (Calculator) proxyUtil.getProxy();
    proxy.add(1, 2);
    System.out.println("---------");
    proxy.minus(1, 2);
    System.out.println("---------");
    proxy.multiply(1, 2);
    System.out.println("---------");
    proxy.div(1, 2);
}



/*
*   [动态代理][日志]add, 参数:[1, 2]
    方法内部:resultAdd = 3
    [动态代理][日志]add, 结果:3
    ---------
    [动态代理][日志]minus, 参数:[1, 2]
    方法内部:resultMinus = -1
    [动态代理][日志]minus, 结果:-1
    ---------
    [动态代理][日志]multiply, 参数:[1, 2]
    方法内部:resultMultiply = 2
    [动态代理][日志]multiply, 结果:2
    ---------
    [动态代理][日志]div, 参数:[1, 2]
    方法内部:resultDiv = 0
    [动态代理][日志]div, 结果:0
* */

AOP概念及相关术语

简介

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

相关术语

横切关注点

分散在各个模块中解决同一问题,如用户验证、日志管理、事务处理、事务缓存都属于横切关注点
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个方面的增强。这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。

通知(增强)

增强,就是想要增强的功能,比如:安全、事务、日志等。每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

前置通知:在被代理的目标方法前执行
返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
异常通知:在被代理的目标方法异常结束后执行(死于非命)
后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置。

切面

封装通知方法的类。

目标

被代理的目标对象。

代理

向目标对象应用通知之后创建的代理对象

连接点

这也是一个逻辑概念,不是语法定义的。把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。也就是spring允许使用通知的地方。

切入点

定位连接点的方式,每个类的方法中都包含多个连接点,如果把连接点看作数据库中的记录,那么切入点就是查询记录的SQL语句。Spring 的AOP技术可以通过切入点定位到特定的连接点,即可以使用增强方法的位置。

切入点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。

作用

简化代码:把方法中固定位置的重复代码抽取出来,让抽取的方法更专注于自己的核心功能,提高内聚性。
代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了


基于注解的AOP

在这里插入图片描述

AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑"植入"被代理的目标,编译得到的字节码文件,所以最终效果是动态的。weaver就是植入器。Spring只是借用了AspectJ中的注解。

动态代理分类

JDK动态代理:有接口,生成接口实现类代理对象,代理对象和目标对象都实现同样的接口。
cglib动态代理:没有接口,继承目标类,生成子类代理对象

准备工作

引入相关的依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.9</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>6.0.9</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.0.9</version>
</dependency>
创建目标资源

接口

public interface Calculator {
    int add(int i, int j);
    int minus(int i, int j);
    int multiply(int i, int j);
    int div(int i, int j);
}

实现类

public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部:resultAdd = " + result);
        return result;
    }

    @Override
    public int minus(int i, int j) {
        int result = i - j;
        System.out.println("方法内部:resultMinus = " + result);
        return result;

    }

    @Override
    public int multiply(int i, int j) {
        int result = i * j;
        System.out.println("方法内部:resultMultiply = " + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部:resultDiv = " + result);
        return result;
    }
}
配置spring配置文件
<?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/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
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--开启组件扫描-->
    <context:component-scan base-package="com.louis.annotation_aop"></context:component-scan>
<!--开启aspectJ自动代理,为目标对象生成代理-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
创建切面类
切入点表达式

语法细节

用*号代替"权限修饰符"和"返回值部分"表示"权限修饰符"和"返回值"不限

包名的部分

①一个"*“号只能代表包的层次结构中的一层,表示这一层是任意的。如:*.com匹配hello.com但不匹配hello.louis.com
②使用”*…"表示任意包名、包的层次深度任意

类名的部分

①类名整体使用*代替,表示类名任意
②可以使用*代替类名的一部分。如:*xxx表示匹配所有名称以xxx结尾的类或接口

方法名的部分

①可以使用*代替,表示方法名任意
②可以使用*代替方法名的一部分。如:*xxx表示匹配所有名称以xxx结尾的方法

方法参数列表部分

①可以使用(…)表示任意参数列表
②可以使用(int,…)表示参数列表以一个int类型的参数开头
③基本数据类型和对应的包装类型是不一样的(切入点中使用int和实际方法中使用Integer是不匹配的)

方法返回值部分

如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符。如:excution(public int…Service.(…,int))

切面类(通知类型)
@Aspect
@Component //表示在spring的ioc容器中进行管理
public class LogAspect {
    //设置切入点和通知类型

    //通知类型:
    // 前置:@Before(value="通过切入点表达式配置切入点")
    //切入点表达式:execution(访问修饰符 增强方法返回类型 方法所在类的全路径.方法名(参数列表))
//    @Before("execution(* com.louis.annotation_aop.impl.LogAspect.*(..))")
    @Before("execution(public int com.louis.annotation_aop.impl.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知,增强的方法名称" + name + " , 参数" + Arrays.toString(args));
    }
    // 返回:@AfterReturning,返回通知与后置通知有很大的区别,后置通知在返回通知之后执行,返回通知能够得到目标方法的返回值,使用属性returning
    //它的参数可以随便命名,但是切面方法的参数必须和上面的一致
    @AfterReturning(value = "execution(* com.louis.annotation_aop.impl.CalculatorImpl.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println("返回通知, 增强方法名称" + name + " , 返回结果" + result);
    }
    // 异常:@AfterThrowing:目标方法出现异常,这个通知会执行,并且能够获得目标方法的异常信息
    @AfterThrowing(value = "execution(* com.louis.annotation_aop.impl.CalculatorImpl.*(..))", throwing = "Exception")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable Exception){
        String name = joinPoint.getSignature().getName();
        System.out.println("异常通知, 增强方法名称" + name + " , 返回结果" + Exception);
    }

    // 后置:@After()
    @After("execution(public int com.louis.annotation_aop.impl.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println("后置通知, 增强方法名称" + name);
    }
    // 环绕:@Around()
    @Around("execution(public int com.louis.annotation_aop.impl.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String argsString = Arrays.toString(args);
        Object result = null;
        try {
            System.out.println("环绕通知, 目标方法之前执行");
            //调用目标方法
             result = joinPoint.proceed();

            System.out.println("环绕通知, 目标方法返回值之后执行");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知, 目标方法出现异常执行");
        } finally {
            System.out.println("环绕通知, 目标方法执行完毕");
        }
        return result;
    }
}
测试
@Test
public void testAOPAdd(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    Calculator calculator = context.getBean(Calculator.class);
    calculator.add(1,0);
}

/*
环绕通知, 目标方法之前执行
前置通知,增强的方法名称add , 参数[1, 0]
方法内部:resultAdd = 1
返回通知, 增强方法名称add , 返回结果1
后置通知, 增强方法名称add
环绕通知, 目标方法返回值之后执行
环绕通知, 目标方法执行完毕
* */

@Test
public void testDiv(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    Calculator calculator = context.getBean(Calculator.class);
    calculator.div(1,0);
}

/*
环绕通知, 目标方法之前执行
前置通知,增强的方法名称div , 参数[1, 0]
异常通知, 增强方法名称div , 返回结果java.lang.ArithmeticException: / by zero
后置通知, 增强方法名称div
环绕通知, 目标方法出现异常执行
环绕通知, 目标方法执行完毕
* */

基于注解的AOP

重用切入点和切面优先级

重用切入点
//重用切入点表达式,定义一个方法,之后在需要切入点的时候直接调用这个方法
/*
* 在同一个类下,可以直接使用:@Before("pointcut()")
* 在不同的类中,需要加入类的路径:  @Before("com.louis.annotation_aop.impl.LogAspect.pointcut()")
* */
@Pointcut(value = "execution(public int com.louis.annotation_aop.impl.CalculatorImpl.*(..))")
public void pointcut(){

}
切面优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序
优先级高的切面在外面,优先级低的切面在里面。
使用@Order注解可以控制切面的优先级
@Order(较小的数):优先级高
@Order(较大的数):优先级低

切面类

@Component //表示在spring的ioc容器中进行管理
public class LogAspect {
    //前置通知
    public void beforeMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知,增强的方法名称" + name + " , 参数" + Arrays.toString(args));
    }
    // 返回通知
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println("返回通知, 增强方法名称" + name + " , 返回结果" + result);
    }
    // 异常通知
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable Exception){
        String name = joinPoint.getSignature().getName();
        System.out.println("异常通知, 增强方法名称" + name + " , 返回结果" + Exception);
    }

    // 后置通知
    public void afterMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println("后置通知, 增强方法名称" + name);
    }
    // 环绕通知
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String argsString = Arrays.toString(args);
        Object result = null;
        try {
            System.out.println("环绕通知, 目标方法之前执行");
            //调用目标方法
             result = joinPoint.proceed();

            System.out.println("环绕通知, 目标方法返回值之后执行");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知, 目标方法出现异常执行");
        } finally {
            System.out.println("环绕通知, 目标方法执行完毕");
        }
        return result;
    }


    //重用切入点表达式,定义一个方法,之后在需要切入点的时候直接调用这个方法
    /*
    * 在同一个类下,可以直接使用:@Before("pointcut()")
    * 在不同的类中,需要加入类的路径:  @Before("com.louis.annotation_aop.impl.LogAspect.pointcut()")
    * */
    @Pointcut(value = "execution(public int com.louis.xml_aop.impl.CalculatorImpl.*(..))")
    public void pointcut(){

    }
}

配置文件beanaop.xml

<?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/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
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--开启组件扫描-->
    <context:component-scan base-package="com.louis.xml_aop"></context:component-scan>
    <!--配置aop五种通知类型-->
    <aop:config>
        <!--配置切面类-->
        <aop:aspect ref="logAspect">
            <!--配置切入点-->
            <aop:pointcut id="pointcut" expression="execution(* com.louis.xml_aop.impl.CalculatorImpl.*(..))"/>
            <!--配置五种通知类型-->
            <!--前置通知-->
            <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
            <!--后置通知-->
            <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
            <!--返回通知-->
            <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointcut"></aop:after-returning>
            <!--异常通知-->
            <aop:after-throwing method="afterThrowingMethod" throwing="Exception" pointcut-ref="pointcut"></aop:after-throwing>
            <!--环绕通知-->
            <aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

测试

@Test
public void testAOPXMLAdd(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beanaop.xml");
    Calculator calculator = context.getBean(Calculator.class);
    calculator.add(1,0);
}
/*
前置通知,增强的方法名称add , 参数[1, 0]
环绕通知, 目标方法之前执行
方法内部:resultAdd = 1
环绕通知, 目标方法返回值之后执行
环绕通知, 目标方法执行完毕
返回通知, 增强方法名称add , 返回结果1
后置通知, 增强方法名称add
* */

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ELK-日志服务【kafka-配置使用】

kafka-01 10.0.0.21 kafka-02 10.0.0.22 kafka-03 10.0.0.23 【1】安装zk集群、配置 [rootes-01 ~]# yum -y install java maven [rootes-01 ~]# tar xf apache-zookeeper-3.5.9-bin.tar.gz -C /opt/[rootes-01 ~]# cd /opt/apache-zookeeper-3.5.9-bin/conf/ [rootes-…

2023年四川大学生程序设计竞赛-A.旷野之息

题目描述 Cuber QQ 终于打败盖农救回了塞尔达公主&#xff0c;海拉鲁大地也开始灾后重建。 在统计学中&#xff0c;幂律表示的是两个量之间的函数关系&#xff0c;其中一个量的相对变化会导致另一个量的相应幂次比例的变化&#xff0c;且与初值无关&#xff1a;表现为一个量是…

【Linux后端服务器开发】UDP协议

目录 一、端口号 二、UDP报头格式 三、UDP的特点 四、UDP协议实现网络聊天群 一、端口号 端口号port标识了一个主机上进行通信的不同的应用程序。 0 ~ 1023&#xff1a;系统端口号&#xff0c;HTTP、FTP、SSH等这些广为使用的应用层协议&#xff0c;它们的端口号都是固定…

TypeScript 1 - 小记

文章目录 关于 TypeScript 关于 TypeScript TypeScript is a superset of JavaScript that compiles to clean JavaScript output. 官网&#xff1a;https://www.typescriptlang.orggithub : https://github.com/microsoft/TypeScriptplayground : https://www.typescriptlan…

AWS MSK集群认证和加密传输的属性与配置

通常&#xff0c;身份认证和加密传输是两项不相关的安全配置&#xff0c;在Kafka/MSK上&#xff0c;身份认证和加密传输是有一些耦合关系的&#xff0c;重点是&#xff1a;对于MSK来说&#xff0c;当启用IAM, SASL/SCRAM以及TLS三种认证方式时&#xff0c;TLS加密传输是必须的&…

RabbitMq(一)

一、基本概念、常见工作模式以及简单使用 MQ全称Message Queue (消息队列)&#xff0c;是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。 小结 MQ消息队列&#xff0c;存储消息的中间件分布式系统通信两种方式:直接远程调用和借助第三方完成间接通信发…

Layui基本功能(增删改查)

话不多说&#xff0c;根据我前面的博客我们直接进行操作。记住以下的文件放置&#xff0c;防止操作出不来. 这是我们要完成的界面及功能 后台功能实现 数据查看 我们在userDao方法里面进行增删改查的方法我们在userAction进行方法的编写R工具类的介绍 查询 userDao方法 因为我…

51单片机学习--矩阵键盘、电子密码锁

矩阵键盘的模块原理图&#xff1a; 功能&#xff1a;按下矩阵按键&#xff0c;在LCD上显示对应的数值。 采用模块化编程&#xff0c;在此只给出MatrixKey.c 和 main.c 两部分代码 #include <REGX52.H> #include "Delay.h"/*矩阵键盘读取键码按下不放&#xf…

typescript manual

这里写目录标题 throw new Error在浏览器中调试Json定义类型定义数组 functionNamed functionanonymous function Axios经典片段 错误及解决ref valuebecause it is a constantAPI 和 客户端定义的数据结构不一样ServerClient throw new Error throw new Error(“Get data err…

存css实现动态时钟背景

代码实现 <!DOCTYPE html> <html lang"en"> <head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"><title>Title</title><meta name"referrer" content"no-referrer…

掌握无人机遥感数据预处理的全链条理论与实践流程、典型农林植被性状的估算理论与实践方法、利用MATLAB进行编程实践(脚本与GUI开发)以及期刊论文插图制作等

目录 专题一 认识主被动无人机遥感数据 专题二 预处理无人机遥感数据 专题三 定量估算农林植被关键性状 专题四 期刊论文插图精细制作与Appdesigner应用开发 近地面无人机植被定量遥感与生理参数反演 更多推荐 遥感技术作为一种空间大数据手段&#xff0c;能够从多时、多…

Linux —— 进程状态

目录 一&#xff0c;进程状态分类 二&#xff0c;僵尸进程 三&#xff0c;孤儿进程 一&#xff0c;进程状态分类 进程状态反应进程执行过程中的变化&#xff0c;状态会随外界条件的变化而转换&#xff1b; 三态模型&#xff0c;运行态、就绪态、阻塞态&#xff1b;五态模型…

PageObject+Python+Appium

目录 前言&#xff1a; 简介 功能 常用目录 配置 实例 - 第一次启动 app 实例 - 登录 代码入口实例 结果展示 前言&#xff1a; Page Object模式是一种常用的设计模式&#xff0c;用于组织和管理自动化测试脚本中的页面对象。它将页面的元素和操作封装在一个独立的类…

skbuff.h在哪

今天看来下底层socket的实现&#xff0c;发现很多人都说有个skbuff结构&#xff0c;我就想着去找下这个&#xff0c; cat /include/linux/skbuff.h 结果找不到&#xff0c;查了下&#xff0c;需要下载内核源码 //Ubuntusudo apt install linux-headers-genericdpkg -S skbuff.…

浏览器显示ERR_NETWORK_ACCESS_DENIED,安全设置或防火墙可能正在阻止连接,无法上网

环境: Win10 专业版 HP台式机 问题描述: 浏览器显示ERR_NETWORK_ACCESS_DENIED,安全设置或防火墙可能正在阻止连接,无法上网 1.无线连接状态正常 打不开网站 2.可以ping通百度DNS解析正常 3.防火墙已关闭 这样的错误可能由于多种原因而发生 原因分析 1.防火墙/防…

Unity游戏源码分享-Third Person Controller - Shooter Template v1.3.1

Unity游戏源码分享-Third Person Controller - Shooter Template v1.3.1 功能非常齐全 AI格斗 2.5D 完整工程地址&#xff1a;https://download.csdn.net/download/Highning0007/88057824

SpringCloud学习路线(3)—— Eureka注册中心

一、导引 服务调用出现的问题 服务调用采取的请求地址是静态的&#xff0c;当我们使用服务集群时&#xff0c;很容易造成只能调用固定的微服务上的接口。多个提供者&#xff0c;消费者的使用对象无法确定消费者无法得知提供者的状态 二、Eureka注册中心 &#xff08;一&…

一个小技巧,分分钟搞定新零售!

新零售模式的兴起带来了线上线下销售渠道的整合&#xff0c;而自动售货机作为新零售模式的一种重要形式&#xff0c;提供了便捷的自助购物体验。 自动售货机作为新零售模式的一种典型应用&#xff0c;以其便利性、快捷性和24小时无人值守的特点&#xff0c;深受消费者和商家的青…

【C++】C++入门必备知识详细讲解

C入门必备知识 一、命名空间1. namespace2. namespace 的使用场景 二、了解 C 中的输入和输出三、缺省参数四、函数重载1. 函数重载的概念2. C支持函数重载的原理 五、引用1. 引用的概念2. 引用特性3. 常引用4. 引用的使用场景&#xff08;1&#xff09;做参数&#xff08;传引…

ES系列--打分机制

一、文档打分机制 当你通过关键字搜索相关文档时&#xff0c;可能会出现多个文档&#xff0c;这些文档的顺序是通过一个max_score属性的大小从高到低顺序展现出来的&#xff0c;max_score属性就是我们所说的评分。而这个评分是通过一个文档打分机制计算出来的。 二、打分原理 …