【Spring篇】AOP案例

news2024/11/15 13:33:26

🍓系列专栏:Spring系列专栏

🍉个人主页:个人主页

一、案例:业务层接口执行效率

1.需求分析

这个需求也比较简单,前面我们在介绍 AOP 的时候已经演示过 :
需求 : 任意业务层接口执行均可显示其执行效率(执行时长)
这个案例的目的是查看每个业务层执行的时间,这样就可以监控出哪个业务比较耗时,将其查找出来方便优化。
具体实现的思路 :
(1) 开始执行方法之前记录一个时间
(2) 执行方法
(3) 执行完方法之后记录一个时间
(4) 用后一个时间减去前一个时间的差值,就是我们需要的结果。
所以要在方法执行的前后添加业务,经过分析我们将采用 环绕通知
说明 : 原始方法如果只执行一次,时间太快,两个时间差可能为 0 ,所以我们要执行万次来计算时间

2.环境准备

  • 创建一个Maven项目
  • pom.xml添加Spring依赖
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.itheima</groupId>
  <artifactId>spring_21_case_interface_run_speed</artifactId>
  <version>1.0-SNAPSHOT</version>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.4</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.16</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.0</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>
添加 AccountService AccountServiceImpl AccountDao Account
public interface AccountService {

    void save(Account account);

    void delete(Integer id);

    void update(Account account);

    List<Account> findAll();

    Account findById(Integer id);

}
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    public void save(Account account) {
        accountDao.save(account);
    }

    public void update(Account account){
        accountDao.update(account);
    }

    public void delete(Integer id) {
        accountDao.delete(id);
    }

    public Account findById(Integer id) {
        return accountDao.findById(id);
    }

    public List<Account> findAll() {
        return accountDao.findAll();
    }
}
public interface AccountDao {

    @Insert("insert into tbl_account(name,money)values(#{name},#{money})")
    void save(Account account);

    @Delete("delete from tbl_account where id = #{id} ")
    void delete(Integer id);

    @Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ")
    void update(Account account);

    @Select("select * from tbl_account")
    List<Account> findAll();

    @Select("select * from tbl_account where id = #{id} ")
    Account findById(Integer id);
}
import java.io.Serializable;

public class Account implements Serializable {

    private Integer id;
    private String name;
    private Double money;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
resources 下提供一个 jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root
创建相关配置类
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})

public class SpringConfig {
}
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
}
public class MybatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.itheima.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.itheima.dao");
        return msc;
    }
}
编写Spring整合Junit的测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTestCase {
    @Autowired
    private AccountService accountService;

    @Test
    public void testFindById(){
        Account ac = accountService.findById(2);
    }

    @Test
    public void testFindAll(){
        List<Account> all = accountService.findAll();
    }

}
最终创建好的项目结构如下 :

3.功能开发

步骤 1: 开启 SpringAOP 的注解功能
Spring 的主配置文件 SpringConfig 类中添加注解
@EnableAspectJAutoProxy
步骤 2: 创建 AOP 的通知类
该类要被 Spring 管理,需要添加 @Component
要标识该类是一个 AOP 的切面类,需要添加 @Aspect
配置切入点表达式,需要添加一个方法,并添加 @Pointcut
@Component
@Aspect
public class ProjectAdvice {
    //匹配业务层的所有方法
    @Pointcut("execution(* com.itheima.service.*Service.*(..))")
    private void servicePt(){}


}
步骤 3: 添加环绕通知
runSpeed() 方法上添加 @Around, 完成核心业务,记录万次执行的时间
@Component
@Aspect
public class ProjectAdvice {
    //匹配业务层的所有方法
    @Pointcut("execution(* com.itheima.service.*Service.*(..))")
    private void servicePt(){}

