一篇文章讲清楚什么是Spring AOP

news2024/11/16 1:47:58

目录

1、什么是代理?

1.1静态代理

1.2动态代理

2、什么是AOP?

3、AOP术语名词介绍

4、Spring AOP框架使用教程

5、Spring AOP框架细节讲解


1、什么是代理?

在讲解AOP之前,我们要先了解下什么是代理。

代理是二十四种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类(中介类),让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类(中介类)间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。简单来说,代理就是替我们去完成一些非核心的功能,降低代码耦合度

举个例子:

1、广告商找明星代言广告需要经过经纪人,经纪人就是代理

2、房产中介是买卖双方的代理

代理在开发中实现的方式具体有两种:静态代理,动态代理

1.1静态代理

静态代理是在编译时由程序员手动创建或使用工具生成代理类。代理类在编译时就已经确定,代理类(中介类)和目标类(被代理的类)都实现相同的接口。

这里的接口就是定义目标对象和代理对象的共同方法。

/**
 *       + - * / 运算的标准接口!
 */
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);
    
}

下面是普通实现类



/**
 * 实现计算接口,单纯添加 + - * / 实现! 掺杂其他功能!
 */
public class CalculatorPureImpl implements Calculator {
    
    @Override
    public int add(int i, int j) {
    
        int result = i + j;
    
        return result;
    }
    
    @Override
    public int sub(int i, int j) {
    
        int result = i - j;
    
        return result;
    }
    
    @Override
    public int mul(int i, int j) {
    
        int result = i * j;
    
        return result;
    }
    
    @Override
    public int div(int i, int j) {
    
        int result = i / j;
    
        return result;
    }
}

下面是静态代理

public class CalculatorStaticProxy implements Calculator {
    
    // 将被代理的目标对象声明为成员变量
    private Calculator target;
    
    public CalculatorStaticProxy(Calculator target) {
        this.target = target;
    }
    
    @Override
    public int add(int i, int j) {
    
        // 附加功能由代理类中的代理方法来实现
        System.out.println("参数是:" + i + "," + j);
    
        // 通过目标对象来实现核心业务逻辑
        int addResult = target.add(i, j);
    
        System.out.println("方法内部 result = " + result);
    
        return addResult;
    }
    ……

静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。

1.2动态代理

将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。

动态代理技术分为两类

DK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口!他会根据目标类的接口动态生成一个代理对象!代理对象和目标对象有相同的接口!(找兄弟)

cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口!(找爹爹)

代理工程:基于jdk代理技术,生成代理对象

public class ProxyFactory {

    private Object target;

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

    public Object getProxy(){

        /**
         * newProxyInstance():创建一个代理实例
         * 其中有三个参数:
         * 1、classLoader:加载动态生成的代理类的类加载器
         * 2、interfaces:目标对象实现的所有接口的class对象所组成的数组
         * 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法
         */
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /**
                 * proxy:代理对象
                 * method:代理对象需要实现的方法,即其中需要重写的方法
                 * args:method所对应方法的参数
                 */
                Object result = null;
                try {
                    System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
                    result = method.invoke(target, args);
                    System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());
                } finally {
                    System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");
                }
                return result;
            }
        };

        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

测试代码:

@Test
public void testDynamicProxy(){
    ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());
    Calculator proxy = (Calculator) factory.getProxy();
    proxy.div(1,0);
    //proxy.div(1,1);
}

2、什么是AOP?

AOP:Aspect Oriented Programming面向切面编程

AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。面向对象编程引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过面向对象编程允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在面向对象设计中,它导致了大量代码的重复,而不利于各个模块的重用

AOP面向切面技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用AOP,可以在不修改原来代码的基础上添加新功能。

