开发规范及一些基础【个人总结 持续更新】

1 maven规范

1.1 pom文件中,尽量使用${}引用,便于修改维护,eg:

<groupId>com.nobug</groupId>
<artifactId>pomonitor</artifactId>
<version>0.0.1</version>
<name>${project.artifactId}</name>

1.2 父工程properties标签中统一管理版本,eg:

<properties>
	<spring-boot.version>2.7.6</spring-boot.version>
    <spring-cloud.version>2021.0.5</spring-cloud.version>
    <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
    ...
</properties>

1.3 全局所有模块都会引入的依赖,在父工程的dependencies标签中声明

<!-- 以下依赖 全局所有的模块都会引入  -->
<dependencies>
    <!--配置文件处理器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <!--配置文件加解密-->
    <dependency>
        <groupId>com.github.ulisesbocchio</groupId>
        <artifactId>jasypt-spring-boot-starter</artifactId>
        <version>${jasypt.version}</version>
    </dependency>
</dependencies>

dependencies中配置的依赖,通常可以使用optional或exclusions排除依赖避免冲突

  • optional标签:A项目设置某个依赖XX optional为true,B项目引入A项目时,XX依赖被排除
  • exclusions标签: 上述场景,也可以是B项目引入A项目时,使用exclusions排除XX依赖

1.4 dependencyManagement标签同一管理版本,子模块使用需要再次引入,但不写版本