    //设置环绕通知,在原始操作的运行前后记录执行时间
    @Around("ProjectAdvice.servicePt()")
    public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
        //获取执行的签名对象
        Signature signature = pjp.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();

        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
           pjp.proceed();
        }
        long end = System.currentTimeMillis();
        System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
    }

}
步骤4 : 运行单元测试类

 

补充说明
当前测试的接口执行效率仅仅是一个理论值,并不是一次完整的执行过程。
这块只是通过该案例把 AOP 的使用进行了学习,具体的实际值是有很多因素共同决定的。

二、AOP通知获取数据

目前我们写 AOP 仅仅是在原始方法前后追加一些操作,接下来我们要说说 AOP 中数据相关的内容,我们将从获取参数 获取返回值 获取异常 三个方面来研究切入点的相关信息。
前面我们介绍通知类型的时候总共讲了五种,那么对于这五种类型都会有参数,返回值和异常吗 ?
我们先来一个个分析下 :
获取切入点方法的参数,所有的通知类型都可以获取参数
  • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
  • ProceedingJoinPoint:适用于环绕通知
获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究
  • 返回后通知
  • 环绕通知
获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究
  • 抛出异常后通知
  • 环绕通知

1.环境准备

  • 创建一个Maven项目
  • pom.xml添加Spring依赖
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.4</version>
    </dependency>
  </dependencies>
添加 BookDao BookDaoImpl
public interface BookDao {
 public String findName(int id);
 }
 @Repository
 public class BookDaoImpl implements BookDao {

 public String findName(int id) {
 System.out.println("id:"+id);
 return "itcast";
 }
 }
创建 Spring 的配置类
@Configuration
@ComponentScan("com.itheima")

public class SpringConfig {
}
编写通知类
@Component
 @Aspect
 public class MyAdvice {
 @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
 private void pt(){}

 @Before("pt()")
 public void before() {
 System.out.println("before advice ..." );
 }

 @After("pt()")
 public void after() {
 System.out.println("after advice ...");
 }

 @Around("pt()")
 public Object around() throws Throwable{
 Object ret = pjp.proceed();
 return ret;
 }
 @AfterReturning("pt()")
 public void afterReturning() {
 System.out.println("afterReturning advice ...");
 }


 @AfterThrowing("pt()")
 public void afterThrowing() {
 System.out.println("afterThrowing advice ...");
 }
 }
编写 App 运行类
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        String name = bookDao.findName(100);
        System.out.println(name);
    }
}
最终创建好的项目结构如下 :

2.获取参数 

非环绕通知获取方式
在方法上添加 JoinPoint, 通过 JoinPoint 来获取参数
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@Before("pt()")
public void before(JoinPoint jp)
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice ..." );
}
//...其他的略
}
运行 App 类,可以获取如下内容,说明参数 100 已经被获取

思考 : 方法的参数只有一个,为什么获取的是一个数组 ?
因为参数的个数是不固定的,所以使用数组更通配些。
如果将参数改成两个会是什么效果呢 ?
(1) 修改 BookDao 接口和 BookDaoImpl 实现类
public interface BookDao {
public String findName(int id,String password);
}
@Repository
public class BookDaoImpl implements BookDao {
public String findName(int id,String password) {
System.out.println("id:"+id);
return "itcast";
}
}
(2) 修改 App 类,调用方法传入多个参数
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new
AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
String name = bookDao.findName(100,"itheima");
System.out.println(name);
}
}
(3) 运行 App ,查看结果 , 说明两个参数都已经被获取到

说明 :
使用 JoinPoint 的方式获取参数适用于 前置 后置 返回后 抛出异常后通知。
环绕通知获取方式
环绕通知使用的是 ProceedingJoinPoint ,因为 ProceedingJoinPoint JoinPoint 类的子
类,所以对于 ProceedingJoinPoint 类中应该也会有对应的 getArgs() 方法,我们去验证下 :
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp)throws Throwable {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
Object ret = pjp.proceed();
return ret;
}
//其他的略
}
运行 App 后查看运行结果,说明 ProceedingJoinPoint 也是可以通过 getArgs() 获取参数

 

