如何构建一个可重复读流 InputStream 的 HttpServletRequest?

之前在某公司工作的时候,领导要求所有前端向后端传递的参数都要经过前端加密,后端解密。说一句题外话:个人认为这种操作纯属脱裤子放屁,没啥用。因为前端代码都是公开的,无论你采用对称加密、非对称加密,或者摘要算法验签等等,对于稍懂技术的人来说,稍稍分析一下就能找到前端加密的方法,然后直接用相同的方式加密就行,所以这就是障眼法,只能骗骗不懂技术的人。不过领导的要求吗,既然定下来了,那么我们总要服从。因为每个方法都需要有这个解密或者验签的过程,我们自然而然想要到了通过 Filter、Interceptor 或者 AOP 等技术统一来做,不可能在各个方法中做这件事,在但是我们都知道,对于 post、put 等请求,参数都是放在请求体中的,需要通过流读出来,而流是不可以重复读的,所以我们应该怎么来解决这个问题,来构造一个可以重复读流 InputStream 的 HttpServletRequest。 解决方法:使用自定义类来缓存 stream 即可 RequestWrapper 类:缓存字节数据 package cn.bridgeli.filter; import cn.bridgeli.utils.http.HttpHelper; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; /** * 构建可重复读取inputStream的request * * @author BridgeLi */ public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public RepeatedlyRequestWrapper(HttpServletRequest request) { super(request); body = HttpHelper.getBodyString(request).getBytes(StandardCharsets.UTF_8); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public int available() throws IOException { return body.length; } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } } package cn.bridgeli.utils.http; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; /** * 通用http工具封装 * * @author BridgeLi */ public class HttpHelper { private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); public static String getBodyString(ServletRequest request) { StringBuilder sb = new StringBuilder(); try (InputStream inputStream = request.getInputStream()) { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { LOGGER.error("getBodyString出现问题!", e); } return sb.toString(); } } 然后,可以在 Servlet 或 Filter 中使用 RepeatableFilter 替换原始的 HttpServletRequest。 ...

May 3, 2024 · 2 min · 343 words · Bridge Li

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

