首页
归档
留言
广告合作
友链
美女主播
Search
1
博瑞GE车机升级/降级
5,146 阅读
2
Mac打印机设置黑白打印
4,517 阅读
3
修改elementUI中el-table树形结构图标
4,516 阅读
4
Mac客户端添加腾讯企业邮箱方法
4,351 阅读
5
intelliJ Idea 2022.2.X破解
4,060 阅读
Java
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
登录
/
注册
Search
标签搜索
Spring Boot
Java
Spring Cloud
Mac
mybatis
WordPress
Nacos
Spring Cloud Alibaba
Mybatis-Plus
jQuery
Java Script
asp.net
微信小程序
Sentinel
UniApp
MySQL
asp.net core
IntelliJ IDEA
Jpa
树莓派
Laughing
累计撰写
570
篇文章
累计收到
1,424
条评论
首页
栏目
Java
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
页面
归档
留言
广告合作
友链
美女主播
搜索到
211
篇与
的结果
2021-11-14
java类型转换的一些问题及处理
最近在项目上被类型转换坑了不少。特以此文做一下记录。问题出在哪以前在项目开发的时候,在数据访问层,基本上是使用MyBatis,偶尔使用JPA,不管哪种方式,我们数据访问都是建立在实体之上的(极少数情况下也会使用Map,但是这种情况是比较特殊的。目前公司的开发平台,数据访问层,统一通过平台封装的BQL进行,问题就出现在这个封装的BQL上,它返回的数据类型是List<Map<String,Object>>类型,这个东西看似灵活(因为是Map,另外还是Object的泛型),既然返回的是Object类型,那就涉及到类型转换的问题。这种Object的弱类型,也给我们创建实体制作了障碍,比如我们一个实体可能有Integer类型、String类型等等。但是,我们总不能把实体全部创建层Object类型的。进一步的问题截止到目前,我们发现的第一个问题就是Object需要转换成具体的类型。紧接着,我们面临着第二个问题,就是我们通过BQL这个东西建表的时候,我们选择的比如是Integer类型(整型),我们会发现,在postgre数据库中,创建的类型确实是Integer,但是在Oracle中创建的是Number类型,然后我们在用BQL取数的时候,在postgre数据库中,取出的Object是Integer,但是在Oracle中取出的Object确实BigDecimal类型,这就让人比较懵逼了。我在创建实体的时候,到底是用Integer还是用BigDecimal呢。如何定义类型通过上面分析,我们可以确定的是,我们需要的是Integer而不是BigDecimal。所以,我们在创建实体时,创建的是一个Integer类型的。如何进行类型转换以上,便引出了我们如何进行类型转换的话题。在Java中如何进行类型转换呢,我们以Integer类型为例,常见的方式可能就以下三种。(Integer)obj:将obj转换成Integer类型,这种obj可以是Object类型。Integer.parseInt(obj):将字符串类型的obj转成对应的数值。Integer.valueOf(obj):将字符串类型的obj转成对应的数值。那么这三种方法有啥区别呢,我们以以下代码为例Object str123 = new BigDecimal("123"); int int123 = (int) str123; int parseInt = Integer.parseInt(str123.toString()); Integer valueOf = Integer.valueOf(str123.toString()); System.out.println(int123); System.out.println(parseInt); System.out.println(valueOf);以上代码,idea不会给出任何的错误提示但是我们运行时,会发现int int123 = (int) str123;这行代码会报错因为str123实际是BigDecimal类型所以(Integer)报错了。区别一:(Integer)只能转换相同的类型,否则会报错。我们跳转代码,可以查看Integer.parseInt()和Integer.valueOf()通过上面截图,我们可以看到Integer.parseInt()和Integer.valueOf()的区别。Integer.parseInt()返回的是基本类型int,Integer.valueOf()返回的是对象类型Integer。区别二:Integer.parseInt()返回的是基本类型int,Integer.valueOf()返回的是对象类型Integer。
2021年11月14日
852 阅读
0 评论
1 点赞
2021-11-13
java object转成Map
/** * object转成Map * * @param obj 对象 * @return Map */ private Map<String, Object> obj2Map(Object obj) { Map<String, Object> map = new HashMap<>(); // System.out.println(obj.getClass()); // 获取f对象对应类中的所有属性域 Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { String varName = field.getName(); varName = varName.toUpperCase();//将key置为小写,默认为对象的属性 try { // 获取原来的访问控制权限 boolean accessFlag = field.isAccessible(); // 修改访问控制权限 field.setAccessible(true); // 获取在对象f中属性fields[i]对应的对象中的变量 Object o = field.get(obj); if (o != null) map.put(varName, o.toString()); // System.out.println("传入的对象中包含一个如下的变量:" + varName + " = " + o); // 恢复访问控制权限 field.setAccessible(accessFlag); } catch (IllegalArgumentException | IllegalAccessException ex) { ex.printStackTrace(); } } return map; }
2021年11月13日
1,076 阅读
0 评论
0 点赞
2021-10-23
java PreDestroy注解使用
被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前。比如我们可以通过此注解实现:程序停止时做一些清理工作。 @PreDestroy public void destroy(){ log.info("PreDestroy"); }当我们停止程序,会输出以下信息
2021年10月23日
899 阅读
0 评论
0 点赞
2021-10-23
Spring PostConstruct注解的使用
一、基本介绍@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。@PostConstruct在构造函数之后执行,init()方法之前执行。通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注解的方法)二、用途@PostConstruct主要用于处理一些初始化工作。比如下面代码@RestController @RequestMapping(value = "book") @Slf4j public class BookController { @Resource private IBookService bookService; /** * 构造函数 */ public BookController() { log.info("构造函数,此时bookService :" + bookService); } @PostConstruct public void init() { log.info("PostConstruct,此时bookService :" + bookService); } @GetMapping(value = "search") public List<Book> search() { return bookService.search(); } }看下控制台可以看到,构造函数里面,Bean还没有初始化,@PostConstruct里面已经完成初始化,所以,我们可以通过@PostConstruct完成一些初始化后的操作。
2021年10月23日
1,156 阅读
0 评论
0 点赞
2021-10-22
java泛型反序列化
其实不管前后端交互还有API接口,我个人更倾向于使用实体(DTO、VO)啥的,但是,有时候也不得不用Map等进行数据交互。一般而言,前后端或者接口之间交互都是通过JSON进行的,而我们在使用Map的时候,一般都是使用的泛型类,而不是使用原始类型,比如Map<String,string>,那么我们来看下下面一段代码。Map<String,String> bookMap = new HashMap<>(); bookMap.put("author","张三"); bookMap.put("name","山海经"); String bookMapString = JSONObject.toJSONString(bookMap); log.info(bookMapString);我们创建一个Map<String,String>,并将其序列化成字符串,如果我们放过来,想将字符串在反序列化成Map,我们首先想到的可能就像下面这样。Map bookMapNew = JSONObject.parseObject(bookMapString); log.info(String.valueOf(bookMapNew));程序有错吗?当然没错,但是优雅嘛,肯定是不优雅。其实这个时候,idea会给我们一个提示Raw use of parameterized class 'Map',大概意思就是说这个类要使用泛型,如果不使用泛型,我们其实丢失了类型的安全性。这个时候,我们就想到了parseObject()的重载方法,通过第二个参数,传递进去类型,如果我们是普通的类,传递一个class进去是没问题的。比如我们改写一下上面反序列化的方法Map<String,String> bookMapNew = JSONObject.parseObject(bookMapString,Map.class);这个时候,idea又会给我们一个提示大意就是类型转换没有校验。那么有没有更加完美的方法呢,有的,那就是TypeReference类,我们继续改造代码如下Map<String,String> bookMapNew = JSONObject.parseObject(bookMapString,new TypeReference<Map<String,String>>(){});这回idea终于没有提示了运行一下,结果也是正确的
2021年10月22日
1,789 阅读
0 评论
1 点赞
2021-08-30
一文撸清楚Java中的枚举enum
Java Enum类型的语法结构尽管和java类的语法不一样,应该说差别比较大。但是经过编译器编译之后产生的是一个class文件。该class文件经过反编译可以看到实际上是生成了一个类,该类继承了java.lang.Enum<E>。定义一个枚举定义一个枚举,最简单的方式可能向下面这样public enum Size{ SMALL, MEDIUM, LARGE, EXTRA_LARGE };枚举的构造函数枚举可以定义构造函数,但是必须是private类型。public enum DishType { Fish("FISH"), Chiken("CHIKEN"), Meat("MEAT"); private String type; private DishType(String type) { this.type = type; } public String getType() { return type; } public void setType(String type) { this.type = type; } }你可以向上面这样添加private修饰符或者啥都不添加,但是不能添加public或protect。重写方法与类一样,可以重写枚举的方法,比如toString()方法。public enum DishType { Fish("FISH"), Chiken("CHIKEN"), Meat("MEAT"); private String type; private DishType(String type) { this.type = type; } public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public String toString() { return type; } }获取枚举值可以通过.语法获取枚举值。如果想获取所有的值,可以通过values()方法 for(DishType dishType:DishType.values()){ System.out.println(dishType); }获取原始值可以通过ordinal()方法获取枚举的原始值,也就是1、2、3System.out.println(fish.ordinal());控制台输出0,因为Fish是我们在枚举值第一个定义的,从0开始。反向获取枚举可以通过valueOf()反向获取枚举值DishType.valueOf("Fish");枚举中定义方法比如在枚举中定义一个sayHello方法public void sayHello() { System.out.println("hello,i am " + type); }然后调用 DishType fish = DishType.valueOf("Fish"); fish.sayHello();枚举中的匿名内部类枚举实例后有花括号时,该枚举实例是枚举类匿名内部之类定义性别枚举类package com.company.enumpackage; public enum Gender { //枚举值调用对应构造器创建 MALE("男") { public void info() { System.out.println("这个枚举代表男"); } }, FEMALE("女") { public void info() { System.out.println("这个枚举代表女"); } }, UNKNOWN("UNKNOWN"); private final String name; //枚举类的构造器只能使用private修饰 private Gender(String name) { this.name = name; } public String getName() { return this.name; } public void info() { System.out.println("这是第一个用于定义性别的枚举类"); } }调用Gender male = Gender.MALE; Gender female = Gender.FEMALE; Gender unKnown = Gender.UNKNOWN; male.info(); female.info(); unKnown.info();
2021年08月30日
865 阅读
0 评论
24 点赞
2021-08-29
java8 Optional<T>简介
Optional<T>是Java8增加的一个用于处理NullPointException异常的类。Optional<T>是一个容器类,代表一个值存在或不存在。如果用过Spring Boot Data Jpa,会发现里面好多返回值都是Optional<T>类型的。下面我们介绍一下Optional<T>类的常见使用方法。作为演示,我们创建了一个Dish类,代码如下:public class Dish { /** * 构造函数 * @param name 名称 * @param isVegetarian 是否蔬菜 */ public Dish(String name, boolean isVegetarian) { this.name = name; this.isVegetarian = isVegetarian; } /** * 名称 */ private String name; /** * 是否蔬菜 */ private boolean isVegetarian; @Override public String toString(){ return name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isVegetarian() { return isVegetarian; } public void setVegetarian(boolean isVegetarian) { this.isVegetarian = isVegetarian; } } 创建一个Optional<T>实例创建Optional<T>实例,有两个方法,分别是of、ofNullable,ofNullable与是of的区别在于,如果传递的参数是null,那么会创建一个空的(empty)Optional<T>实例。 Optional<Dish> dishOptionalofNullable = Optional.ofNullable(null); dishOptionalofNullable.ifPresent(System.out::println); System.out.println("准备输出of方法的值"); Optional<Dish> dishOptionalof = Optional.of(null); dishOptionalof.ifPresent(System.out::println);运行代码,我们可以看到如下输出我们可以看到ofNullable创建的实例,即使传递的参数是null调用实例方法时,系统也会抛出异常,而of传递null值时,创建的Optional<T>实例也是null。判断是否为空通过isPresent()方法可以判断Optional<T>包含的类是否是null。返回true代表不是null,返回false代表是null。 Optional<Dish> emptyOptional = Optional.empty(); if (emptyOptional.isPresent()) { System.out.println(emptyOptional.get()); } else { System.out.println("empty optional"); }还有一个方法是ifPresent(Consumer<? super T> action)可以传递一个Consumer接口,当类不是null。 Optional<Dish> emptyOptional = Optional.empty(); emptyOptional.ifPresent(System.out::println);还有一个方法 ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction),当类不是null时,调用action接口,否则调用emptyAction接口。 List<Dish> menu = new ArrayList<>(); Dish fish = new Dish("鱼", false); Dish tomato = new Dish("西红柿", true); menu.add(fish); menu.add(tomato); menu.stream().filter(Dish::isVegetarian).findAny().ifPresentOrElse(System.out::println, () -> { System.out.println("没找到呢"); });获取值获取值比较简单,通过get()方法即可。 Optional<Dish> fishOptional = Optional.ofNullable(new Dish("鱼", false)); if(fishOptional.isPresent()){ System.out.println(fishOptional.get()); }通过orElse(T other)可以设置默认值。 Optional<Dish> fishOptional = Optional.ofNullable(new Dish("鱼", false)); Dish defaultDish = new Dish("default", false); if (fishOptional.isPresent()) { System.out.println(fishOptional.orElse(defaultDish)); } Optional<Dish> emptyOptional = Optional.empty(); System.out.println(emptyOptional.orElse(defaultDish));上面emptyOptional是空的,那么我们可以返回一个默认的Dish实例,下面看下控制台输出。orElse(T other)还有一些变种方法,大家可以试一下。
2021年08月29日
989 阅读
0 评论
0 点赞
2021-08-25
java中对象和Map的相互转换
实体对象转map集合private static Map<String, String> obj2Map(Object obj) { Map<String, String> map = new HashMap<String, String>(); // System.out.println(obj.getClass()); // 获取f对象对应类中的所有属性域 Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { String varName = field.getName(); varName = varName.toLowerCase();//将key置为小写,默认为对象的属性 try { // 获取原来的访问控制权限 boolean accessFlag = field.isAccessible(); // 修改访问控制权限 field.setAccessible(true); // 获取在对象f中属性fields[i]对应的对象中的变量 Object o = field.get(obj); if (o != null) map.put(varName, o.toString()); // System.out.println("传入的对象中包含一个如下的变量:" + varName + " = " + o); // 恢复访问控制权限 field.setAccessible(accessFlag); } catch (IllegalArgumentException | IllegalAccessException ex) { ex.printStackTrace(); } } return map; }将map集合转化成实体对象利用反射实现/** * Map转成实体对象 * @param map map实体对象包含属性 * @param clazz 实体对象类型 * @return */ public static <T> T map2Object(Map<String, Object> map, Class<T> clazz) { if (map == null) { return null; } T obj = null; try { obj = clazz.newInstance(); Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { int mod = field.getModifiers(); if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) { continue; } field.setAccessible(true); field.set(obj, map.get(field.getName())); } } catch (Exception e) { e.printStackTrace(); } return obj; }
2021年08月25日
884 阅读
0 评论
0 点赞
2021-08-21
java8谓词复合和函数复合
谓词及功能复合,就是在通过组合已有的谓词或功能,形成更加复杂的谓词或功能。谓词复合谓词(Predicate)接口包含三个方法:negate、and和or,通过这三个方法,我们可以在已有谓词的基础上,组合形成更加复杂的谓词。negate代表非,and代表和,or代表或。举个例子,我们有一个苹果(Apple)类,包含颜色(color)和重量(weight)两个属性。class Apple { public Apple(String color, double weight) { this.color = color; this.weight = weight; } private String color; private double weight; @Override public String toString() { return "color:" + color + "---------" + "weight:" + weight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } } 然后我们创建一个苹果的列表,存储几条测试数据。List<Apple> appleList = new ArrayList<>(); appleList.add(new Apple("red", 2)); appleList.add(new Apple("red", 6)); appleList.add(new Apple("green", 2)); appleList.add(new Apple("green", 6));negate测试我们创建一个查找红色苹果的谓词Predicate<Apple> redApple = apple -> "red".equals(apple.getColor());然后创建一个查找不是红色苹果的谓词List<Apple> notRedAppleList = appleList.stream().filter(notRedApple).collect(Collectors.toList()); for (Apple apple : notRedAppleList) { System.out.println(apple); }and测试我们首先创建一个查找重量大于5的谓词Predicate<Apple> weightThan5Apple = apple -> apple.getWeight() > 5;然后组合红苹果及重量大于5的两个谓词Predicate<Apple> redAndWeightThan5Apple = redApple.and(weightThan5Apple);测试 List<Apple> redAndWeightThan5AppleList = appleList.stream().filter(redAndWeightThan5Apple).collect(Collectors.toList()); for (Apple apple : redAndWeightThan5AppleList) { System.out.println(apple); }or测试还是利用刚才的谓词,这次我们测试重量大于5或者颜色是红色的苹果Predicate<Apple> redOrWeightThan5Apple = redApple.or(weightThan5Apple);测试 List<Apple> redOrWeightThan5AppleList = appleList.stream().filter(redOrWeightThan5Apple).collect(Collectors.toList()); for (Apple apple : redOrWeightThan5AppleList) { System.out.println(apple); }函数复合函数复合跟谓词复合类似,函数复合提供了两个接口方法:andThen和compose,通过字面含义我们可以知道andThen代表先执行第一个函数,然后在执行第二个函数,compose与之相反,先执行第二个函数,在执行第一个函数。 Function<Integer, Integer> funcPlus = x -> x + 1; Function<Integer, Integer> funcMul = y -> y * 2; Function<Integer, Integer> funcAndThen = funcPlus.andThen(funcMul); Function<Integer, Integer> funcCompose = funcPlus.compose(funcMul); System.out.println("andThen:" + funcAndThen.apply(1)); System.out.println("compose:" + funcCompose.apply(1));通过测试,我们发现andThen先执行了第一个函数,也就是1+1=2,然后执行2*2=4,所以最终结果就是4。compose限制性1*2=2,然后执行1+2=3,所以结果是3。
2021年08月21日
974 阅读
0 评论
0 点赞
2021-08-21
java8函数式接口Predicate、Consumer、Function
java8在java.util.function包中给我们预留了几个泛型函数式接口。Predicate、Consumer、Function。Predicate接口里面有一个test方法,接口一个泛型T对象并返回一个boolean值。 static <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> result = new ArrayList<>(); for (T t : list) { if (predicate.test(t)) { result.add(t); } } return result; }传递一个String类型的列表,过滤掉其中为空的元素。 List<String> list = new ArrayList<>(); list.add(""); list.add("one"); List<String> result = filter(list,s->s.length()>0); for(String s:result){ System.out.println(s); }Consumer接口里面定义了一个accept方法,接受一个泛型T对象,没有返回值。 static <T> void consumer(List<T> list, Consumer<T> consumer) { for (T t : list) { consumer.accept(t); } }输出列表元素。 consumer(list, System.out::println);Function接口里面定义了一个apply方法,接受一个泛型T,并返回泛型R。static <T,R> List<R> func(List<T> list, Function<T,R> fun){ List<R> result = new ArrayList<>(); for(T t:list){ result.add(fun.apply(t)); } return result; }获取列表每个元素的长度。List<Integer> lengthList=func(list, String::length); for(Integer integer:lengthList){ System.out.println(integer); }
2021年08月21日
1,034 阅读
0 评论
2 点赞
2021-07-31
JVM故障诊断与性能优化之常用虚拟机参数
类加载子系统负责从文件系统或网络中加载Class信息。方法区存放类加载子系统加载的Class信息及运行时常量池信息,包括字符串的字面量和数字常量Java堆在虚拟机启动时建立,是Java程序最主要的内存工作区域,几乎所有的Java对象实例都放于Java堆中。堆空间是线程共享的。直接内存直接内存是在Java堆外直接向系统申请的内存空间,通常,访问直接内存的速度会优于Java堆。因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存。直接内存在Java堆外,因此它的大小不会直接首先与Xmx指定的最大堆大小。垃圾回收系统垃圾回收器可以堆方法区、Java堆和直接内存进行回收。Java栈每个一Java虚拟机线程都有一个私有的Java栈,一个线程的Java栈在线程创建的时候被创建,Java栈中保存着局部变量、方法参数,同时和Java方法的调用、返回密切相关。本地方法栈与Java栈类似,最大的不同在于Java栈用于Java方法的调用,而本地方法栈用于本地方法的调用。作为Java虚拟机的重要扩展,Java虚拟机允许Java直接调用本地的方法(通常使用C编写)PC寄存器寄存器也是每个线程私有的空间,Java虚拟机会为每一个Java线程创建PC寄存器,在任意时刻,一个Java线程总是在执行一个方法,这个正在被执行的方法称为当前方法。如果当前方法不是本地方法,PC寄存器就会只想当前正在被执行的指令,如果当前方法是本地方法,那么PC寄存器的值就是undefined执行引擎负责执行虚拟机的字节码。常见Java虚拟机参数要诊断虚拟机,我们就要学习如何对Java虚拟机进行最基本的配置和跟踪。跟踪垃圾回收可以通过-XX:+PrintGC参数启动Java虚拟机,只要遇到GC,就会打印日志。System.out.println("##########准备申请1m内存#########"); byte[] bytes = new byte[1 * 1024 * 1024]; System.gc();GC和Full GC是垃圾回收的停顿类型,而不是区分是新生代还是年老代,如果有Full说明发生了Stop-The-World。如果是调用 System.gc()触发的,那么将显示的是Full GC (System) 4878K->1576K(249344K)表示GC 前该区域已使用容量 -> GC 后该区域已使用容量 (该区域内存总容量)0.0013118 secs表示GC所用时间,单位为秒。可以通过-XX:+PrintGCDetails参数来输出更加详细的参数。PSYoungGen:代表新生代。total 75776K, used 1951K代表新生代大小及已使用大小。[0x000000076b800000, 0x0000000770c80000, 0x00000007c0000000)代表新生代的下界、当前上界和上届。eden、from、to代表新生代的三个区,当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数-XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。ParOldGen代表老年代
2021年07月31日
573 阅读
0 评论
0 点赞
2021-07-29
Spring Cloud集成Gateway
一、SpringCloud Gateway 简介SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 2.0之前的非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。二、SpringCloud Gateway 特征SpringCloud官方,对SpringCloud Gateway 特征介绍如下:基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0集成 Hystrix 断路器集成 Spring Cloud DiscoveryClientPredicates 和 Filters 作用于特定路由,易于编写的 Predicates 和 Filters具备一些网关的高级功能:动态路由、限流、路径重写从以上的特征来说,和Zuul的特征差别不大。SpringCloud Gateway和Zuul主要的区别,还是在底层的通信框架上。简单说明一下上文中的三个术语:Filter(过滤器):和Zuul的过滤器在概念上类似,可以使用它拦截和修改请求,并且对上游的响应,进行二次处理。过滤器为org.springframework.cloud.gateway.filter.GatewayFilter类的实例。Route(路由):网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个ID,一个目标URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。Predicate(断言):这是一个 Java 8 的Predicate,可以使用它来匹配来自HTTP请求的任何内容,例如headers或参数。断言的输入类型是一个 ServerWebExchange。三、Spring Cloud Gateway的处理流程客户端向 Spring Cloud Gateway 发出请求。然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler。Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(pre)或之后(post)执行业务逻辑。四、路由匹配规则对于这种配置类的,我们一般情况都会放到配置文件中去配置。Spring Cloud Gateway 的功能很强大,我们仅仅通过 Predicates 的设计就可以看出来,前面我们只是使用了 predicates 进行了简单的条件匹配,其实 Spring Cloud Gataway 帮我们内置了很多 Predicates 功能。Spring Cloud Gateway 是通过 Spring WebFlux 的 HandlerMapping 做为底层支持来匹配到转发路由,Spring Cloud Gateway 内置了很多 Predicates 工厂,这些 Predicates 工厂通过不同的 HTTP 请求参数来匹配,多个 Predicates 工厂可以组合使用。代码Demo父工程依赖<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.12.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR12</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <!-- <version>2.1.0.RELEASE</version>--> <version>2.2.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> </dependencies> </dependencyManagement>当前模块依赖 <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>配置文件server: port: 9527 spring: application: name: cloud-gateway-service cloud: nacos: discovery: server-addr: 192.168.120.180:8848 gateway: discovery: locator: enabled: true routes: - id: payment-routh uri: lb://nacos-provider-payment # 服务名称 predicates: - Path=/app/** - Header=username,laughing可以看到gateway的路由匹配规格主要是包含id、uri、predicates。id随意指定,唯一即可。uri代表配置的路径,可以匹配URL或者服务名称,如果是服务名称,以lb:打头。predicates代表断言,条件为true时才能进入对应的路径。说白了 Predicate 就是为了实现一组匹配规则,方便让请求过来找到对应的 Route 进行处理,接下来我们接下 Spring Cloud GateWay 内置几种 Predicate 的使用。假定转发uri都设定为lb://nacos-provider-payment规则实例说明Path- Path=/gate/,/rule/当请求的路径为gate、rule开头的时,转发到nacos-provider-payment服务Before- Before=2017-01-20T17:42:47.789-07:00[America/Denver]在某个时间之前的请求才会被转发到nacos-provider-payment服务After- After=2017-01-20T17:42:47.789-07:00[America/Denver]在某个时间之后的请求才会被转发到nacos-provider-payment服务Between- Between=2017-01-20T17:42:47.789-07:00[America/Denver],2017-01-21T17:42:47.789-07:00[America/Denver]在某个时间段之间的才会被转发Cookie- Cookie=chocolate, ch.p名为chocolate的表单或者满足正则ch.p的表单才会被匹配到进行请求转发Header- Header=X-Request-Id, \d+携带参数X-Request-Id或者满足\d+的请求头才会匹配Host- Host=www.xiangcaowuyu.net当主机名为www.xiangcaowuyu.net的时候直接转发到nacos-provider-payment服务Method- Method=GET只有GET方法才会匹配转发请求,还可以限定POST、PUT等请求方式测试我们上面代码的匹配规则如果我们访问的路径是/echo开头,并且Header中username=laughing就转发到nacos-provider-payment服务。第一次正常访问第二次,我们配置Header可以看到报404第三次,我们配置Header但是参数值设置为test,可以看到也是报404其他参数配置我们不再测试。五、过滤器规则(Filter)过滤规则实例说明PrefixPath- PrefixPath=/app在请求路径前加上appRewritePath- RewritePath=/test, /app/test访问localhost:9022/test,请求会转发到localhost:8001/app/testSetPathSetPath=/app/{path}通过模板设置路径,转发的规则时会在路径前增加app,{path}表示原请求路径RedirectTo 重定向RemoveRequestHeader 去掉某个请求头信息测试Demo我们只测试一个PrefixPath服务提供者 @GetMapping(value = "app/echo/{string}") public String echo1(@PathVariable("string") String string) { return "app/echo/{string} " + string+"\t当前服务端口:"+serverPort; }配置文件server: port: 9527 spring: application: name: cloud-gateway-service cloud: nacos: discovery: server-addr: 192.168.120.180:8848 gateway: discovery: locator: enabled: true routes: - id: payment-routh uri: lb://nacos-provider-payment # 服务名称 predicates: - Path=/** filters: PrefixPath=/app测试
2021年07月29日
1,318 阅读
0 评论
1 点赞
1
...
3
4
5
...
18