注意 :
pjp.proceed() 方法是有两个构造方法,分别是 :

  •  调用无参数的proceed,当原始方法有参数,会在调用的过程中自动传入参数
  • 所以调用这两个方法的任意一个都可以完成功能
  • 但是当需要修改原始方法的参数时,就只能采用带有参数的方法,如下:
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
Object ret = pjp.proceed(args);
return ret;
}
//其他的略
}
有了这个特性后,我们就可以在环绕通知中对原始方法的参数进行拦截过滤,避免由于参数的
问题导致程序无法正确运行,保证代码的健壮性。

3.获取返回值

对于返回值,只有返回后 AfterReturing 和环绕 Around 这两个通知类型可以获取,具体如何获取 ?
环绕通知获取返回值
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
Object ret = pjp.proceed(args);
return ret;
}
//其他的略
}
上述代码中, ret 就是方法的返回值,我们是可以直接获取,不但可以获取,如果需要还可以进行修改。
返回后通知获取返回值
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(Object ret) {
System.out.println("afterReturning advice ..."+ret);
}
//其他的略
}
注意 :
(1) 参数名的问题

(2)afterReturning 方法参数类型的问题
参数类型可以写成 String ,但是为了能匹配更多的参数类型,建议写成 Object 类型
(3)afterReturning 方法参数的顺序问题

运行App后查看运行结果,说明返回值已经被获取到

4.获取异常 

对于获取抛出的异常,只有抛出异常后 AfterThrowing 和环绕 Around 这两个通知类型可以获取,具
体如何获取 ?
环绕通知获取异常
这块比较简单,以前我们是抛出异常,现在只需要将异常捕获,就可以获取到原始方法的异常信息了
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp){
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
Object ret = null;
try{
ret = pjp.proceed(args);
}catch(Throwable throwable){
t.printStackTrace();
}
return ret;
}
//其他的略
}
catch 方法中就可以获取到异常,至于获取到异常以后该如何处理,这个就和你的业务需求有关
了。
抛出异常后通知获取异常
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt(){}
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ..."+t);
}
//其他的略
}
如何让原始方法抛出异常,方式有很多,
@Repository
public class BookDaoImpl implements BookDao {
public String findName(int id,String password) {
System.out.println("id:"+id);
if(true){
throw new NullPointerException();
}
return "itcast";
}
}
注意 :

运行App后,查看控制台,就能看的异常信息被打印到控制台 

至此,AOP通知如何获取数据就已经讲解完了,数据中包含参数返回值异常(了解) 

三、百度网盘密码数据兼容处理

1.需求分析

需求: 对百度网盘分享链接输入密码时尾部多输入的空格做兼容处理。

问题描述 :
点击链接,会提示,请输入提取码,如下图所示

  • 当我们从别人发给我们的内容中复制提取码的时候,有时候会多复制到一些空格,直接粘贴到百度的提取码输入框
  • 但是百度那边记录的提取码是没有空格的
  • 这个时候如果不做处理,直接对比的话,就会引发提取码不一致,导致无法访问百度盘上的内容
  • 所以多输入一个空格可能会导致项目的功能无法正常使用。
此时我们就想能不能将输入的参数先帮用户去掉空格再操作呢 ?
  • 答案是可以的,我们只需要在业务方法执行之前对所有的输入参数进行格式处理——trim() 是对所有的参数都需要去除空格么?
  • 也没有必要,一般只需要针对字符串处理即可。

  • 以后涉及到需要去除前后空格的业务可能会有很多,这个去空格的代码是每个业务都写么?
  • 可以考虑使用AOP来统一处理。
AOP 有五种通知类型,该使用哪种呢 ?
我们的需求是将原始方法的参数处理后在参与原始方法的调用,能做这件事的就只有环绕通知。
综上所述,我们需要考虑两件事 :
①:在业务方法执行之前对所有的输入参数进行格式处理 —— trim()