<!-- 定义全局jar版本,模块使用需要再次引入但不用写版本号-->
<dependencyManagement>
    <dependencies>
        <!--pig 公共版本定义-->
        <dependency>
            <groupId>com.pig4cloud</groupId>
            <artifactId>pig-common-bom</artifactId>
            <version>${project.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- spring boot 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- spring cloud 依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- spring cloud alibaba 依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

清晰一下scope的作用域

  • import:配合dependencyManagement使用,将目标pom文件下dependencyManagement中的内容整合到当前dependencyManagement中。
    在这里插入图片描述

1.5 profiles标签定义多环境

<!--多环境配置-->
<profiles>
    <profile>
        <id>dev</id>
        <properties>
            <!-- 环境标识,需要与配置文件的名称相对应 -->
            <profiles.active>dev</profiles.active>
        </properties>
        <activation>
            <!-- 默认环境 -->
            <activeByDefault>true</activeByDefault>
        </activation>
        <build>
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <filtering>true</filtering>
                </resource>
            </resources>
        </build>
    </profile>
</profiles>

1.6 springboot配置文件yml中,使用${}取yml中的属性,@@取maven中的属性,eg:@profiles.active@

1.7 待补充

2 lombok技巧

POJO(Plain Ordinary Java Object),普通java对象,存在getter、setter、toString等方法;异常处理;流的关闭等等模板代码影响美观,lombok提供注解简化,源码只需要加注解,编译后的字节码会自动生成这些代码。

常用注解:

  • @Getter/@Setter:自动生成getter/setter方法

  • @ToString:自动重写toString方法,打印所有变量

  • @EqualsAndHashCode():重写equals和hashCode方法,包括所有的非static、非transient的变量

    • 参数callSuper:默认false,为true时,从父类继承的字段一同参与equals和hashCode
  • @NoArgsConstructor:生成无参构造

  • @AllArgsConstructor : 生成包含所有参数的构造,一定要配合@NoArgsConstrcutor使用

  • @RequiredArgsConstructor:生成包含所有final修饰参数的构造

    后两个加在Spring管理的类中,会代替@Autowrited构造注入,eg:

    @Slf4j
    @Configuration
    @AllArgsConstructor
    public class RouterFunctionConfiguration {
        private final HystrixFallbackHandler hystrixFallbackHandler;
        private final ImageCodeHandler imageCodeHandler;
        
    }
    
    // 替代如下代码
    
    @Slf4j
    @Configuration
    public class RouterFunctionConfiguration {
        @Autowired
        private  HystrixFallbackHandler hystrixFallbackHandler;
        @Autowired
        private  ImageCodeHandler imageCodeHandler;
    }
    
  • @Data:相对于@Getter/@Setter+@ToString+@EqualsAndHashCode+@RequiredArgsConstructor

  • @Value:相对于把所有变量加final,然后@Data

    @Data 适合用在 POJO 或 DTO 上,而这个 @Value 注解,则是适合加在不可变类上,而且lombok 的注解 @Value 和另一个 Spring 的注解 @Value 撞名,在 import 时要注意

  • @Slf4j:自动生成log静态常量,可以直接使用log.info()方法

  • @SneakyThrows:帮我们将异常包装成RuntimeException,一路向上抛

    @SneakyThrows
    public void test() {
        throw new Throwable();
    }
    
    //实际上等同于
    public void test() {
        try {
            throw new Throwable();
        } catch (Throwable t) {
            throw Lombok.sneakyThrow(t);
        }
    }
    
    //sneakyThrow源码
    public static RuntimeException sneakyThrow(Throwable t) {
        if (t == null) throw new NullPointerException("t");
        return Lombok.<RuntimeException>sneakyThrow0(t);
    }
    
    private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T {
        throw (T)t;
    }
    
  • UtilityClass:声明工具类,不用程序员加static

  • Cleanup:自动关闭流对象

  • Accessors:控制访问,有三个属性可以设置setter、getter方法

    • fluent :默认false,当值为true时,字段的setter、getter方法前就没有set和get

      eg:new Student().setName(“AA”); 等价于 new Student().name(“AA”);

    • chain:默认false,当值为true时,setter方法返回当前对象

    • prefix:设置一个字符串数组,setter、getter方法不包含指定字符串数组中的内容

      eg:

      @Data
      @Accessors(prefix= {"xx","yy}) 
      public class Student{
          String xxName;
          Integer yyAge;
      }
      //new Student().setName("AA")即可                    
      

3 Assert断言类的使用技巧

org.springframework.util下的Assert类,用于简化参数校验

  • Assert.notNull(Object object, “object is required”) - 对象非空
  • Assert.isTrue(Object object, “object must be true”) - 对象必须为true
  • Assert.notEmpty(Collection collection, “collection must not be empty”) - 集合非空
  • Assert.hasLength(String text, “text must be specified”) - 字符不为null且字符长度不为0
  • Assert.hasText(String text, “text must not be empty”) - text 不为null且必须至少包含一个非空格的字符
  • Assert.isInstanceOf(Class clazz, Object obj, “clazz must be of type [clazz]”) - obj必须能被正确造型成为clazz 指定的类

4 Lambda表达式的使用

lambda表达式本质上是一个匿名函数,允许函数作为方法的参数,可以简化函数式接口(只有一个抽象方法的接口)的使用,写出更为简洁、灵活的代码。

接口中仅有一个抽象方法时,则称为函数式接口。可以在接口上使用@FunctionalInterface修饰,检查接口是否仅有一个抽象方法。

lambda表达式需要函数式接口的支持,eg:

// 匿名函数的形式
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("hello world");
    }
};
r.run();

// lambda表达式
Runnable r1 = ()-> System.out.println("hello lambda");
r1.run();

4.1 四大函数式接口:

  • Consumer<T>:消费型接口

    ​ void accept(T t);

    public void happy(double money,Consumer<Double> con){
        con.accept(money);
    }
    @Test
    public test1(){
        happy(1000,m->System.out.println("消费"+m+"元"))
    }
    
  • Supplier<T>:供给型接口

    ​ T get();

    public List<Integer> getNumList(int num, Supplier<Integer> sup){
        List<Integer> list = new ArrayList<>();
        for(int i=0;i<num;i++){
            list.add(sup.get());
        }
        return list;
    }
    
    @Test
    public void test2(){
        List<Integer> numList = getNumList(5, () -> (int) (Math.random() * 100));
        System.out.println(numList);
    }
    
  • Function<T,R>:函数型接口

    ​ R apply(T t);

    public String strHandler(String str, Function<String,String> fun){
        return fun.apply(str);
    }
    @Test
    public void test3(){
        String s = strHandler("   abcdefg  ", str -> str.trim().toUpperCase());
        System.out.println(s);
    }
    
  • Predicate<T>:断言型接口

    ​ boolean test(T t);

    public List<String> filterStr(List<String> list, Predicate<String> pre){
        List<String> filterList = new ArrayList<>();
        for (String s : list) {
            if(pre.test(s))
                filterList.add(s);
        }
        return filterList;
    }
    @Test
    public void test4(){
        List<String> list = Arrays.asList("Hello","Nobug嘻嘻","lambda","no","Yes");
        List<String> filterStr = filterStr(list, str -> str.length() > 3);
        System.out.println(filterStr);
    }
    

4.2 方法引用

若lambda体中的内容已经有了实现,则可使用“方法引用”。(方法引用中的方法必须和函数式接口中抽象方法的参数列表,返回值一致

语法:

  1. 对象::实例方法名

    @Test
    public void test5(){
        //Consumer<String> con = (str)-> System.out.println(str);
        Consumer<String> con = System.out::println;
        con.accept("abc");
    }
    
  2. 类::静态方法名

    @Test
    public void test6(){
        //Comparator<Integer> com = (x,y)->Integer.compare(x,y);
        Comparator<Integer> com = Integer::compare;
    }
    
  3. 类::实例方法名

    这种方法引用的前提是:第一个参数为实例方法的调用者,第二个参数为实例方法的实参

    @Test
    public void test7(){
        //BiPredicate<String,String> bp = (x,y)->x.equals(y);
        BiPredicate<String,String> bp = String::equals;
    }
    

4.3 构造器引用

ClassName::new

使用抽象方法的形参列表匹配类构造器的参数列表

@Test
public void test8(){
    Supplier<Employee> sup = ()->new Employee();

    //构造器引用
    Supplier<Employee> sup2 = Employee::new;
    Employee emp = sup2.get();
    System.out.println(emp);
}

4.4 数组引用

Type::new

@Test
public void test9(){
    Function<Integer,String[]> fun = (x)->new String[x];
    String[] strs = fun.apply(10);
    System.out.println(strs.length);

    Function<Integer,String[]> fun2 = String[]::new;
    String[] strs2 = fun2.apply(20);
    System.out.println(strs2.length);
}

5 Stream api的使用

java8的新特性:Stream,流。流是数据渠道,用于操作数据源,进行一些处理。流不存储元素,也不会改变数据源,而是创建一个新的流。
流是惰性求值的,多个中间操作组成一条流水线,只有触发终止操作时才会一次性执行整条流水线。

5.1 创建流

  1. Collection接口中的stream()方法:
List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> stream1 = list.stream();
  1. Arrays中的静态方法stream():
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream2 = Arrays.stream(arr);
//stream2.forEach(System.out::println);
  1. Stream类中的of()方法:
Stream<String> stream3 = Stream.of("aa", "bb", "cc");
  1. 无限流:
// 迭代
Stream<Integer> stream4 = Stream.iterate(1, x -> x + 3);
stream4.limit(5).forEach(System.out::println);

// 生成
Stream.generate(()->(int)(Math.random()*100)).limit(5).forEach(System.out::println);

5.2 中间操作

  • filter(Predicate pre)——根据断言型接口,返回符合条件的元素
  • limit(int n)——截断流,使流中元素最大不超n
  • skip(int n)——跳过n个元素
  • distinct()——去重,根据hashCode()和equals()去重
  • map(Function fun)——根据函数型接口,对每个元素调用apply()处理,返回结果。如果每个元素处理后返回的是一个流,那么map完成后,会出现流嵌套。
  • flatMap(Function fun)——与map类似,唯一不同时如果每个元素处理后返回一个流,经过flatmap后最终是一个流。
  • sorted()——自然排序(Comparable)
  • sorted()——定制排序(Comparator)

5.3 终止操作

  • allMatch()——检查是否匹配所有元素

  • anyMatch()——检查是否匹配至少一个元素

  • noneMatch()——检查是否没有匹配所有元素

    以下使用Optional<T>容器接收,因为返回的元素可能为null

  • findFirst()——返回第一个元素

  • findAny()——返回当前流中任意一个元素

  • max——返回流中最大值

  • min——返回流中最小值

  • count——返回当前流中个数

  • reduce——规约

    Integer sum = list.stream().reduce(0,(x,y)->x+y);
    
  • map和reduce的连接通常称为map-reduce模式

    Optional<Double> op = employees.stream().map(Employee::getSalary).reduce(Double::sum);
    System.out.println(op.get());
    
  • collect(Collector coll)——收集

    List<String> list = employees.stream().map(Employee::getName).collect(Collectors.toList());
    
    HashSet<String> set = employees.stream().map(Employee::getName).collect(Collectors.toCollection(HashSet::new));
    

6 接口中的默认方法和静态方法

  • 接口默认方法的“类优先”原则:
    若子类实现一个接口,同时继承父类。接口(默认方法)和父类中存在同名方法时,优先选择父类的方法;

  • 接口和父类存在同名变量:

    1. 接口中的变量——接口.变量名使用
    2. 父类中的变量——在子类创建同名变量,在构造中使用super.变量名为其赋值。
  • 接口默认方法冲突:必须重写

    1. 若一个类同时实现多个接口,这些接口中存在同名默认方法,则该类必须重写该方法
    2. 若一个接口同时继承多个父接口,父接口中存在同名默认方法,则该接口必须重写该方法

7 泛型

7.1 泛型概述

为什么使用泛型?(两个好处:1.消除类型转换;2.类型安全)

1.在没有泛型之前,java中集合元素都是Object类型,因此可以往集合内加入任意类型的数据,但是使用时需要强转,非常容易造成ClassCastException异常。

2.而泛型可以提供编译时类型检测,在编译时检测所添加元素与指定的泛型是否匹配,将运行时可能出现的类型转换异常提前到编译时出现。(泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数)

ArrayList<Number> list = new ArrayList<>();
//list中可以添加Number的子类数据,如Integer,Double等

7.2 泛型类

//1.定义 public class 类名<泛型标识,泛型标识,...>
/**
 * 泛型类的定义
 * @param <T>  泛型标识,创建对象时指定具体的数据类型
 */
public class Generic<T> {
    private T key;

    public Generic(T key) {
        this.key = key;
    }

    public T getKey() {
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }
}

//2.使用
Generic<String> ge = new Generic<String>("a");
Generic<String> ge = new Generic<>("a");  //第二个尖括号可以不写,根据上下文自动推断

1.泛型类没有指定类型时,按Object类型处理

2.泛型类不支持基本数据类型

3.同一个泛型类指定不同的数据类型所创建的对象,实际上是相同的类型(类型都是这个泛型类)

Generic<String> stringGeneric = new Generic<>("a");
Generic<Integer> integerGeneric = new Generic<>(100);
System.out.println("stringGeneric的类型:"+stringGeneric.getClass());
System.out.println("integerGeneric的类型:"+integerGeneric.getClass());
System.out.println(stringGeneric.getClass()==integerGeneric.getClass());

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rNEvYvVK-1681629592798)(java基础.assets/image-20220620125349621.png)]