AOP主要应用场景

  • 日志记录:在系统中记录日志是非常重要的,可以使用AOP来实现日志记录的功能,可以在方法执行前、执行后或异常抛出时记录日志。
  • 事务处理:在数据库操作中使用事务可以保证数据的一致性,可以使用AOP来实现事务处理的功能,可以在方法开始前开启事务,在方法执行完毕后提交或回滚事务。
  • 安全控制:在系统中包含某些需要安全控制的操作,如登录、修改密码、授权等,可以使用AOP来实现安全控制的功能。可以在方法执行前进行权限判断,如果用户没有权限,则抛出异常或转向到错误页面,以防止未经授权的访问。
  • 性能监控:在系统运行过程中,有时需要对某些方法的性能进行监控,以找到系统的瓶颈并进行优化。可以使用AOP来实现性能监控的功能,可以在方法执行前记录时间戳,在方法执行完毕后计算方法执行时间并输出到日志中。
  • 异常处理:系统中可能出现各种异常情况,如空指针异常、数据库连接异常等,可以使用AOP来实现异常处理的功能,在方法执行过程中,如果出现异常,则进行异常处理(如记录日志、发送邮件等)。
  • 动态代理:AOP的实现方式之一是通过动态代理,可以代理某个类的所有方法,用于实现各种功能

3、AOP术语名词介绍

1-横切关注点:AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点是那些影响应用程序多个模块的功能或行为,如日志记录、安全性、事务管理等。传统的面向对象编程难以有效地处理这些横切关注点,导致代码的重复和分散。通过面向切面编程(AOP),可以将横切关注点模块化,减少代码冗余,提高代码的清晰度和可维护性。Spring AOP 提供了一种强大而灵活的方式来实现横切关注点,使得应用程序的开发和维护更加高效

2-通知(增强):每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

  • 前置通知:在被代理的目标方法前执行
  • 返回通知:在被代理的目标方法成功结束后执行
  • 异常通知:在被代理的目标方法异常结束后执行
  • 后置通知:在被代理的目标方法最终结束后执行
  • 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

3-连接点 joinpoint::程序执行过程中可以插入切面的点,例如方法调用、异常抛出等。
4-切入点 pointcut:定位连接点的方式,或者可以理解成被选中的连接点!是一个表达式,比如execution(* com.spring.service.impl..(..))。符合条件的每个方法都是一个具体的连接点。

5-切面 aspect:横切关注点的模块化实现,可以理解为切入点和通知的结合,是一个类

6-目标 target:被代理的目标对象。(可以理解为明星)

7-代理 proxy:向目标对象通知(增强)之后创建的代理对象

8-织入 weave:指把通知应用到目标上,生成代理对象的过程。可以在编译期织入,也可以在运行期织入,Spring采用后者。

4、Spring AOP框架使用教程

AOP一种区别于面向对象的编程思维,用来完善和解决面向对象的非核心代码冗余和不方便统一维护问题!

代理技术(动态代理|静态代理)是实现AOP思维编程的具体技术,但是自己使用动态代理实现代码比较繁琐!

Spring AOP框架,基于AOP编程思维,封装动态代理技术,简化动态代理技术实现的框架!SpringAOP内部帮助我们实现动态代理,我们只需写少量的配置,指定生效范围即可,即可完成面向切面思维编程的实现!

Spring AOP底层技术组成

动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(找兄弟)

cglib:通过继承被代理的目标类(找爹爹)实现代理,所以不需要目标类实现接口

AspectJ:早期的AOP实现的框架,SpringAOP借用了AspectJ中的AOP注解。

初步实现Sping AOP框架

第一步:加入依赖

<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>6.0.6</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.0.6</version>
</dependency>

第二步:准备接口

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);
    
}

第三步:简单实现接口


/**
 * 实现计算接口,单纯添加 + - * / 实现! 掺杂其他功能!
 */
@Component
public class CalculatorPureImpl implements Calculator {
    
    @Override
    public int add(int i, int j) {
    
        int result = i + j;
    
        return result;
    }
    
    @Override
    public int sub(int i, int j) {
    
        int result = i - j;
    
        return result;
    }
    
    @Override
    public int mul(int i, int j) {
    
        int result = i * j;
    
        return result;
    }
    
