开发工具篇第十二讲:常用开发库 - Lombok工具库详解

news2024/11/15 15:54:16

开发工具篇第十二讲:常用开发库 - Lombok工具库详解

Lombok是一款非常实用Java工具,可用来帮助开发人员消除Java的冗长代码,尤其是对于简单的Java对象(POJO)。实际上我并不推荐使用Lombok(不主动使用它), 但是因为它有着很大的使用量,我们仍然有必要掌握它,不仅知道如何使用和它解决的问题,还要知道它有哪些坑。

文章目录

  • 开发工具篇第十二讲:常用开发库 - Lombok工具库详解
    • 1、Lombok的引入
      • 1.1、在引入Lombok之前我们是怎么做的
    • 2、Lombok的安装和使用
      • 2.1、Lombok官网
      • 2.2、Lombok安装
      • 2.3、Lombok注解说明
      • 2.4、Lombok代码示例
    • 3、Lombok深入理解
      • 3.1、Lombok解决了什么问题
      • 3.2、Lombok的原理
      • 3.3、Lombok类似原理工具有什么
      • 3.4、Lombok没有未来 - Java14 Record了解下
    • 4、Lombok有什么坑
      • 4.1、`@Data`的坑
      • 4.2、代码可读性,可调试性低
      • 4.3、Lombok有很强的侵入性
      • 4.4、Lombok破坏了封装性
    • 5、总结
    • 6、参考文章

1、Lombok的引入

我们通常需要编写大量代码才能使类变得有用。如以下内容:

  • toString()方法
  • hashCode() and equals()方法
  • Getter and Setter 方法
  • 构造函数

对于这种简单的类,这些方法通常是无聊的、重复的,而且是可以很容易地机械地生成的那种东西(ide通常提供这种功能)。

1.1、在引入Lombok之前我们是怎么做的

IDE中添加getter/setter, toString等代码

img

2、Lombok的安装和使用

2.1、Lombok官网

  • Lombok官网

2.2、Lombok安装

IDEA搜索Lombok插件

  • img

另外需要注意的是,在使用lombok注解的时候记得要导入lombok.jar包到工程,如果使用的是Maven的工程项目的话,要在其pom.xml中添加依赖如下

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

2.3、Lombok注解说明