7.3 泛型类派生子类

1.子类是泛型类,则子类的泛型标识必须与父类(泛型类)一致

//泛型标识一致,父类才能从子类中获取数据类型
public class Child<T> extends Parent<T>{}
//即使是子类泛型扩展,子类中的泛型标识也必须包含父类的泛型标识
public class Child<T,K,V> extends Parent<T>{}

2.子类不是泛型,父类是泛型,子类定义时必须明确父类的泛型,否则父类泛型为Object

public class Child extends Parent<String>{}

7.4 泛型接口

//1.定义
/**
 * 泛型接口
 * @param <T>
 */
public interface Generator<T> {
    T getKey();
}
//2.使用(同泛型类的派生)
// 2.1泛型接口的实现类不是泛型类,需要明确泛型接口的数据类型
public class GeneratorImpl1 implements Generator<String>{
    @Override
    public String getKey(){
        return "hello world";
    }
}
// 2.2泛型接口的实现类是泛型类,实现类与接口的泛型标识需一致(实现类泛型扩展时必须包含接口的泛型标识)
public class GeneratorImpl<T,E> implements Generator<T> {
    private T key;
    private E value;
    @Override
    public T getKey() {
        return key;
    }

    public GeneratorImpl(T key, E value) {
        this.key = key;
        this.value = value;
    }