②:使用处理后的参数调用原始方法——环绕通知中存在对原始方法的调用

2.具体实现

添加 ResourcesService ResourcesServiceImpl,ResourcesDao ResourcesDaoImpl
public interface ResourcesService {
    public boolean openURL(String url ,String password);

}
@Service
public class ResourcesServiceImpl implements ResourcesService {
    @Autowired
    private ResourcesDao resourcesDao;

    public boolean openURL(String url, String password) {
        return resourcesDao.readResources(url,password);
    }
}
public interface ResourcesDao {

    boolean readResources(String url, String password);
}
@Repository
public class ResourcesDaoImpl implements ResourcesDao {
    public boolean readResources(String url, String password) {
        System.out.println(password.length());
        //模拟校验
        return password.equals("root");
    }
}
创建 Spring 的配置类
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}
AOP
@Component
@Aspect
public class DataAdvice {
    @Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))")
    private void servicePt(){}

    @Around("DataAdvice.servicePt()")
    public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        for (int i = 0; i < args.length; i++) {
            //判断参数是不是字符串
            if(args[i].getClass().equals(String.class)){
                args[i] = args[i].toString().trim();
            }
        }
        Object ret = pjp.proceed(args);
        return ret;
    }


}
编写 App 运行类
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        ResourcesService resourcesService = ctx.getBean(ResourcesService.class);
        boolean flag = resourcesService.openURL("http://pan.baidu.com/haha", "root ");
        System.out.println(flag);
    }
}

四、AOP总结

AOP的知识就已经讲解完了,接下来对于AOP的知识进行一个总结:

1.AOP的核心概念

概念: AOP(Aspect Oriented Programming) 面向切面编程,一种编程范式
作用:在不惊动原始设计的基础上为方法进行功能 增强
核心概念
  • 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
  • 连接点(JoinPoint):在SpringAOP中,理解为任意方法的执行
  • 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
  • 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
  • 切面(Aspect):描述通知与切入点的对应关系
  • 目标对象(Target):被代理的原始对象成为目标对象

2.切入点表达式

切入点表达式标准格式:动作关键字 ( 访问修饰符 返回值 包名 . / 接口名 . 方法名(参数)异常
)
 execution(* com.itheima.service.*Service.*(..))
切入点表达式描述通配符:
  • 作用:用于快速描述,范围描述
  • *:匹配任意符号(常用)
  • .. :匹配多个连续的任意符号(常用)
  • +:匹配子类类型

切入点表达式书写技巧

1. 标准规范 开发
2. 查询操作的返回值建议使用 * 匹配
3. 减少使用 .. 的形式描述包
4. 对接口 进行描述 ,使用 * 表示模块名,例如 UserService 的匹配描述为 *Service
5. 方法名书写保留动词,例如get ,使用 * 表示名词,例如 getById 匹配描述为 getBy*
6. 参数根据实际情况灵活调整

3.五种通知类型

  • 前置通知
  • 后置通知
  • 环绕通知(重点)
  1. 环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用
  2. 环绕通知可以隔离原始方法的调用执行
  3. 环绕通知返回值设置为Object类型
  4. 环绕通知中可以对原始方法调用过程中出现的异常进行处理
  • 返回后通知
  • 抛出异常后通知

4.通知中获取参数

获取切入点方法的参数,所有的通知类型都可以获取参数
  • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
  • ProceedingJoinPoint:适用于环绕通知
获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究
  • 返回后通知
  • 环绕通知
获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究
  • 抛出异常后通知
  • 环绕通知

笔记来自: 黑马程序员SSM框架教程

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

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

相关文章

如何选对适合你的FPGA?快速掌握选型技巧!