自从 ChatGPT 横空出世之后,突然没了写博客的兴趣,因为无论有什么问题,随便往 ChatGPT 一帖,结果就哗哗的出来了,今天这个问题就是。前一段时间和某公司联调接口,对方的接口测试环境好好的,一上线调不通,报错信息就是标题,只见贴到 ChatGPT,ChatGPT 回答如下: 这个错误通常是由于缺少信任证书或证书链不完整导致的。解决方法通常有两种: **添加证书到信任库**:获取正确的证书,并将其添加到 Java 的信任库中。可以使用 `keytool` 工具来完成此操作。具体步骤如下: – 首先,使用以下命令导出证书: keytool -printcert -rfc -sslserver <hostname> -port <port> 其中 <hostname> 和 <port> 是你要访问的 HTTPS 接口的主机名和端口号。 – 然后将证书导出到一个文件中。 – 最后,使用以下命令将证书添加到 Java 的信任库中: keytool -importcert -file <certificate-file> -keystore <path-to-truststore> -alias <alias> 其中 <certificate-file> 是你导出的证书文件,<path-to-truststore> 是 Java 信任库的路径,<alias> 是证书的别名。 **忽略证书验证**:如果你确定你要连接的服务器是安全的,并且不需要严格的证书验证,你可以在 Java 代码中忽略证书验证。但这不是推荐的做法,因为可能会导致安全风险。你可以使用 `TrustManager` 接口实现自定义的信任管理器,然后将其应用于 SSL 连接。下面是一个简单的示例代码: import javax.net.ssl.*; import java.security.cert.X509Certificate; public class SSLCertificateIgnore { public static void main(String[] args) throws Exception { // Create a trust manager that does not validate certificate chains TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) { } public void checkServerTrusted(X509Certificate[] certs, String authType) { } } }; // Install the all-trusting trust manager SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, null); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); // Now you can access the HTTPS endpoint without certificate validation } } 请注意,这种方法会降低安全性,因为它会使你的应用程序容易受到中间人攻击。因此,最好的做法是第一种方法,即添加正确的证书到信任库中。 ...

April 21, 2024 · 1 min · 146 words · Bridge Li

AntPathMatcher 路径匹配

公司项目使用 AntPathMatcher 路径匹配是否登陆,之前没有接触过,刚好趁这次机会学习了一番。 一、基本规则 1、? 匹配一个字符(除过操作系统默认的文件分隔符) 2、* 匹配0个或多个字符 3、** 匹配0个或多个目录 4、{spring:[a-z]+} 将正则表达式 [a-z]+ 匹配到的值,赋值给名为 spring 的路径变量 PS:必须是完全匹配才行,在 SpringMVC 中只有完全匹配才会进入 controller 层的方法 二、注意事项: 1、匹配文件路径,需要匹配某目录下及其各级子目录下所有的文件,使用 /*/* 而非 *.*,因为有的文件不一定含有文件后缀 2、匹配文件路径,使用 AntPathMatcher 创建一个对象时,需要注意 AntPathMatcher 也有有参构造,传递路径分隔符参数 pathSeparator,对于文件路径的匹配来说,可以根据不同的操作系统来传递各自的文件分隔符,以此防止匹配文件路径错误 3、最长匹配规则(has more characters),即越精确的模式越会被优先匹配到。例如,URL请求 /app/dir/file.jsp,现在存在两个路径匹配模式 /*/*.jsp 和 /app/dir/*.jsp,那么会根据模式 /app/dir/*.jsp 来匹配 三、实例 可以参考若依框架:com.ruoyi.gateway.filter.AuthFilter 和 com.ruoyi.gateway.filter.XssFilter

March 27, 2022 · 1 min · 45 words · Bridge Li

身份证校验方法

我国的身份证编制是有标准的,每一位都不是随便瞎写的,就像我国的地图坐标经纬度一样,并不是真是的经纬度,而是人为加入了偏转,被称为:火星坐标系,但是工作中发现很多人并不了解,在工作中,用户输入的身份证号是否正确,我们根据这个规则是可以做初步校验的,当然真是的校验肯定是要通过公安部授权的接口,这是收费的。但是初步校验是真简单的,我个人发现有些系统并没有加入,所以今天写一篇小文章,做一个常用的工具类来校验身份证号,至于具体的规则,大家可以搜一下这个国标:GB11643-1999,代码如下: package cn.bridgeli.demo; import org.apache.commons.lang3.StringUtils; import org.junit.Assert; import org.junit.Test; /** * @author BridgeLi * @date 2022/1/23 15:01 */ public class IdNoUtil { @Test public void testId() { String IdNo = ""; boolean b = validateIdNo(IdNo); Assert.assertTrue(b); } public static boolean validateIdNo(String IdNo) { if (StringUtils.isBlank(IdNo) || IdNo.length() != 18) { return false; } char[] charArray = IdNo.toCharArray(); //前十七位加权因子 int[] idCardWi = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}; //这是除以11后,可能产生的11位余数对应的验证码 String[] idCardY = {"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"}; int sum = 0; for (int i = 0; i < 17; i++) { int current = Integer.parseInt(String.valueOf(charArray[i])); int count = current * idCardWi[i]; sum += count; } char idCardLast = charArray[17]; int idCardMod = sum % 11; if (idCardY[idCardMod].equalsIgnoreCase(String.valueOf(idCardLast))) { return true; } else { return false; } } }

January 23, 2022 · 1 min · 144 words · Bridge Li

JWT 实际应用例子

JWT 是什么,很多网站都有例子,但是如何使用,却不是很多,今天就介绍一个很具体的、能在项目中实际应用的例子。 pom <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency> Java 代码 package cn.bridgeli.demo; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; public class JwtUtil { static String key = "key"; /** * 用户登录成功后生成Jwt * 使用Hs256算法 私匙使用用户密码 * * @param ttlMillis jwt过期时间 * @param user 登录成功的user对象 * @return */ public static String createJWT(long ttlMillis, User user) { //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //生成JWT的时间 long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的) Map<String, Object> claims = new HashMap<>(); // claims.put("id", user.getId()); claims.put("username", user.getUsername()); // claims.put("password", user.getPassword()); //生成签发人 String subject = user.getUsername(); //下面就是在为payload添加各种标准声明和私有声明了 //这里其实就是new一个JwtBuilder,设置jwt的body JwtBuilder builder = Jwts.builder() //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 .setClaims(claims) //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。 .setId(UUID.randomUUID().toString()) //iat: jwt的签发时间 .setIssuedAt(now) //代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。 .setSubject(subject) //设置签名使用的签名算法和签名使用的秘钥 .signWith(signatureAlgorithm, key); if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); //设置过期时间 builder.setExpiration(exp); } return builder.compact(); } /** * Token的解密 * * @param token 加密后的token * @param user 用户的对象 * @return */ public static Claims parseJWT(String token, User user) { //签名秘钥,和生成的签名的秘钥一模一样 //得到DefaultJwtParser Claims claims = Jwts.parser() //设置签名的秘钥 .setSigningKey(key) //设置需要解析的jwt .parseClaimsJws(token).getBody(); return claims; } /** * 校验token * 在这里可以使用官方的校验,我这里校验的是token中携带的密码于数据库一致的话就校验通过 * * @param token * @param user * @return */ public static Boolean isVerify(String token, User user) { //签名秘钥,和生成的签名的秘钥一模一样 //得到DefaultJwtParser Claims claims = Jwts.parser() //设置签名的秘钥 .setSigningKey(key) //设置需要解析的jwt .parseClaimsJws(token).getBody(); if (claims.get("username").equals(user.getUsername())) { return true; } return false; } }

December 26, 2021 · 2 min · 223 words · Bridge Li

关于 CPU 的缓存的证明和应用

证明: 首先,我们都知道现在的 CPU 多核技术,同时会有三级缓存(L1,L2,L3 ),如图: 缓存基本上来说就是把后面的数据加载到离自己近的地方,对于 CPU 来说,是一个字节一个字节的加载数据的吗?其实不是的,一般来说都是要一块一块的加载的,对于这样的一块一块的数据单位,我们叫做“Cache Line”,中文翻译:缓存行,一般来说,一个主流的 CPU 的 Cache Line 是 64 Bytes,也就是 8 个 64 位的整型,这就是 CPU 从内存中捞数据上来的最小数据单位。那么这个如何证明呢? package cn.bridgeli.demo; import java.util.concurrent.CountDownLatch; /** * @author BridgeLi * @date 2021/11/29 20:41 */ public class CacheLineTest { private static long loop = 1_0000_0000L; private static class T { // private volatile long x1, x2, x3, x4, x5, x6, x7; private volatile long x = 0L; // private volatile long x8, x9, x10, x11, x12, x13, x14; } private static T[] arr = new T[2]; static { arr[0] = new T(); arr[1] = new T(); } public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(2); Thread t1 = new Thread(() -> { for (long i = 0; i < loop; i++) { arr[0].x = i; } countDownLatch.countDown(); }, "t1"); Thread t2 = new Thread(() -> { for (long i = 0; i < loop; i++) { arr[1].x = i; } countDownLatch.countDown(); }, "t2"); long currentTimeMillis = System.currentTimeMillis(); t1.start(); t2.start(); countDownLatch.await(); System.out.println(System.currentTimeMillis() &#8211; currentTimeMillis); } } 我们定义了一个长度为 2 的数组,数组中的元素是 T 类型,T 有一个属性 x,我们同时启动两个线程分别给第一个元素和第二个元素中的 x 复制从 0 到一亿减 1,这个时候我们测试他耗时多少,不同的电脑配置肯定是不同的,我的电脑大概是四千多毫秒,然后我们把 T 对象中属性 x 前后各被注释调的一行打开再跑一次看看,变成了大概 700 毫秒,相差整整 6 倍!这是为何? ...

November 29, 2021 · 2 min · 279 words · Bridge Li

神奇的 (a == (Integer) 1 && a == (Integer) 2 && a == (Integer) 3) = true

前一段时间看了一篇文章 (a == (Integer) 1 && a == (Integer) 2 && a == (Integer) 3) 是否可以为 true,当时第一反应怎么可能,谁知道再往下看,作者竟然给出来如下代码,一运行神奇的事出现了,真的为 true,代码如下: package cn.bridgeli.demo; import java.lang.reflect.Field; public class Magic { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Class cache = Integer.class.getDeclaredClasses()[0]; Field c = cache.getDeclaredField("cache"); c.setAccessible(true); Integer[] array = (Integer[]) c.get(cache); // array[129] is 1 array[130] = array[129]; // Set 2 to be 1 array[131] = array[129]; // Set 3 to be 1 Integer a = 1; if (a == (Integer) 1 && a == (Integer) 2 && a == (Integer) 3) { System.out.println(true); } else { System.out.println(false); } } } 因为作者没有给出解释,所以就研究了一番,发现需要基础非常扎实才能写出这段代码,这段代码之所以为 true,要理解如下几个问题: ...

October 31, 2021 · 2 min · 344 words · Bridge Li

GeoHash 算法的 Java 版实现

之前曾经做过一个类 LBS 的小需求,当时是用 redis 做的,就是这篇文章,其实 GeoHash 算法,我们也可以自己实现,具体如下: package cn.bridgeli.demo; import java.util.ArrayList; import java.util.BitSet; import java.util.HashMap; public class GeoHash { public static final double MINLAT = -90; public static final double MAXLAT = 90; public static final double MINLNG = -180; public static final double MAXLNG = 180; private static int numbits = 5 * 5; //经纬度单独编码长度 private static double minLat; private static double minLng; private final static char[] digits = {&#8216;0&#8217;, &#8216;1&#8217;, &#8216;2&#8217;, &#8216;3&#8217;, &#8216;4&#8217;, &#8216;5&#8217;, &#8216;6&#8217;, &#8216;7&#8217;, &#8216;8&#8217;, &#8216;9&#8217;, &#8216;b&#8217;, &#8216;c&#8217;, &#8216;d&#8217;, &#8216;e&#8217;, &#8216;f&#8217;, &#8216;g&#8217;, &#8216;h&#8217;, &#8216;j&#8217;, &#8216;k&#8217;, &#8216;m&#8217;, &#8216;n&#8217;, &#8216;p&#8217;, &#8216;q&#8217;, &#8216;r&#8217;, &#8216;s&#8217;, &#8216;t&#8217;, &#8216;u&#8217;, &#8216;v&#8217;, &#8216;w&#8217;, &#8216;x&#8217;, &#8216;y&#8217;, &#8216;z&#8217;}; //定义编码映射关系 final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>(); //初始化编码映射内容 static { int i = 0; for (char c : digits) { lookup.put(c, i++); } } public GeoHash() { setMinLatLng(); } public String encode(double lat, double lon) { BitSet latbits = getBits(lat, -90, 90); BitSet lonbits = getBits(lon, -180, 180); StringBuilder buffer = new StringBuilder(); for (int i = 0; i < numbits; i++) { buffer.append((lonbits.get(i)) ? &#8216;1&#8217; : &#8216;0&#8217;); buffer.append((latbits.get(i)) ? &#8216;1&#8217; : &#8216;0&#8217;); } String code = base32(Long.parseLong(buffer.toString(), 2)); //Log.i("okunu", "encode lat = " + lat + " lng = " + lon + " code = " + code); return code; } public ArrayList<String> getAroundGeoHash(double lat, double lon) { //Log.i("okunu", "getArroundGeoHash lat = " + lat + " lng = " + lon); ArrayList<String> list = new ArrayList<>(); double uplat = lat + minLat; double downLat = lat &#8211; minLat; double leftlng = lon &#8211; minLng; double rightLng = lon + minLng; String leftUp = encode(uplat, leftlng); list.add(leftUp); String leftMid = encode(lat, leftlng); list.add(leftMid); String leftDown = encode(downLat, leftlng); list.add(leftDown); String midUp = encode(uplat, lon); list.add(midUp); String midMid = encode(lat, lon); list.add(midMid); String midDown = encode(downLat, lon); list.add(midDown); String rightUp = encode(uplat, rightLng); list.add(rightUp); String rightMid = encode(lat, rightLng); list.add(rightMid); String rightDown = encode(downLat, rightLng); list.add(rightDown); //Log.i("okunu", "getArroundGeoHash list = " + list.toString()); return list; } //根据经纬度和范围,获取对应的二进制 private BitSet getBits(double lat, double floor, double ceiling) { BitSet buffer = new BitSet(numbits); for (int i = 0; i < numbits; i++) { double mid = (floor + ceiling) / 2; if (lat >= mid) { buffer.set(i); floor = mid; } else { ceiling = mid; } } return buffer; } //将经纬度合并后的二进制进行指定的32位编码 private String base32(long i) { char[] buf = new char[65]; int charPos = 64; boolean negative = (i < 0); if (!negative) { i = -i; } while (i <= -32) { buf[charPos&#8211;] = digits[(int) (-(i % 32))]; i /= 32; } buf[charPos] = digits[(int) (-i)]; if (negative) { buf[&#8211;charPos] = &#8216;-&#8216;; } return new String(buf, charPos, (65 &#8211; charPos)); } private void setMinLatLng() { minLat = MAXLAT &#8211; MINLAT; for (int i = 0; i < numbits; i++) { minLat /= 2.0; } minLng = MAXLNG &#8211; MINLNG; for (int i = 0; i < numbits; i++) { minLng /= 2.0; } } //根据二进制和范围解码 private double decode(BitSet bs, double floor, double ceiling) { double mid = 0; for (int i = 0; i < bs.length(); i++) { mid = (floor + ceiling) / 2; if (bs.get(i)) { floor = mid; } else { ceiling = mid; } } return mid; } //对编码后的字符串解码 public double[] decode(String geohash) { StringBuilder buffer = new StringBuilder(); for (char c : geohash.toCharArray()) { int i = lookup.get(c) + 32; buffer.append(Integer.toString(i, 2).substring(1)); } BitSet lonset = new BitSet(); BitSet latset = new BitSet(); //偶数位,经度 int j = 0; for (int i = 0; i < numbits * 2; i += 2) { boolean isSet = false; if (i < buffer.length()) { isSet = buffer.charAt(i) == &#8216;1&#8217;; } lonset.set(j++, isSet); } //奇数位,纬度 j = 0; for (int i = 1; i < numbits * 2; i += 2) { boolean isSet = false; if (i < buffer.length()) { isSet = buffer.charAt(i) == &#8216;1&#8217;; } latset.set(j++, isSet); } double lon = decode(lonset, -180, 180); double lat = decode(latset, -90, 90); return new double[]{lat, lon}; } public static void main(String[] args) { GeoHash geohash = new GeoHash(); String s = geohash.encode(39.923201, 116.390705); System.out.println("geohash:" + s); ArrayList<String> aroundGeoHash = geohash.getAroundGeoHash(39.923201, 116.390705); for (String s1 : aroundGeoHash) { System.out.println("aroundGeoHash:" + s1); } double[] geo = geohash.decode(s); System.out.println(geo[0] + " " + geo[1]); } } 参考:https://www.jianshu.com/p/2fd0cf12e5ba

September 25, 2021 · 4 min · 701 words · Bridge Li

Spring boot 自动装配实现的原理 – 文字简述版

当启动 Spring boot 应用程序的时候,会先创建 SpringApplication 的对象,在对象的构造方法中会进行某些参数的初始化工作,最主要的是判断当前应用程序的类型以及初始化器和监听器,在这个过程中会加载整个应用程序的 spring.factories 文件,将文件的内容放到缓存对象中,方便后续获取。 SpringApplication 对象创建完成之后,开始执行 run 方法,来完成整个启动,启动过程中最主要的有两个方法,第一个叫做 prepareContext,第二个叫做 refreshContext,在这两个关键步骤中完成了自动装配的核心功能,前面的处理逻辑包含了上下文对象的创建,banner 的打印,异常报告器的准备等各个准备工作,方便后续来进行调用。 在 prepareContext 方法中主要完成的是对上下文对象的初始化操作,包含了属性值的设置,比如环境对象,在整个过程中有一个非常重要的方法,叫做 load,load 主要完成一件事,将当前启动类作为一个 beanDefinition 注册到 registry 中,方便后续在进行 BeanFactoryPostProcessor 调用执行的时候,找到对应的主类,来完成 @SpringBootApplication、@EnableAutoConfiguration 等注解的解析工作。 在 refreshContext 方法中会进行整个容器的刷新过程,会调用 Spring 中的 refresh 方法,refresh 中有 13 个非常关键的方法,来完成整个 Spring 应用程序的启动,在自动装配过程中,会调用 invokeBeanFactoryPostProcessor 方法,在此方法中主要对 ConfigurationClassPostProcessor 类的处理,他是 BeanFactoryPostProcessor 的子类也是,BeanDefinitionRegistryPostProcessor 的子类,在调用的时候会先调用 BeanDefinitionRegistryPostProcessor 中的 postProcessBeanDefinitionRegistry 方法,然后调用 BeanFactoryPostProcessor 中的 postProcessBeanFactory 方法,在执行 postProcessBeanDefinitionRegistry 方法的时候会解析处理各种注解,包含 @PropertySource、@ComponentScan、@ComponentScans、@Bean、@Import 等注解,最主要的是 @Import 注解的解析。 在解析 @Import 注解的时候,会有一个 getImport 的方法,从主类开始递归解析注解,把所有包含 @Import 的注解都解析道,然后在 processImport 方法中对 Import 的类进行分类,此处最主要的是识别 AutoConfigurationImportSelect 归属于 ImportSelect 的子类,在后续过程中会调用 deferredImportSelectorHandler 中的 process 方法,来完善 EnableAutoConfiguration 的加载。 ...

August 29, 2021 · 1 min · 82 words · Bridge Li

好用的 IDEA 插件

好久没有好好的写过博客了,不过这一篇也没打算好好写。前一段时间换工作,所谓工欲善其事,必先利其器,所以常用的软件都需要重新配置,而作为一名 Java 程序员,最重要的就是 IDEA 了,所以这次就写一下,我个人认为比较好用的 IDEA 插件,以及他们的作用,后面如果发现更好用的插件了,也会在这篇文章里面更新。 IDE Eval Reset,开发者:zhile.io 大家众所周知的,IDEA 很好用,但是付费软件,还不便宜,但是在国内很多人都是找各种方法破解,人家也在做反破解,一直搞攻防战,但是 IDEA 有一个很人性的一点,可以试用 30 天,所以这个插件就是让大家无限试用。 安装完成之后,在 Help 菜单下面会多一个:Eval Rest 的子菜单,就可以重制 30 天的有效期,当然也可以选上右下角的:Auto reset before per restart,啥功能不用说了吧。 最后多说一句:请大家最好不要滥用此功能,最好还是支持正版。 Maven Helper,开发者:Vojtech Krasa 在我刚工作的时候的那个年代,我们 build 还用 Ant,现在可能很多人都没听说过了,后来才开始用 maven,但无论无论如何都还有一个巨大的问题,jar 包冲突,有时会出莫名其妙的问题,所以这个插件就是用来分析 maven 项目的 jar 冲突的。 装上这个插件之后,在 pom 文件左下方会多一个:Dependency Analyzer 的子菜单,点一下,就可以看到那些 jar 冲突了,然后选中,在右侧排除掉即可。 GsonFormatPlus,开发者:mars-men 在我们的工作中,使用第三方的接口,现在数据一般都是用 json,所以不可避免的要用对象和 json 的互转,而我们根据 json 写对象的时候,自己一行一行的写,不仅容易错,而且还没有效率,唯一的好处就是锻炼大家打字的说平,所以这个插件应运而生了,他可以很轻松的根据 json 数据,生成实体类。 安装完成后,我们只需要新建一个实体类,然后在该类中,摁下 option + s 键,然后把 json 数据 copy 到那个框里面,就可以直接生成相应的实体类了,巨方便快捷。 any-rule,开发者:any-rule 在我们工作中,自从有了正则表达式,我们的工作量可能减轻了很多,但很多时候写正则表达式也挺头疼的,最起码我是这样的,老是写不对。所以这个插件就是帮我们生成一些常见的正则表达式。 安装完成后,你只需要摁下 option + a,然后就可以看到一些常见的正则表达式,选择合适的即可。 ...

July 17, 2021 · 1 min · 156 words · Bridge Li