看官网这里

  • val:用在局部变量前面,相当于将变量声明为final
  • @NonNull:给方法参数增加这个注解会自动在方法内对该参数进行是否为空的校验,如果为空,则抛出NPE(NullPointerException)
  • @Cleanup:自动管理资源,用在局部变量之前,在当前变量范围内即将执行完毕退出之前会自动清理资源,自动生成try-finally这样的代码来关闭流
  • @Getter/@Setter:用在属性上,再也不用自己手写settergetter方法了,还可以指定访问范围
  • @ToString:用在类上,可以自动覆写toString方法,当然还可以加其他参数,例如@ToString(exclude=”id”)排除id属性,或者@ToString(callSuper=true, includeFieldNames=true)调用父类的toString方法,包含所有属性
  • @EqualsAndHashCode:用在类上,自动生成equals方法和hashCode方法
  • @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor:用在类上,自动生成无参构造和使用所有参数的构造函数以及把所有+ @NonNull属性作为参数的构造函数,如果指定staticName = “of”`参数,同时还会生成一个返回类对象的静态工厂方法,比使用构造函数方便很多
  • @Data:注解在类上,相当于同时使用了@ToString@EqualsAndHashCode@Getter@Setter@RequiredArgsConstrutor这些注解,对于POJO类十分有用
  • @Value:用在类上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter方法,而不提供setter方法
  • @Builder:用在类、构造器、方法上,为你提供复杂的builder APIs,让你可以像如下方式一样调用 Person.builder().name("Adam Savage").city("SanFrancisco").job("Mythbusters").job("Unchained Reaction").build(); 更多说明参考Builder
  • @SneakyThrows:自动抛受检异常,而无需显式在方法上使用throws语句
  • @Synchronized:用在方法上,将方法声明为同步的,并自动加锁,而锁对象是一个私有的属性$lock$LOCK,而java中的synchronized关键字锁对象是this,锁在this或者自己的类对象上存在副作用,就是你不能阻止非受控代码去锁this或者类对象,这可能会导致竞争条件或者其它线程错误
  • @Getter (lazy=true):可以替代经典的 Double Check Lock样板代码
  • @Log:根据不同的注解生成不同类型的log对象,但是实例名称都是log,有六种可选实现类
    • @CommonsLog Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
    • @Log Creates log = java.util.logging.Logger.getLogger(LogExample.class.getName());
    • @Log4j Creates log = org.apache.log4j.Logger.getLogger(LogExample.class);
    • @Log4j2 Creates log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
    • @Slf4j Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
    • @XSlf4j Creates log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);

2.4、Lombok代码示例

  • val示例
public static void main(String[] args) {
    val sets = new HashSet<String>();
    val lists = new ArrayList<String>();
    val maps = new HashMap<String, String>();
    //=>相当于如下
    final Set<String> sets2 = new HashSet<>();
    final List<String> lists2 = new ArrayList<>();
    final Map<String, String> maps2 = new HashMap<>();
}

// 商品中心的使用
val result = instance.updateRule(request, user);
assertTrue(result.isSuccess());
  • @NonNull 示例
public void notNullExample(@NonNull String string) {
    string.length();
}
//=>相当于
public void notNullExample(String string) {
    if (string != null) {
        string.length();
    } else {
        throw new NullPointerException("null");
    }
}

// 在商品中心使用
/**
     * 根据服务商品ID列表,获取服务商品下所有的sku,根据itemid分组
     * @param itemIds
     * @return
     */
    private Map<@NonNull Long, List<Sku>> getItemSkusGroupItemId(List<Long> itemIds) {
        // 每个服务商品的sku分组信息
        return skusByItemIdsResponse.getResult().stream().filter(sku -> !ObjectUtils.isEmpty(sku.getItemId()))
            .collect(Collectors.groupingBy(Sku::getItemId));
    }
  • @Cleanup示例
public static void main(String[] args) {
    try {
        @Cleanup InputStream inputStream = new FileInputStream(args[0]);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    //=>相当于
    InputStream inputStream = null;
    try {
        inputStream = new FileInputStream(args[0]);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • @Getter/@Setter示例
@Setter(AccessLevel.PUBLIC)
@Getter(AccessLevel.PROTECTED)
private int id;
private String shape;
  • @ToString示例
// callSuper = true  可以继承父类字段参与toString计算
@ToString(exclude = "id", callSuper = true, includeFieldNames = true)
public class LombokDemo {
    private int id;
    private String name;
    private int age;
    public static void main(String[] args) {
        //输出 LombokDemo(super = LombokDemo@48524010, name=null, age=0)
        System.out.println(new LombokDemo());
    }
}
  • @EqualsAndHashCode示例
// callSuper为false,可以继承父类字段
@EqualsAndHashCode(exclude = {"id", "shape"}, callSuper = false)
public class LombokDemo {
    private int id;
    private String shape;
}
  • @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor示例
@NoArgsConstructor
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor
public class LombokDemo {
    @NonNull
    private int id;
    @NonNull
    private String shape;
    private int age;
    public static void main(String[] args) {
        new LombokDemo(1, "circle");
        //使用静态工厂方法
        LombokDemo.of(2, "circle");
        //无参构造
        new LombokDemo();
        //包含所有参数
        new LombokDemo(1, "circle", 2);
    }
}
  • @Data示例
import lombok.Data;

@Data
public class Menu {
    private String shopId;
    private String skuMenuId;
    private String skuName;
    private String normalizeSkuName;
    private String dishMenuId;
    private String dishName;
    private String dishNum;
    //默认阈值
    private float thresHold = 0;
    //新阈值
    private float newThresHold = 0;
    //总得分
    private float totalScore = 0;
}
  • @Value示例
@Value
public class LombokDemo {
    @NonNull
    private int id;
    @NonNull
    private String shap;
    private int age;
    //相当于
    private final int id;
    public int getId() {
        return this.id;
    }
    ...
}
  • @Builder示例
@Builder
public class BuilderExample {
    private String name;
    private int age;
    @Singular
    private Set<String> occupations;
    public static void main(String[] args) {
        LombokDemo3 test = LombokDemo3.builder().age(11).name("test")
                .occupation("1")
                .occupation("2")
                .build();
    }
}

@Singular 可以为集合类型的参数或字段生成一种特殊的方法, 它采用修改列表中一个元素而不是整个列表的方式,可以是增加一个元素,也可以是删除一个元素。

在使用@Singular注释注释一个集合字段(使用@Builder注释类),lombok会将该构建器节点视为一个集合,并生成两个adder方法而不是setter方法。

生成代码如下:

public LombokDemo3.LombokDemo3Builder occupation(String occupation) {
    if (this.occupations == null) {
        this.occupations = new ArrayList();
    }
    this.occupations.add(occupation);
    return this;
}

public LombokDemo3.LombokDemo3Builder occupations(Collection<? extends String> occupations) {
    if (occupations == null) {
        throw new NullPointerException("occupations cannot be null");
    } else {
        if (this.occupations == null) {
            this.occupations = new ArrayList();
        }
        this.occupations.addAll(occupations);
        return this;
    }
}

public LombokDemo3.LombokDemo3Builder clearOccupations() {
    if (this.occupations != null) {
        this.occupations.clear();
    }
    return this;
}
  • Builder.Default
@Builder
@ToString
public class BuilderDefaultExample {

    @Builder.Default
    private final String id = UUID.randomUUID().toString();
    
    private String username;

    @Builder.Default
    private long insertTime = System.currentTimeMillis();

}
  • @SneakyThrows 示例
import lombok.SneakyThrows;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
public class Test {
    @SneakyThrows()
    public void read() {
        InputStream inputStream = new FileInputStream("");
    }
    @SneakyThrows
    public void write() {
        throw new UnsupportedEncodingException();
    }
    //相当于
    public void read() throws FileNotFoundException {
        InputStream inputStream = new FileInputStream("");
    }
    public void write() throws UnsupportedEncodingException {
        throw new UnsupportedEncodingException();
    }
}
  • @Synchronized示例
public class SynchronizedDemo {
    @Synchronized
    public static void hello() {
        System.out.println("world");
    }
    //相当于
    private static final Object $LOCK = new Object[0];
    public static void hello() {
        synchronized ($LOCK) {
            System.out.println("world");
        }
    }
}
  • @Getter(lazy = true)示例
public class GetterLazyExample {
    @Getter(lazy = true)
    private final double[] cached = expensive();
    private double[] expensive() {
        double[] result = new double[1000000];
        for (int i = 0; i < result.length; i++) {
            result[i] = Math.asin(i);
        }
        return result;
    }
}

// 相当于如下所示: 
import java.util.concurrent.atomic.AtomicReference;

public class GetterLazyExample {
    private final AtomicReference<java.lang.Object> cached = new AtomicReference<>();
    public double[] getCached() {
        java.lang.Object value = this.cached.get();
        if (value == null) {
            synchronized (this.cached) {
                value = this.cached.get();
                if (value == null) {
                    final double[] actualValue = expensive();
                    value = actualValue == null ? this.cached : actualValue;
                    this.cached.set(value);
                }
            }
        }
        return (double[]) (value == this.cached ? null : value);
    }
    private double[] expensive() {
        double[] result = new double[1000000];
        for (int i = 0; i < result.length; i++) {
            result[i] = Math.asin(i);
        }
        return result;
    }
}

3、Lombok深入理解

3.1、Lombok解决了什么问题

这个简单,就是简化代码。

3.2、Lombok的原理

会发现在Lombok使用的过程中,只需要添加相应的注解,无需再为此写任何代码。自动生成的代码到底是如何产生的呢?

核心之处就是对于注解的解析上。JDK5引入了注解的同时,也提供了两种解析方式。

  • 运行时解析

运行时能够解析的注解,必须将@Retention设置为RUNTIME, 比如@Retention(RetentionPolicy.RUNTIME),这样就可以通过反射拿到该注解。java.lang,reflect反射包中提供了一个接口AnnotatedElement,该接口定义了获取注解信息的几个方法,Class、Constructor、Field、Method、Package等都实现了该接口,对反射熟悉的朋友应该都会很熟悉这种解析方式。

  • 编译时解析

编译时解析有两种机制,分别简单描述下:

1)Annotation Processing Tool

apt自JDK5产生,JDK7已标记为过期,不推荐使用,JDK8中已彻底删除,自JDK6开始,可以使用Pluggable Annotation Processing API来替换它,apt被替换主要有2点原因:

  • api都在com.sun.mirror非标准包下
  • 没有集成到javac中,需要额外运行

2)Pluggable Annotation Processing API

JSR 269: Pluggable Annotation Processing API 自JDK6加入,作为apt的替代方案,它解决了apt的两个问题,javac在执行的时候会调用实现了该API的程序,这样我们就可以对编译器做一些增强,这时javac执行的过程如下:

  • img

Lombok本质上就是一个实现了“JSR 269 API”的程序。在使用javac的过程中,它产生作用的具体流程如下:

  • javac对源代码进行分析,生成了一棵抽象语法树(AST)

  • 运行过程中调用实现了“JSR 269 API”的Lombok程序

  • 此时Lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点

  • javac使用修改后的抽象语法树(AST)生成字节码文件,即给class增加新的节点(代码块)

  • img

从上面的Lombok执行的流程图中可以看出,在Javac 解析成AST抽象语法树之后, Lombok 根据自己编写的注解处理器,动态地修改 AST,增加新的节点(即Lombok自定义注解所需要生成的代码),最终通过分析生成JVM可执行的字节码Class文件。使用Annotation Processing自定义注解是在编译阶段进行修改,而JDK的反射技术是在运行时动态修改,两者相比,反射虽然更加灵活一些但是带来的性能损耗更加大。

3.3、Lombok类似原理工具有什么

换言之,我们可以通过Lombok同样的思路解决什么问题?

  • 第一个问题,我可以通过上述原理,自己实现一个类似Lombok 吗?

可以的,给你找了一篇文章

  • 还有一些其它类库使用这种方式实现,比如:
    • Google Auto
    • Dagger

3.4、Lombok没有未来 - Java14 Record了解下

Lombok是没有未来的,因为Java完全可以对于这种纯数据载体通过另外一种方式表示, 所以有了JEP 359: Records, 简单而言就是通过一个语法糖来解决。

  • img

  • 从前

public class Range {
 
    private final int min;
    private final int max;
 
    public Range(int min, int max) {
        this.min = min;
        this.max = max;
    }
 
    public int getMin() {
        return min;
    }
 
    public int getMax() {
        return max;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Range range = (Range) o;
        return min == range.min && max == range.max;
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(min, max);
    }
 
    @Override
    public String toString() {
        return "Range{" +
          "min=" + min +
          ", max=" + max +
          '}';
    }
}
  • Java14 record
public record Range(int min, int max) {}

没错就是这个简单!这个语法糖是不是有 “卧槽” 的感觉?我们声明这种类使用record 标识(目前不知道 record 会不会上升到关键字的高度)。当你用record 声明一个类时,该类将自动拥有以下功能:

  • 获取成员变量的简单方法,以上面代码为例 min() 和 max() 。注意区别于我们平常getter的写法。
  • 一个 equals 方法的实现,执行比较时会比较该类的所有成员属性
  • 重写 equals 当然要重写 hashCode
  • 一个可以打印该类所有成员属性的 toString 方法。
  • 请注意只会有一个构造方法。

因为这个特性是 preview feature,默认情况下是无法编译和执行的。同样以上面为例我们需要执行:

 $ javac -d classes --enable-preview --release 14 Range.java
 $ java -classpath classes --enable-preview Range

在 Jshell 中运行

jshell> Range r = new Range(10, 20);
r ==> Range[min=10, max=20]
jshell> r.min()
$5 ==> 10
jshell> r.toString()
$6 ==> "Range[min=10, max=20]"
jshell> r
r ==> Range[min=10, max=20]

虽然 record 声明的类没有 final 关键字,实际上它是一个不可变类。除了一些限制外,它依旧是一个普通的Java 类。因此,我们可以像添加普通类一样添加逻辑。我们可以在构造实例时强制执行前提条件:

public record Range(int min, int max) {
    public Range {
        if (min >= max)
            throw new IllegalArgumentException("min should be less than max");
    }
}

另外我们也可以给 Records 类增加普通方法、静态属性、静态方法,这里不再举例;

为了简化语法糖的推理,不能在类内声明成员属性。以下是错误的示范:

public record Range(int min, int max) {
    // 错误的示范
    private String name;
}

4、Lombok有什么坑

谈谈Lombok容易被忽视的坑, 看似代码简洁背后的代价。

4.1、@Data的坑

在使用Lombok过程中,如果对于各种注解的底层原理不理解的话,很容易产生意想不到的结果。

举一个简单的例子,我们知道,当我们使用@Data定义一个类的时候,会自动帮我们生成equals()方法 。

但是如果只使用了@Data,而不使用@EqualsAndHashCode(callSuper=true)的话,会默认是@EqualsAndHashCode(callSuper=false),** 这时候生成的equals()方法只会比较子类的属性,不会考虑从父类继承的属性**,无论父类属性访问权限是否开放。

这就可能得到意想不到的结果。

4.2、代码可读性,可调试性低

在代码中使用了Lombok,确实可以帮忙减少很多代码,因为Lombok会帮忙自动生成很多代码。但是这些代码是要在编译阶段才会生成的,所以在开发的过程中,其实很多代码其实是缺失的。

在代码中大量使用Lombok,就导致代码的可读性会低很多,而且也会给代码调试带来一定的问题。 比如,我们想要知道某个类中的某个属性的getter方法都被哪些类引用的话,就没那么简单了。

4.3、Lombok有很强的侵入性

  • 强J队友

因为Lombok的使用要求开发者一定要在IDE中安装对应的插件。如果未安装插件的话,使用IDE打开一个基于Lombok的项目的话会提示找不到方法等错误。导致项目编译失败。也就是说,如果项目组中有一个人使用了Lombok,那么其他人就必须也要安装IDE插件。否则就没办法协同开发。

更重要的是,如果我们定义的一个jar包中使用了Lombok,那么就要求所有依赖这个jar包的所有应用都必须安装插件,这种侵入性是很高的。

  • 影响升级

因为Lombok对于代码有很强的侵入性,就可能带来一个比较大的问题,那就是会影响我们对JDK的升级。按照如今JDK的升级频率,每半年都会推出一个新的版本,但是Lombok作为一个第三方工具,并且是由开源团队维护的,那么他的迭代速度是无法保证的

所以,如果我们需要升级到某个新版本的JDK的时候,若其中的特性在Lombok中不支持的话就会受到影响。

还有一个可能带来的问题,就是Lombok自身的升级也会受到限制。因为一个应用可能依赖了多个jar包,而每个jar包可能又要依赖不同版本的Lombok,这就导致在应用中需要做版本仲裁,而我们知道,jar包版本仲裁是没那么容易的,而且发生问题的概率也很高。

4.4、Lombok破坏了封装性

以上几个问题,我认为都是有办法可以避免的。但是有些人排斥使用Lombok还有一个重要的原因,那就是他会破坏封装性

众所周知,Java的三大特性包括封装性继承性多态性

如果我们在代码中直接使用Lombok,那么他会自动帮我们生成getter、setter 等方法,这就意味着,一个类中的所有参数都自动提供了设置和读取方法。

举个简单的例子,我们定义一个购物车类:

@Data
public class ShoppingCart { 

    //商品数目
    private int itemsCount; 
    //总价格
    private double totalPrice; 
    //商品明细
    private List items = new ArrayList<>();
}

//例子来源于《极客时间-设计模式之美》

我们知道,购物车中商品数目、商品明细以及总价格三者之前其实是有关联关系的,如果需要修改的话是要一起修改的。

但是,我们使用了Lombok的@Data注解,对于itemsCount 和 totalPrice这两个属性。虽然我们将它们定义成 private 类型,但是提供了 publicgettersetter 方法。

外部可以通过 setter 方法随意地修改这两个属性的值。我们可以随意调用 setter 方法,来重新设置 itemsCount、totalPrice 属性的值,这也会导致其跟 items 属性的值不一致。

而面向对象封装的定义是:通过访问权限控制,隐藏内部数据,外部仅能通过类提供的有限的接口访问、修改内部数据。所以,暴露不应该暴露的 setter 方法,明显违反了面向对象的封装特性

好的做法应该是不提供getter/setter,而是只提供一个public的addItem方法,同时去修改itemsCount、totalPrice以及items三个属性。

以上问题其实也是可以解决的,但这提醒了我们需要理解Lombok,而不是一股脑的用@Data注解。

5、总结

  • 优缺点
    • 优点:大大减少了代码量,使代码非常简洁
    • 缺点:可能存在对队友不友好、对代码不友好、对调试不友好、对升级不友好等问题。Lombok还会导致破坏封装性的问题。@Data中覆盖equalshashCode的坑等。
  • 什么样的情况使用Lombok
    • 团队整体的共识,IDE规范,相关代码规范等
    • 对Lombok足够了解,比如知道其中的坑等
  • 不推荐使用Lombok的理由
    • 其实我们不缺时间写Getter和Setter的,这些代码通常是由IDE生成的。简化也是有代价的。
    • 对Lombok认知不够,导致带来的坑。
    • Java14中Record了解下。

6、参考文章

  • https://projectlombok.org/
  • http://blog.itpub.net/69908877/viewspace-2676272/
  • https://www.jianshu.com/p/63038c7c515a
  • https://www.cnblogs.com/heyonggang/p/8638374.html
  • http://blog.didispace.com/java-lombok-how-to-use/

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

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

相关文章

CANoe测试TC8

OPEN联盟发布的TC8是目前行业内关于车载以太网的标准测试规范之一。 CANoe环境需要硬件和软件: 硬件是CANoe设备&#xff0c;用来连接电脑和DUT&#xff0c;TC8测试的是以太&#xff0c;那么CANoe设备必须支持以太才行&#xff0c;目前VN5640以上都是支持的。 软件需要安装CANo…

对git rebase 和git merge的理解

一、是什么 在使用 git 进行版本管理的项目中&#xff0c;当完成一个特性的开发并将其合并到 master 分支时&#xff0c;会有两种方式&#xff1a; git mergegit rebase git rebase 与 git merge都有相同的作用&#xff0c;都是将一个分支的提交合并到另一分支上&#xff0c;…

物联网技术在地下综合管廊智能化建设中的应用实例分析

摘 要:物联网是21世纪传感技术、通信技术、信息技术的应用大集成利用物联网技术实现地下综合管廊的智能化管理符合经济和城市规划发展的需要分析了物联网技术的特点及地下综合管廊建设中物联网技术的应用并结合物联网技术在地下综合管廊建设的应用实例对物联网技术在地下综合…

Qt扫盲-Windows任务栏使用总结

Qt扫盲-Windows任务栏使用总结一、概述二、覆盖图标和进度指示器三、跳转列表四、缩略图工具栏一、概述 任务栏为用户提供了访问桌面上打开的应用程序的权限。Windows自动在任务栏上创建用于访问应用程序窗口的按钮。 从 Windows 7到Windows10 都有效果。就是在任务栏上的一个…

基于springboot框架个人博客管理系统

一、项目简介 本项目是一套基于springboot框架实现的个人博客管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&a…

头歌:UDP Ping程序实现 客户端创建UDP套接字

头歌平台&#xff1a;头歌实践教学平台 (educoder.net)创建数据包套接字套接字 Socket 实质上提供了主机间进程通信的连接点。进程通信之前&#xff0c;双方首先必须各自创建一个连接点,否则是没有办法建立联系并相互通信的。一个完整的 Socket {协议,本地地址,本地端口,远程地…

在 React 中使用 i18next

1. 安装依赖 npm i i18next react-i18next i18next-browser-languagedetectori18next 提供了翻译的基本能力。react-i18next 是 i18next 的一个插件&#xff0c;用来降低 react 的使用成本。i18next-browser-languagedetector 是用来检测浏览器语言的插件。 2. 在src下创建i18…

.net core 中使用confluent kafka构建生产者

创建.net 6 API安装依赖包 创建kafka生产者 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Confluent.Kafka; using Confluent.Kafka.Admin; using KafkaHelper.Config; using Microsoft.Exte…

Cuk拓扑产生负压

1、基础拓扑的输入输出电流连续情况 1>Buck电路 图中绿色波形为输入端的电流&#xff08;测的图中MOS上的电流&#xff09;&#xff0c;蓝色的输出端的电流&#xff08;图中电感L4的电流&#xff09;&#xff0c;可以看出输入端电流不连续&#xff0c;输出端电流连续。 2&…

Revit中项目特别大如何将项目完整的体现在图纸中?

一、Revit中项目特别大如何将项目完整的体现在图纸中? 遇到项目特别大&#xff0c;在一张图纸是放置不下时&#xff0c;如图1所示&#xff0c;怎样才能将项目完整的体现在图纸当中? 在遇到特别大的项目可能会在图纸中放不下&#xff0c;在这种情况下我们要用拼接线来处理。在…

【JavaEE】SSM框架

文章目录一、Spring1、Spring相关概念1.1 Spring Framework系统架构1.2 核心概念(lOC、lOC容器、Bean、DI)2、入门案例2.1 IOC入门案例2.2 DI入门案例3、lOC相关内容3.1 bean配置3.2 bean实例化3.3 bean的生命周期3.3.1 控制bean生命周期执行的方法3.3.2 bean销毁时机4、DI相关…

AI智能分析在智慧电厂的典型应用

电力供应是整个社会生产、人民生活的基本保证之一。智慧电力作为城市智能化发展的客观需求&#xff0c;是智慧城市的重要基础&#xff0c;也是智慧城市建设的一项重要内容。 智慧能源用最前沿技术淋漓尽致地表达着对未来能源发展趋势的理解与实践。智慧电力将多项创新成果应用于…

【前端】Vue项目:旅游App-(6)city:隐藏TabBar的2种方法

文章目录目标过程与代码方法1&#xff1a;通过路由隐藏方法2&#xff1a;用样式隐藏对方法2封装总代码修改的文件common.cssindex.jscity.vue目标 city页是点击上篇“广州”位置所跳转的页面。此页面要隐藏TabBar。 过程与代码 city页要隐藏TabBar。我们这里有两种隐藏的方法…

【Effective Objective - C】—— 读书笔记(五)

【Effective Objective - C】—— 读书笔记&#xff08;五&#xff09; 内存管理 文章目录【Effective Objective - C】—— 读书笔记&#xff08;五&#xff09;内存管理29.理解引用计数引用计数工作原理属性存取方法中的内存管理自动释放池保留环要点30.以ARC简化引用计数使…

Qt扫盲-QSystemTrayIcon理论总结

QSystemTrayIcon理论总结一、概述二、使用对象三、使用四、常用函数介绍1. 静态函数2. 公共槽函数3. 信号一、概述 现代操作系统通常在桌面上提供一个特殊的区域&#xff0c;称为系统托盘或通知区域&#xff0c;长期运行的应用程序可以在这里显示图标和短消息。什么意思呢&…

【Spring】1. Java对象序列化和反序列化

1. 概念 1.1 序列化 将数据结构或对象转换成二进制字节流的过程 1.2 反序列化 序列化的反过程把二进制字节流恢复为数据结构或对象的过程1.3 序列化的目的&#xff1a; 通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。 2. 为什么要进行序列化&#xff1f;&…

【Linux】Linux开发工具(一)——vim工具

作者&#xff1a;一个喜欢猫咪的的程序员 专栏&#xff1a;《Linux》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 1.什么是vim 1.1什么是vim 1.2vim和vi的区别&#xff1a; 2.vim基础 2.…

字符串的模式匹配

字符串的模式匹配引言应用方法一 暴力匹配算法 (C语言实现)程序实现暴力算法思想暴力算法的时间复杂度方法二 KMP 算法程序实现KMP 算法思想KMP 算法的时间复杂度暴力匹配算法 vs KMP 算法next 数组的训练KMP 算法的优化next 数组 转换成 nextval 数组的思想引言 在我们日常生…

第12章 角色页的修改、添加

1 定义src\components\Users\EditRole.vue <template> <el-dialog width"30%"> <!-- <span>{{propParent}}</span> --> <template #header> <div class"my-header"> <h1 style"margin: 0px; padding: …

快速指南 :ESP-IDF 自定义以太网 PHY 驱动程序

“我想用我最喜欢的芯片开始新的产品设计&#xff0c;但它断货了&#xff01;哦&#xff0c;不&#xff01;我必须设计一个新的 PCB&#xff0c;并重新开发驱动程序&#xff01;”如今&#xff0c;每个设计师都非常清楚这种感觉…好消息是&#xff0c;至少在 ESP-IDF 以太网 PH…