FPGA厂家和芯片型号众多&#xff0c;在开发过程中&#xff0c;特别是新产品新项目时&#xff0c;都会面临FPGA选型的问题。 如何选择出适合的FPGA型号非常关键&#xff0c;需要评估需求、功能、成本、存储器、高速收发器等各种因素&#xff0c;选出性能与成本平衡的FPGA芯片。…

从零玩转设计模式之外观模式-waiguanmos

title: 从零玩转设计模式之外观模式 date: 2022-12-12 15:49:05.322 updated: 2022-12-23 15:34:40.394 url: https://www.yby6.com/archives/waiguanmos categories: - 设计模式 tags: - 设计模式 什么是外观模式 外观模式是一种软件设计模式&#xff0c;它提供了一种将多个…

进阶必看:高速PCB Layout设计的技术指南

当今电子行业中&#xff0c;高速PCB电路越来越广泛&#xff0c;已成为当代PCB工程师的重要技能&#xff0c;而在高速PCB电路中&#xff0c;高速PCB Layout设计是一项高难度高技术的工作&#xff0c;其设计质量直接关系到电路的性能。所以做好PCB Layout设计是非常非常重要的。 …

Boost开发指南-1.2progress_display

Progress_display progress_display可以在控制台上显示程序的执行进度&#xff0c;如果程序执行很耗费时间&#xff0c;那么它能够提供一个友好的用户界面&#xff0c;不至于让用户在等待中失去耐心。 progress_display位于名字空间boost&#xff0c;为了使用progress_displa…

内网自建代理ChatGPT

使用GPT比较频繁&#xff0c;一开始翻墙还能接受&#xff0c;但是用美国节点访问其他国外网站&#xff0c;确实比较麻烦。因此决定自己转发一个出来。 一、获取OpenAI授权密钥 首先&#xff0c;进入platform.openai.com-Personal-View API keys 不过OpenAI的key并不是免费的&…

VMware虚拟机三种网络模式详解之Bridged(桥接模式)

VMware虚拟机三种网络模式详解 Bridged&#xff08;桥接模式&#xff09; 由于Linux目前很热门&#xff0c;越来越多的人在学习Linux&#xff0c;但是买一台服务放家里来学习&#xff0c;实在是很浪费。那么如何解决这个问题&#xff1f;虚拟机软件是很好的选择&#xff0c;常…

登高作业安全带穿戴识别系统 yolov5

登高作业安全带穿戴识别系统通过yolov5python网络框架模型技术&#xff0c;登高作业安全带穿戴识别算法模型实现对登高作业人员是否穿戴安全带进行监测并及时发出警报。YOLO系列算法是一类典型的one-stage目标检测算法&#xff0c;其利用anchor box将分类与目标定位的回归问题结…

前端web入门-HTML-day02

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 列表 无序列表 有序列表 定义列表 表格 基本使用 合并单元格 跨列合并 跨行合并 表单 input …

网络安全里主要的岗位有哪些?小白如何快速入门学习黑客?

入门Web安全、安卓安全、二进制安全、工控安全还是智能硬件安全等等&#xff0c;每个不同的领域要掌握的技能也不同。 当然入门Web安全相对难度较低&#xff0c;也是很多人的首选。主要还是看自己的兴趣方向吧。 本文就以下几个问题来说明网络安全大致学习过程&#x1f447; 网…

软件设计师 数据库刷题项并包含知识点总结

**两级映像 有概念模式和内模式跟物理独立性相关&#xff0c;有外模式和概念模式跟逻辑独立性相关 ** 属性列就是RS共同拥有的ABC&#xff0c;一般去除后面的&#xff0c;所以就只有前面三个ABC&#xff0c;元组就是有没有自然连接成功的&#xff0c;就是R.AS.A R.BS.B… 选项里…

[人工智能原理]

软件工程 定义 采用工程概念、原理、技术、方法来开发、维护软件&#xff0c;把经过时间考验而证明正确的管理技术和当前能够得到的最好技术方法结合起来&#xff0c;经济开发出高质量软件并有效的维护 基本目标 目标 可用性 正确性 合算性 原则 采用适合的开发范型、开…