    public E getValue() {
        return value;
    }
}

7.5 泛型方法

修饰符 <T,E,…> 返回值 方法名(形参列表){

​ 方法体

}

public static <T> T genericMethod(T key){
    System.out.println("泛型类型:"+key.getClass().getSimpleName()+",key:"+key);
    return key;
}

1.泛型方法可以是静态的,泛型方法的类型是调用方法时确定的;泛型类的成员方法的类型是类创建对象时确定,因此不能是static的。

2.泛型方法可变参数(本质上是基于数组)

public static <E> void genericMethod2(E... e){
    for(int i=0;i<e.length;i++){
        System.out.println(e[i]);
    }
}

7.6 类型通配符

为什么使用类型通配符?当一个方法的参数为一个泛型类对象时,举个例子:形参为List<Number>时,传递参数时只能传递List<Number>的对象,List<Integer>就不行,即便Integer是Number的子类。而当形参定为List<?>,实参可为List<任意类型>的对象。

7.6.1 类型通配符上限

List<? extends Number>,即实参可传递List<Number>及List<Number的子类>对象

采用类型通配符上限匹配的集合,不能添加元素,因为并不确定集合数据类型

取元素时用上限类型来接收(类型多态)

7.6.2 类型通配符下限

List<? super Integer>,即实参可传递List<Integer>及List<Integer的父类>对象