    @Override
    public int div(int i, int j) {
    
        int result = i / j;
    
        return result;
    }
}

第四步:声明切面类


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

// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LogAspect {
        
    // @Before注解:声明当前方法是前置通知方法
    // value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上
    @Before(value = "execution(public int com.proxy.CalculatorPureImpl.add(int,int))")
    public void printLogBeforeCore() {
        System.out.println("[AOP前置通知] 方法开始了");
    }
    
    @AfterReturning(value = "execution(public int com.proxy.CalculatorPureImpl.add(int,int))")
    public void printLogAfterSuccess() {
        System.out.println("[AOP返回通知] 方法成功返回了");
    }
    
    @AfterThrowing(value = "execution(public int com.proxy.CalculatorPureImpl.add(int,int))")
    public void printLogAfterException() {
        System.out.println("[AOP异常通知] 方法抛异常了");
    }
    
    @After(value = "execution(public int com.proxy.CalculatorPureImpl.add(int,int))")
    public void printLogFinallyEnd() {
        System.out.println("[AOP后置通知] 方法最终结束了");
    }
    
}

第五步:开启aspectj注解支持

可以用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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 进行包扫描-->
    <context:component-scan base-package="com.xxx" />
    <!-- 开启aspectj框架注解支持-->
    <aop:aspectj-autoproxy />
</beans>

或者配置类方式

@Configuration
@ComponentScan(basePackages = "com.xxx")
//作用等于 <aop:aspectj-autoproxy /> 配置类上开启 Aspectj注解支持!
@EnableAspectJAutoProxy
public class MyConfig {
}

第六步:测试

//@SpringJUnitConfig(locations = "classpath:spring-aop.xml")
@SpringJUnitConfig(value = {MyConfig.class})
public class AopTest {

    @Autowired
    private Calculator calculator;

    @Test
    public void testCalculator(){
        calculator.add(1,1);
    }
}

成功输出


[AOP前置通知] 方法开始了
[AOP返回通知] 方法成功返回了
[AOP后置通知] 方法最终结束了

5、Spring AOP框架细节讲解

上面的案例中,有一些细节需要我们注意的

第一种:获取通知细节

JointPoint接口

需要获取方法签名、传入的实参等信息时,可以在通知方法声明JoinPoint类型的形参。

要点1:JoinPoint 接口通过 getSignature() 方法获取目标方法的签名

要点2:通过目标方法签名对象获取方法名

要点3:通过 JoinPoint 对象获取外界调用目标方法时传入的实参列表组成的数组、

// @Before注解标记前置通知方法
// value属性:切入点表达式,告诉Spring当前通知方法要套用到哪个目标方法上
// 在前置通知方法形参位置声明一个JoinPoint类型的参数,Spring就会将这个对象传入
// 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
@Before(value = "execution(public int com.xxx.aop.api.Calculator.add(int,int))")
public void printLogBeforeCore(JoinPoint joinPoint) {
    
    // 1.通过JoinPoint对象获取目标方法签名对象
    // 方法的签名:一个方法的全部声明信息
    Signature signature = joinPoint.getSignature();
    
    // 2.通过方法的签名对象获取目标方法的详细信息
    String methodName = signature.getName();
    System.out.println("methodName = " + methodName);
    
    int modifiers = signature.getModifiers();
    System.out.println("modifiers = " + modifiers);
    
    String declaringTypeName = signature.getDeclaringTypeName();
    System.out.println("declaringTypeName = " + declaringTypeName);
    
    // 3.通过JoinPoint对象获取外界调用目标方法时传入的实参列表
    Object[] args = joinPoint.getArgs();
    
    // 4.由于数组直接打印看不到具体数据,所以转换为List集合
    List<Object> argList = Arrays.asList(args);
    
    System.out.println("[AOP前置通知] " + methodName + "方法开始了,参数列表:" + argList);
}

方法返回值:在返回通知中,通过@AfterReturning注解的returning属性获取目标方法的返回值!