计算机操作系统(慕课版)第一章课后题答案

第一章 操作系统引论 一、简答题 1.在计算机系统上配置OS的目标是什么&#xff1f;作用表现在哪几个方面&#xff1f; 在计算机系统上配置OS&#xff0c;主要目标是实现&#xff1a;方便性、有效性、可扩充性和开放性&#xff1b; OS的作用主要表现在以下3个方面&#xff1a; 1…

matplotlib后端@backend@高清图输出格式控制@SVG格式输出

文章目录 notebookmatplotlib&#x1f388;matplotlib backendbackendfrontend后端类型AGG配置后端Note不区分大小写三种配置方式在matplotlibrc文件中使用rcParams["backend"]参数&#xff1a;使用MPLBACKEND环境变量&#xff1a;使用matplotlib.use()函数&#xff…

阿里三面过了,却无理由挂了,HR反问一句话:为什么不考虑阿里?

进入互联网大厂一般都是“过五关斩六将”&#xff0c;难度堪比西天取经&#xff0c;但当你真正面对这些大厂的面试时&#xff0c;有时候又会被其中的神操作弄的很是蒙圈。 近日&#xff0c;某位测试员发帖称&#xff0c;自己去阿里面试&#xff0c;三面都过了&#xff0c;却被…

SpringCloud微服务调用方式(RestTemplate)

服务调用方式 RPC和HTTP 无论是微服务还是SOA&#xff0c;都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢&#xff1f; 常见的远程调用方式有以下2种&#xff1a; RPC&#xff1a;Remote Produce Call远程过程调用&#xff0c;类似的还有 。自定义数据格式&am…

由浅入深Netty组件实战

目录 1 EventLoop1.1 演示 NioEventLoop 处理 io 事件1.2 演示 NioEventLoop 处理普通任务1.3 演示 NioEventLoop 处理定时任务 2 Channel2.1 ChannelFuture2.2 CloseFuture 3 Future & Promise3.1 例1&#xff1a;同步处理任务成功3.2 例2&#xff1a;异步处理任务成功3.3…

Navicat premium 15激活教程及安装教程+报错解决办法

Navicat premium 15激活教程及安装教程报错解决办法 1、安装包和注册工具下载2、安装Navicate Premium 15&#xff0c;直接下一步安装即可&#xff0c;安装位置可以按照到D盘3、激活Navicate Premium 15打开安装包里面的Navicat Keygen Patch v5.6.0 DFoX.exe工具当点击path选择…

看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动UART串口通信) 2023.5.20

目录 前言整体文件结构源码分析&#xff08;保姆级讲解&#xff09;串口驱动初始化部分UART1数据收发函数部分 编译结果验证结束语 前言 串口是我们在开发过程中最常用到的外设&#xff0c;所以我们必须掌握。 整体文件结构 源码分析&#xff08;保姆级讲解&#xff09; 串口…

[Leetcode刷题] - LC075 Sort Colors

题目描述 一个随机序列包含0&#xff0c;1&#xff0c;2 在不占用额外内存的情况下将序列排序。 题目思路 1. 计数排序 计数排序思路比较简单&#xff0c;记录三个数字出现频率&#xff0c;然后根据频率将0&#xff0c;1&#xff0c;2重新加入数组&#xff0c;思路较为简单…

【数据结构】--单链表力扣面试题④找链表中倒数第k个结点

目录 法一、遍历链表法 法二、快慢指针法 题述&#xff1a;输入一个链表&#xff0c;输出该链表中倒数第k个结点 示例&#xff1a; 输入:1,[1,2,3,4,5] 返回值:[5] 已知链表的定义和函数头FindKthToTail&#xff0c;让你完善FindKthToTail函数 struct ListNode { int…