采用类型通配符下限匹配的集合,可以添加下限类型即其子类的数据,因为匹配的集合数据类型为下限类型及其父类

取元素用Object接收,因为所有类型的父类都是Object

JDK中TreeSet构造器中传入的比较器,采用的就是类型通配符下限

7.7.类型擦除

泛型信息仅存在与编译阶段,在进入JVM前泛型信息会被擦除,称之为类型擦除。

为什么要泛型擦除?

可以很好地兼容旧版本
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.8 泛型数组

1.可以声明带泛型的数组引用,不能直接创建带泛型的数组对象

2.可以通过java.lang.reflect.Array下的newInstance(Class<T>,int)创建T[]数组

//泛型数组创建方法一
ArrayList<String>[] arrayLists = new ArrayList[3];
ArrayList<String> list = new ArrayList<>();
list.add("你好");
arrayLists[0] = list;
//泛型数组创建方法二
import java.lang.reflect.Array;

public class GenericArray<T> {
    private T[] arr;

    GenericArray(Class<T> cls,int len){
        arr = (T[])Array.newInstance(cls, len);
    }

    public void put(int idx,T item){
        arr[idx] = item;
    }

    public T get(int idx){
        return arr[idx];
    }

    public T[] getArr(){
        return arr;
    }
}

//使用
GenericArray<String> genericArray = new GenericArray<>(String.class,3);
genericArray.put(0,"你好");
genericArray.put(1,"泛型");
genericArray.put(2,"数组");
System.out.println(genericArray.get(0));//你好
System.out.println(Arrays.toString(genericArray.getArr()));//[你好, 泛型, 数组]

8 SPI机制

SPI全称Service Provider Interface,是java提供的一套API,可以用来扩展或替换组件。

在这里插入图片描述

实际上是一种“基于接口编程+策略模式+配置文件”组合实现的动态加载机制,在多模块设计中尤其重要。

springboot项目默认扫描XXXApplication所在包及其子包下的组件,若想引入其他包下的注册bean,就得使用@import注解来导入。SPI机制在resources/META-INF/下创建相应的配置文件,源码框架就可以根据配置文件找到类,实例化后交由容器管理。

SPI机制的实现方式

  • spring.factories+spring-autoconfigure-metadata.properties:

    • spring-autoconfigure-metadata.properties文件存储的是类的过滤规则,内容(类全限定名.条件Condition=值)

    • spring.factories存储待自动装配的候选类

      org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
        com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
        com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
      
      

    自动装配的原理:

    首先springboot项目中XXXApplication上都一定有一个注解:@SpringbootApplication,它其实是三个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan的组合注解,其中的@EnableAutoConfiguration注解即开启自动装配,注册项目包外的bean;而@ComponentScan只能扫描注册项目包内的bean。

    @EnableAutoConfiguration注解使用@Import注解,将AutoConfigurationImportSelector注入到IOC容器中,AutoConfigurationImportSelector类即自动装配的入口,**selectImports()**方法会在Spring容器生命周期的invokeBeanFactoryPostProcessors 阶段(bean工厂后置处理器)调用。

    该方法中主要有两步:

    1、AutoConfigurationMetadataLoader类的静态方法loadMeatdata会加载spring-autoconfigure-metadata.properties中的过滤信息

    2、getAutoConfigurationEntry( )方法会获取spring.factories中配置的键值对,实际是调用SpringFactoriesLoader的静态方法loadFactoryNames,逻辑就是扫描pom文件中引入的其他starter中的spring.factories文件,返回配置中的类名

    实例化后注入到IOC容器

  • services/下创建以“接口全限定名”命名的文件,内容为实现类的全限定名

    核心:ServiceLoader中的load方法,读取配置文件,迭代遍历,Class.forName()加载类对象,newInstance()方法完成实例化。

    缺点:实现类全部实例化,有可能浪费; ServiceLoader线程不安全

  • spring/下以“org.springframework.boot.autoconfigure.AutoConfiguration.imports”命名的文件,内容为import类的全限定名

9 proxyBeanMethods

@Configuration注解中proxyBeanMethods的取值对应两种模式
 - full:全模式,注入该容器的组件都是单例的,采用CGLIB代理。当容器中bean实例存在依赖关系时,采用
 - lite:轻量级模式,注入该容器的组件是多例的,采用JDK动态代理,当容器中bean实例不存在依赖关系时,
         采用lite模式加快springboot的启动速度和性能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值