// @AfterReturning注解标记返回通知方法
// 在返回通知中获取目标方法返回值分两步:
// 第一步:在@AfterReturning注解中通过returning属性设置一个名称
// 第二步:使用returning属性设置的名称在通知方法中声明一个对应的形参
@AfterReturning(
        value = "execution(public int com.xxx.aop.api.Calculator.add(int,int))",
        returning = "targetMethodReturnValue"
)
public void printLogAfterCoreSuccess(JoinPoint joinPoint, Object targetMethodReturnValue) {
    
    String methodName = joinPoint.getSignature().getName();
    
    System.out.println("[AOP返回通知] "+methodName+"方法成功结束了,返回值是:" + targetMethodReturnValue);
}

异常对象捕捉:在异常通知中,通过@AfterThrowing注解的throwing属性获取目标方法抛出的异常对象

// @AfterThrowing注解标记异常通知方法
// 在异常通知中获取目标方法抛出的异常分两步:
// 第一步:在@AfterThrowing注解中声明一个throwing属性设定形参名称
// 第二步:使用throwing属性指定的名称在通知方法声明形参,Spring会将目标方法抛出的异常对象从这里传给我们
@AfterThrowing(
        value = "execution(public int com.xxx.aop.api.Calculator.add(int,int))",
        throwing = "targetMethodException"
)
public void printLogAfterCoreException(JoinPoint joinPoint, Throwable targetMethodException) {
    
    String methodName = joinPoint.getSignature().getName();
    
    System.out.println("[AOP异常通知] "+methodName+"方法抛异常了,异常类型是:" + targetMethodException.getClass().getName());
}

第二种:切点表达式语法

AOP切点表达式(Pointcut Expression)是一种用于指定切点的语言,它可以通过定义匹配规则,来选择需要被切入的目标对象。

execution(public int swift.service.impl.CalculatorPureImpl.add(int,int))

第一位:execution( ) 固定开头

第二位:方法访问修饰符

第三位:方法返回值

第四位:指定包的地址

第五位:指定类名称

第六位:指定方法名称

第七位:方法参数

第三种:提取切点表达式

​
 // @Before注解:声明当前方法是前置通知方法
// value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上
@Before(value = "execution(public int com.proxy.CalculatorPureImpl.add(int,int))")
public void printLogBeforeCore() {
    System.out.println("[AOP前置通知] 方法开始了");
}

@AfterReturning(value = "execution(public int com.proxy.CalculatorPureImpl.add(int,int))")
public void printLogAfterSuccess() {
    System.out.println("[AOP返回通知] 方法成功返回了");
}

@AfterThrowing(value = "execution(public int com.proxy.CalculatorPureImpl.add(int,int))")
public void printLogAfterException() {
    System.out.println("[AOP异常通知] 方法抛异常了");
}

@After(value = "execution(public int com.proxy.CalculatorPureImpl.add(int,int))")
public void printLogFinallyEnd() {
    System.out.println("[AOP后置通知] 方法最终结束了");
}

​

上面案例,是我们之前编写切点表达式的方式,发现, 所有增强方法的切点表达式相同!

出现了冗余,如果需要切换也不方便统一维护!

我们可以将切点提取,在增强上进行引用即可

同一类内部引用

提取

// 切入点表达式重用
@Pointcut("execution(public int com.aop.api.Calculator.add(int,int)))")
public void declarPointCut() {}

注意:提取切点注解使用@Pointcut(切点表达式) , 需要添加到一个无参数无返回值方法上即可!

引用:

@Before(value = "declarPointCut()")
public void printLogBeforeCoreOperation(JoinPoint joinPoint) {

不同类中引用:不同类在引用切点,只需要添加类的全限定符+方法名即可

@Before(value = "com.spring.aop.aspect.LogAspect.declarPointCut()")
public Object roundAdvice(ProceedingJoinPoint joinPoint) {

切点统一管理

建议:将切点表达式统一存储到一个类中进行集中管理和维护!

@Component
public class XXXPointCut {
    
    @Pointcut(value = "execution(public int *..Calculator.sub(int,int))")
    public void xxxGlobalPointCut(){}
    
    @Pointcut(value = "execution(public int *..Calculator.add(int,int))")
    public void xxxSecondPointCut(){}
    
    @Pointcut(value = "execution(* *..*Service.*(..))")
    public void transactionPointCut(){}
}

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

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

相关文章

Flutter集成Firebase框架

本文档的插件版本 flutter&#xff1a;3.19.4dart版本&#xff1a;3.3.2 firebase_core&#xff1a;2.30.0 firebase&#xff1a;13.7.3flutterfire&#xff1a;1.0.0 前言 Flutter集成Firebase框架要完成以下内容 在Firebase网页端创建一个项目在终端全局安装Firebase工具&…

中断管理笔记

1、异常与中断的基本概念 异常是指任何打断处理器正常执行&#xff0c;并且迫使处理器进入一个由有特权的特殊指令执行的事件。 异常可以分为两类&#xff1a;同步异常和异步异常。 由内部事件&#xff08;像处理器指令运行产生的事件&#xff09;引起的异常称为同步异常。异…

雅思7分相当于六级多少?雅思考试怎么备考才能到7分?

雅思7分相当于六级多少&#xff1f;雅思考试怎么备考才能到7分? 六级500分也不能和雅思7分相提并论&#xff0c;毕竟两者压根不在一个层级。楼主两月自学雅思获得8分&#xff0c;系统总结了雅思速成的提分秘籍&#xff0c;跟着我的方法走&#xff0c;两月屠鸭7分问题不大。1.6…

你做的SEO为什么效果不够好?

SEO&#xff08;Search Engine Optimization&#xff09;即搜索引擎优化&#xff0c;指在不同的搜索引擎中&#xff0c;按照相应的规则与机制提高网站在特定的搜索引擎中的自然排名。作为对产品和服务进行数字宣传营销的重要手段&#xff0c;SEO已然成为各企业提高网站曝光和流…

AI生产力工具暑期迎来大爆发 极光数据:夸克新增用户规模领先

9月3日&#xff0c;极光旗下月狐数据发布《AI生产力工具暑期发展报告》。数据显示&#xff0c;AI生产力工具在用户侧呈现高速增长态势&#xff0c;总体月活跃用户数量达1.7亿。其中&#xff0c;夸克APP实现暑期新增用户数量行业第一&#xff0c;凭借大模型、数据、场景等优势&a…

Java中实现写Word文档

背景&#xff1a;通过java代码&#xff0c;往docx文档中写入标题和段落。 依赖的maven包&#xff1a; <dependency> <groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.2</version> </depend…

【技术前沿】智能反向寻车解决方案:提升停车场用户体验与运营效率

亲爱的技术员及停车场管理者们&#xff0c;您是否曾遇到过车主在庞大的停车场中迷失方向&#xff0c;耗费大量时间寻找爱车的困境&#xff1f;这不仅影响了车主的停车体验&#xff0c;也无形中增加了停车场的管理难度和运营成本。本文专为解决这一痛点而生&#xff0c;介绍最新…

油猴插件编写测试工具

参考&#xff1a;如何使用油猴插件提高测试工作效率 一、背景 在酷家乐设计工具测试中&#xff0c;总会有许多高频且较繁琐的工作&#xff0c;比如&#xff1a; 查询插件版本&#xff1a;需要打开Chrome控制台&#xff0c;输入好几个命令然后过滤出版本信息。 查询模型商品&…

调用Claude 3.5 API的实战代码

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于大模型算法的研究与应用。曾担任百度千帆大模型比赛、BPAA算法大赛评委,编写微软OpenAI考试认证指导手册。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。授权多项发明专利。对机器学…

CSD三层架构

Web开发三层架构 controller&#xff1a;控制层&#xff0c;接收前端发送的请求&#xff0c;处理请求并响应数据service&#xff1a;业务逻辑层dao&#xff1a;数据访问层&#xff0c;负责数据访问操作 分层解耦 内聚&#xff1a;软件中各个功能模块内部的功能联系 耦合&…

拒绝霸王条约,苹果用户用不了微信了?

相信关注科技圈的同学都知道了&#xff0c;一年一度的科技春晚——苹果新机发布会就要来了&#xff0c;将于北京时间 9 月 10 日凌晨一点召开。带来全新的 iPhone16 系列。 在这之前咱们也有 iPhone16 的爆料&#xff0c;感兴趣的同学可以看下。 iPhone16外观配置敲定&#xf…

Android Gradle 插件的说明

1、前天运行好好的项目&#xff0c;今天运行就报错&#xff1a; 这个意思是Gradle版本低了 这个意思是Gradle plugin(8.5.1) 最高的compileSdk 34&#xff0c;用了35&#xff0c;就不对&#xff0c;因为一开始我们安装的就是35的版本&#xff0c;我们可以安装下34&#xff0c;…

编译原理项目——C++实现C语言编译器输出为8086级汇编(代码/报告材料)

完整的材料 代码见文章末尾 以下为核心内容和部分结果 项目介绍 一个小型的c语言编译器&#xff0c;实现的功能如下&#xff1a; 可以定义多个变量&#xff0c;并且能初始化。可以支持基本的加减乘除运算。可以支持带括号的多个变量的四则混合运算。可以支持单行注释和多行注…

指数分布的两种形式

指数分布是连续概率分布的一种&#xff0c;常用于描述等待时间、寿命等随机变量的分布。 1. 标准形式的指数分布 标准形式的指数分布的概率密度函数&#xff08;PDF&#xff09;为&#xff1a; f ( x ; λ ) { λ e − λ x if x ≥ 0 0 if x < 0 f(x; \lambda) \begi…

MYSQL:删除指定时间范围内每个电站每天发电数据除最大值以外的记录

有一个需求&#xff0c;需要保留每个电站每一天发电数据的最大值记录&#xff0c;其余删除。 表数据大概长这样&#xff1a; MYSQL 5.7写法&#xff1a;&#xff08;因为不支持ROW_NUMBER()函数&#xff0c;采用自定义的变量来代替&#xff09; 首次清理一年内数据&#xff1…

5是否有路通向AGI

5.1是否有路通向AGI

如何在算家云搭建模型Stable-diffusion-webUI(AI绘画)

一、Stable Diffusion WebUI简介 Stable Diffusion WebUI 是一个网页版的 AI 绘画工具&#xff0c;基于强大的绘画模型Stable Diffusion &#xff0c;可以实现文生图、图生图等。 二、模型搭建流程 1.选择主机和镜像 &#xff08;1&#xff09;进入算家云的“应用社区”&am…

一本书加印19次,回答小伙伴们几个写书的疑问

前几天又有一个高校老师加松哥微信&#xff0c;表示本学期选了松哥的书做教材&#xff1a; 松哥在 2019 年 1 月份出版了《Spring BootVue 全栈开发实战》这本书&#xff0c;到现在已经是第六年了。 今年 1 月份收到出版社稿酬的时候&#xff0c;我特意去看了下稿酬通知单&…

渣土车识别算法解决城市治理难题

随着城市化进程的加速&#xff0c;渣土车作为建筑工程中不可或缺的运输工具&#xff0c;其频繁的穿行和装载运输过程往往引发一系列问题&#xff0c;如超载、扬尘污染、乱倒渣土等&#xff0c;对城市环境和交通秩序造成了不良影响。为了解决这些问题&#xff0c;采用基于视觉分…

一文教你StableDiffusion图生图批量处理!

今天给大家讲解一下SD图生图的批量处理功能应该如何使用&#xff5e; 一、图生图批量处理功能的基本用法 首先打开webUI&#xff0c;在图生图页面下我们先找到批量处理的菜单&#xff1a; 最简单的批量处理方法只需要用到【输入目录】和【输出目录】两个功能&#xff1a; 第一…