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体中的内容已经有了实现,则可使用“方法引用”。(方法引用中的方法必须和函数式接口中抽象方法的参数列表,返回值一致)
语法:
-
对象::实例方法名
@Test public void test5(){ //Consumer<String> con = (str)-> System.out.println(str); Consumer<String> con = System.out::println; con.accept("abc"); }
-
类::静态方法名
@Test public void test6(){ //Comparator<Integer> com = (x,y)->Integer.compare(x,y); Comparator<Integer> com = Integer::compare; }
-
类::实例方法名
这种方法引用的前提是:第一个参数为实例方法的调用者,第二个参数为实例方法的实参
@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 创建流
- Collection接口中的stream()方法:
List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> stream1 = list.stream();
- Arrays中的静态方法stream():
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream2 = Arrays.stream(arr);
//stream2.forEach(System.out::println);
- Stream类中的of()方法:
Stream<String> stream3 = Stream.of("aa", "bb", "cc");
- 无限流:
// 迭代
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 接口中的默认方法和静态方法
-
接口默认方法的“类优先”原则:
若子类实现一个接口,同时继承父类。接口(默认方法)和父类中存在同名方法时,优先选择父类的方法; -
接口和父类存在同名变量:
- 接口中的变量——接口.变量名使用
- 父类中的变量——在子类创建同名变量,在构造中使用super.变量名为其赋值。
-
接口默认方法冲突:必须重写
- 若一个类同时实现多个接口,这些接口中存在同名默认方法,则该类必须重写该方法
- 若一个接口同时继承多个父接口,父接口中存在同名默认方法,则该接口必须重写该方法
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的启动速度和性能