开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Java动态代理 JDK动态代理 使用Cglib实现动态代理

发表于 2021-11-11

JDK动态代理

JDK的动态代理只允许代理接口

重要的类和接口Proxy和InvocationHandler
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)方法创建目标对象的代理对象
获取到目标对象的代理对象之后,执行目标对象的方法实际上是通过调用代理对象的invoke方法实现的
我们可以在invoke方法中增加额外的通用的逻辑来对目标对象进行增强

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typescript复制代码/**
* @Description 代理对象
*/
public class ProxyHandler implements InvocationHandler{
private Object target;

public Object getProxyHandler(Object target){
this.target = target;
Object o = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
return o;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object invoke = method.invoke(target, args);
System.out.println("After");
return invoke;
}
}

获取到代理对象之后,像调用目标对象一样调用代理对象的方法,直观上和普通的调用没有任何区别,但是实际上调用的是代理对象的invoke方法,在invoke方法里面可以做的事情有很多,可以增强甚至重写某些方法,通过method.getName()来对指定方法进行处理

1
2
3
4
5
6
7
8
9
10
11
scala复制代码public class ProxyHandlerTest extends TestInit {
@Autowired
ISalesOrderFlowDomainService salesOrderFlowDomainService;
@Test
public void test(){
ProxyHandler proxyHandler = new ProxyHandler();
ISalesOrderFlowDomainService iSalesOrderFlowDomainService = (ISalesOrderFlowDomainService) proxyHandler.getProxyHandler(salesOrderFlowDomainService);
SalesOrderFlowDTO salesOrderFlow = iSalesOrderFlowDomainService.getSalesOrderFlow("20210702000001", OrderFlowType.SIGNED);
System.out.println(salesOrderFlow);
}
}

使用Cglib实现动态代理

Cglib不是jdk自带的jar包,需要下载并加入到项目中
CGLIB代理相关的类:

  • net.sf.cglib.proxy.Enhancer 主要的增强类。
  • net.sf.cglib.proxy.MethodInterceptor 主要的方法拦截类,它是Callback接口的子接口,需要用户实现。
  • net.sf.cglib.proxy.MethodProxy JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用。

CGLIB动态代理的原理就是用Enhancer生成一个原有类的子类,并且设置好callback到proxy, 则原有类的每个方法调用都会转为调用实现了MethodInterceptor接口的proxy的intercept() 函数

实现MethodInterceptor接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typescript复制代码public class CglibProxy implements MethodInterceptor{
//主要的增强类。
private Enhancer enhancer = new Enhancer();
@Override
/**
*
* @param o 是被代理对象
* @param method 调用方法的Method对象
* @param args 方法参数
* @param methodProxy
* @return cglib生成用来代替Method对象的一个对象,使用MethodProxy比调用JDK自身的Method直接执行方法效率会有提升
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("before " + methodProxy.getSuperName());
System.out.println(method.getName());
Object o1 = methodProxy.invokeSuper(o, args);
//Object o2 = method.invoke(o, args); 使用这种方式会发生死循环,因为方法会被拦截
System.out.println("after " + methodProxy.getSuperName());
return o1;
}

public Object newProxyInstance(Class<?> c) {
//设置产生的代理对象的父类。
enhancer.setSuperclass(c);
//设置CallBack接口的实例
enhancer.setCallback(this);
//使用默认无参数的构造函数创建目标对象
return enhancer.create();
}
}

被代理对象和测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
csharp复制代码public class CglibDemo {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
Do o = (Do)cglibProxy.newProxyInstance(Do.class);
System.out.println(o.doSomething(5));
}
}
class Do{
public int doSomething(int num){
System.out.println("方法执行中。。。。。。");
return num;
}
}

Jdk和Cglib 的区别

java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

  1. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
  2. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
  3. 如果目标对象没有实现接口,必须采用CGLIB库.
    spring会自动在JDK动态代理和CGLIB之间转换如何强制使用CGLIB实现AOP?
  • 添加CGLIB库,SPRING_HOME/cglib/*.jar
  • 在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=”true”/>
    JDK动态代理和CGLIB字节码生成的区别?
  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类
  • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
    因为是继承,所以该类或方法最好不要声明成final

【参考文章】

【1】www.cnblogs.com/shijiaqi106…

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Maven高级

发表于 2021-11-11

maven的生命周期

Maven 拥有三套独立的生命周期,它们分别是 clean、default 和 site。

  • clean 生命周期的目的是清理项目;
  • default(build) 生命周期的目的是构建项目;
  • site 生命周期的目的是建立项目站点。
clean 生命周期

clean 生命周期的目的是清理项目,它包括以下三个阶段。

  • pre-clean:执行清理前需要完成的工作。
  • clean:清理上一次构建过程中生成的文件,比如编译后的 class 文件等。
  • post-clean:执行清理后需要完成的工作。
default 生命周期

default 生命周期定义了构建项目时所需要的执行步骤,它是所有生命周期中最核心部分,包含的阶段如下表所述,下面只列出了一些常用的阶段

名称 作用
validate 验证项目结构是否正常,必要的配置文件是否存在
compile 编译项目中的源代码
test 运行测试案例
package 将编译后的 class 和资源打包,比如 jar/war/pom
verify 检测测试后的包是否完好
install 将打好的包安装到本地仓库中,以便共享给本地的其他项目
deploy 将打好的包安装到私服中,方便所有程序员共享
site生命周期

site 生命周期的目的是建立和发布项目站点,根据不同的插件可以生成一些html文件,html文件中包含项目的依赖、描述、java文档、甚至源码等信息。

分模块开发

服务拆分

所有的功能都写在一个项目中面临如下问题:

  1. 代码臃肿,应用启动时间长;
  2. 应用容错性差,某个小小功能的程序错误可能导致系统资源的不断消耗,最后整个系统宕机;
  3. 伸缩困难,单个应用扩展性能时只能整个应用进行扩展,造成计算资源浪费。
  4. 回归测试周期长,修复一个小小bug可能都需要对所有关键业务进行回归测试。
  5. 开发协作困难,一个大型应用系统,可能几十个甚至上百个开发人员,大家都在维护一套代码的话,代码merge复杂度急剧增加。

模块聚合与继承

模块聚合

  1. packaging 类型为 pom
1
xml复制代码 <packaging>pom</packaging>
  1. 定义构建时需要关联的模块名称
1
2
3
4
5
xml复制代码 <modules>
     <module>dao</module>
     <module>service</module>
     <module>web</module>
 </modules>

模块继承

在子模块中指定父工程坐标(GAV)

1
2
3
4
5
xml复制代码<parent>
<artifactId>MyMaven</artifactId>
<groupId>com.eponine</groupId>
<version>1.0-SNAPSHOT</version>
</parent>

依赖管理

将依赖统一放在父工程中进行管理,这样可以统一版本.子模块只需要从父工程继承依赖即可。

依赖管理升级版

不使用<dependencyManagement>子工程会继承全部依赖,使用之后继承指定依赖

  1. 父工程使用<dependencyManagement>标签对依赖进行管理
  2. 子工程声明自己要从父工程继承的依赖,可以省略版本号

属性(版本)管理

在父工程中会管理大量的依赖,其中一组依赖可能需要使用相同的版本,例如spring相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
xml复制代码<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>

在maven父工程中,可以使用<properties>标签统一管理属性,在依赖中使用${}引用定义的属性版本即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
xml复制代码<properties>
<spring.version>5.2.12.RELEASE</spring.version>
</properties>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>

依赖冲突

依赖冲突是指项目依赖的某一个jar包,有多个不同的版本,因而造成包版本冲突

依赖冲突很经常是包之间的间接依赖引起的。每个显式声明的包都会依赖于一些其它的隐式包,这些隐式的包会被maven间接引入进来,从而造成类包冲突

依赖冲突的解决

  1. maven自己的解决原则
  • 第一声明优先原则
  • 最短路径匹配原则
  1. 手动依赖排除
  2. 版本锁定
第一声明优先原则

直接引入:后定义的覆盖前面的

间接引入:第一声明优先原则:谁先定义,谁优先

最短路径匹配原则

image.png

手动排除依赖

排除掉5.2.12版本的冲突依赖,使用5.1.18版本的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
xml复制代码<dependencies>
<!--第一声明原则,谁先声明,用谁的版本-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.12.RELEASE</version>
<!--手动依赖排除-->
<!--这里排除了出现冲突的5个依赖-->
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.18.RELEASE</version>
</dependency>

</dependencies>
版本锁定

在父工程中使用<dependencyManagement>进行版本管理就是版本锁定

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

还在手写数据库设计文档?java一键生成

发表于 2021-11-11

这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战」

前言:

以前我们还需要手写数据库设计文档、现在可以通过引入screw核心包来实现Java 数据库文档一键生成。话不多说、直接上代码演示。

支持的数据库列表:

  • MySQL
  • MariaDB
  • TIDB
  • Oracle
  • SqlServer
  • PostgreSQL
  • 这些主流的数据库都支持的。

实现步骤:

引入 pom.xml核心配置screw包

1
2
3
4
5
xml复制代码       <dependency>
<groupId>cn.smallbun.screw</groupId>
<artifactId>screw-core</artifactId>
<version>1.0.5</version>
</dependency>

main方法类全部代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
java复制代码package com.railway.modules.sys.controller;

/**
* Created by LiYangYong on 2021/11/11
*/
import cn.smallbun.screw.core.Configuration;
import cn.smallbun.screw.core.engine.EngineConfig;
import cn.smallbun.screw.core.engine.EngineFileType;
import cn.smallbun.screw.core.engine.EngineTemplateType;
import cn.smallbun.screw.core.execute.DocumentationExecute;
import cn.smallbun.screw.core.process.ProcessConfig;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Collections;

/**
* Created by LiYangYong on 2021/11/11.
*/
public class TestScrewMain {
private static final String DB_URL = "jdbc:mysql://localhost:3307";
private static final String DB_NAME = "renren_fast?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai";
private static final String DB_USERNAME = "root";
private static final String DB_PASSWORD = "crit@2019";
private static final String FILE_OUTPUT_DIR = "C:\\Users\\Administrator\\Desktop\\java\\";

// 可以设置 Word 或者 Markdown 格式
private static final EngineFileType FILE_OUTPUT_TYPE = EngineFileType.WORD;
private static final String DOC_FILE_NAME = "数据库表设计文档";
private static final String DOC_VERSION = "V1.0.0";
private static final String DOC_DESCRIPTION = "数据库表设计描述";

public static void main(String[] args) {
// 创建 screw 的配置
Configuration config = Configuration.builder()
// 版本
.version(DOC_VERSION)
// 描述
.description(DOC_DESCRIPTION)
// 数据源
.dataSource(buildDataSource())
// 引擎配置
.engineConfig(buildEngineConfig())
// 处理配置
.produceConfig(buildProcessConfig())
.build();

// 执行 screw,生成数据库文档
new DocumentationExecute(config).execute();
}

/**
* 创建数据源
*/
private static DataSource buildDataSource() {
// 创建 HikariConfig 配置类
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("com.mysql.jdbc.Driver");
hikariConfig.setJdbcUrl(DB_URL + "/" + DB_NAME);
hikariConfig.setUsername(DB_USERNAME);
hikariConfig.setPassword(DB_PASSWORD);
// 设置可以获取 tables remarks 信息
hikariConfig.addDataSourceProperty("useInformationSchema", "true");
// 创建数据源
return new HikariDataSource(hikariConfig);
}

/**
* 创建 screw 的引擎配置
*/
private static EngineConfig buildEngineConfig() {
return EngineConfig.builder()
// 生成文件路径
.fileOutputDir(FILE_OUTPUT_DIR)
// 打开目录
.openOutputDir(false)
// 文件类型
.fileType(FILE_OUTPUT_TYPE)
// 文件类型
.produceType(EngineTemplateType.freemarker)
// 自定义文件名称
.fileName(DOC_FILE_NAME)
.build();
}

/**
* 创建 screw 的处理配置,一般可忽略
* 指定生成逻辑、当存在指定表、指定表前缀、指定表后缀时,将生成指定表,其余表不生成、并跳过忽略表配置
*/
private static ProcessConfig buildProcessConfig() {
return ProcessConfig.builder()
// 根据名称指定表生成
.designatedTableName(Collections.<String>emptyList())
// 根据表前缀生成
.designatedTablePrefix(Collections.<String>emptyList())
// 根据表后缀生成
.designatedTableSuffix(Collections.<String>emptyList())
// 忽略表名
.ignoreTableName(Arrays.asList("test", "mytable","role","t_role","t_user"))
// 忽略表前缀
//.ignoreTablePrefix(Collections.singletonList("t_"))
// 忽略表后缀
//.ignoreTableSuffix(Collections.singletonList("_test"))
.build();
}


}

生成的数据库设计文档演示

​

表qrtz_blob_triggers

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 SCHED_NAME varchar 120 0 N Y
2 TRIGGER_NAME varchar 200 0 N Y
3 TRIGGER_GROUP varchar 200 0 N Y
4 BLOB_DATA blob 65535 0 Y N

表qrtz_calendars

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 SCHED_NAME varchar 120 0 N Y
2 CALENDAR_NAME varchar 200 0 N Y
3 CALENDAR blob 65535 0 N N

表qrtz_cron_triggers

编号 名称 数据类型 长度 小数位 允许空值 主键 默认值 说明
1 SCHED_NAME varchar 120 0 N Y
2 TRIGGER_NAME varchar 200 0 N Y
3 TRIGGER_GROUP varchar 200 0 N Y
4 CRON_EXPRESSION varchar 120 0 N N
5 TIME_ZONE_ID varchar 80 0 Y N

这样就解放双手拉

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java动作事件监听器详解与实例分析 监听事件简介 动作事件

发表于 2021-11-11

这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

Hello!大家好哇,我是灰小猿!

今天来和大家总结一下有关在进行Java的GUI编程时动作事件监听函数的基本作用和功能。

我们知道在Java窗体中的组件,本身是不具备任何功能的,因此我们必须要给控件添加相应的事件监听,才能在界面中触发相应的事件处理,今天就来和大家分享一下在Java中动作事件监听器的使用过程

监听事件简介

首先,什么的监听事件?

在swing事件模型中由三个分离的对象完成对事件的处理,分别为事件源、事件和监听程序,由事件源触发一个事件,它由一个或多个监听器进行监听,并且由监听器触发事件并执行相应的事件。

然而对于事件监听器,其实就是一个“实现特定监听器接口”类对象,事件几乎都以对象来表示,它是某种事件类的对象,事件源(控件,如按钮)会在用户做出相应的动作(如点击按钮)时产生事件对象,

在这里需要注意的是:所有的事件源都具有addXXXListener()和removeXXXListener()方法(其中的XXX表示添加的监听事件的类型),前者表示添加相应的监听事件,后者表示移除相应的监听事件。

在Java中常用的两种事件监听器是“动作事件监听器”和“焦点事件监听器”。接下来我和大家分别分享一下这两种常用的事件监听机制。

动作事件监听器

动作事件(ActionEvent)监听器是swing中比较常用的一种监听器,在Java窗体中很多事件都需要使用它来进行监听,如我们常见的按钮点击事件等,以下是动作事件监听器的接口和常见的事件源:

事件名称

事件源

监听接口

添加或删除相应类型监听器的方法

ActionEvent

JButton、JList、JTextField

ActionListener

addActionListener()、removeActionListener()

下面以一个实例来说明该事件,在为按钮控件添加动作事件监听之后,点击按钮出现弹窗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
java复制代码package actionEvent;

import java.awt.Color;
import java.awt.Container;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.WindowConstants;

public class ActionEvent extends JFrame{
JButton jButton; //定义点击按钮

public ActionEvent() {
setTitle("动作事件监听器");
setSize(400,400);
Container container = getContentPane(); //添加容器

jButton = new JButton("点击触发动作事件");
jButton.setBounds(100, 150, 200, 30);

container.add(jButton); //将按钮加入到容器
jButton.addActionListener(new jButtonActionEvent()); //为按钮添加监听事件,监听事件为控件对应的内部类

setLayout(null); //清空窗体布局管理器
setLocationRelativeTo(null); //设置窗体居中显示
setVisible(true); //设置窗体居中
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); //设置窗体关闭方式

}

public static void main(String[] args) {
// TODO Auto-generated method stub
new ActionEvent();
}

//定义内部类实现ActionListener接口
class jButtonActionEvent implements ActionListener{

@Override
public void actionPerformed(java.awt.event.ActionEvent arg0) {
jButton.setBackground(Color.RED); //点击以后将按钮背景换成红色
JOptionPane.showMessageDialog(null, "触发了动作事件监听器!" ,"提示",JOptionPane.INFORMATION_MESSAGE);
}
}
}

效果如下:

​

对于动作事件监听器在这里要注意的两点:

  1. 在重写的actionPerformed()函数中应该对单击的控件进行判断,判断是否是单击了该控件。
  2. 在对某一个控件进行动作事件监听的时候,一定要使用addActionListener()方法为该控件添加事件监听,否则就算对该控件写了触发事件,在没有为该控件添加监听时,单击该控件也还是没有用的。
  3. 一般情况下,为事件源做监听事件应使用匿名内部类的形式,如上例代码为按钮添加事件时采用匿名内部类的形式

觉得有用记得点赞关注哟!

大灰狼期待与你一同进步!

​

​

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

jpsite-v2-cloud 一套拿来即用的企业级云原生微

发表于 2021-11-11

jpsite-v2-cloud 企业级微服务快速开发解决方案

2020年最新微服务架构版本(发布日期:2021.1.1)

随着现代应用的架构演变, 微服务的概念逐渐兴起, 相关的技术栈与架构体系也日渐成熟, 本项目基于apache与alibaba的开源框架, 构建一套企业级的微服务开发快速解决方案,
该套架构是目前2020年主流微服务开发框架, 后续有机会将改造成Service Mesh架构

2021年最新微服务架构版本(发布日期:2022.1.1)

时过境迁,转眼就要来到2020年了,程序的发展进步越来越快,微服务已成过去,云原生已经逐渐普及,
众多开发语言齐头并进,Java生态体系也有了一些新的变化,为了顺应时代的发展,框架做了部分版本升级已新的
技术融合,该套架构是目前依然是2021年主流微服务开发框架,后续有机会将改造成Service Mesh架构

技术栈:

相关技术 版本 介绍
nacos 1.3.2 -> 2.0.3 一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台
dubbo 3.0.0 一款高性能、轻量级的开源 Java 服务通信框架
jdk 15 2020年Java开发员的软件开发工具包
elasticSearch 7.6 一个分布式多用户能力的全文搜索引擎
spring-security 5.3.4.RELEASE 一个功能强大且高度可定制的身份验证和访问控制框架
spring 5.2.9.RELEASE 为现代基于 Java 的企业应用程序提供了一个全面的编程和配置模型
spring-boot 2.3.4.RELEASE 一个框架,一种全新的编程规范,其设计目的是用来简化新Spring应用的初始搭建以及开发过程
spring-security-oauth2 2.3.4.RELEASE 提供最新的 OAuth 2.0 支持
mybatis-plus 3.4.0 一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生
spring-mvc 5.2.9.RELEASE 最初建立在 Servlet API 之上的 Web 框架
knife4j 2.0.4 Knife4j是一个为Swagger接口文档赋能的工具
lombok 1.18.2 一种 Java 工具,可用来帮助开发人员消除 Java 的冗长
lettuce 5.3.4.RELEASE 一个Redis的Java驱动包
spring-cloud-gateway 3.0.3 一个用于在 Spring WebFlux 之上构建 API 网关的库
Reactor 3.4.8 第四代响应式库,基于Reactive Streams规范,用于在 JVM 上构建非阻塞应用程序
RocketMQ -> 4.9.2 一个统一消息引擎、轻量级数据处理平台
skywalking 分布式系统的应用程序性能监控工具,专为微服务、云原生和基于容器(Docker、Kubernetes、Mesos)架构而设计
docker 20.10.8 一个虚拟环境容器,可以将你的开发环境、代码、配置文件等一并打包到这个容器中,并发布和应用到任意平台中
sentinel -> 1.8.2 一个强大的流量控制组件,可实现微服务的可靠性、弹性和监控
seata 一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务
云原生套件 版本 DevOps企业级云原生架构
Prometheus 一个开源系统监控和警报工具包
kubernetes v1.21 一个开源系统,用于自动化部署、扩展和管理容器化应用程序
Grafana 一个完全开源的度量分析与可视化平台,可对来自各种各种数据源的数据进行查询、分析、可视化处理以及配置告警
Alertmanager 一个独立的告警模块,接收Prometheus等客户端发来的警报
jenkins 一个开源的、提供友好操作界面的持续集成(CI)工具, 主要用于持续、自动的构建/测试软件项目
go 一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易
Istio 提供一种统一化的微服务连接、安全保障、管理与监控方式。
EFK 提供了一整套开源实时日志分析解决方案
Rancher 多云混合云多集群Kubernetes管理平台
harbor 为企业用户设计的容器镜像仓库
nexus 一个强大的Maven仓库管理器
sonarqube 一个开源的代码分析平台, 用来持续分析和评测项目源代码的质量
kubesphere 3.2.0 在 Kubernetes 之上构建的面向云原生应用的容器混合云开源平台

未来计划:

关于技术栈替换

原技术 未来技术 介绍
spring-mvc spring-webFlux 一个采用响应式堆的 Web 框架
lettuce redission 一个高级的分布式协调 Redis 客户端

alt jpsite-cloud集成dubbo&nacos&getway架构图

alt oauth2 授权中心流程

本地开发流程:

首先安装相关依赖项目模块

1
2
复制代码dove-common  mvn install
rbac-api mvn install
  1. 启动dubbo-rbac

1.2 启动dubbo-shop
2. 启动auth-server
3. 启动client

访问localhost, 登录用户名jiangpeng, 密码123456

nacos地址:http://localhost:8848/nacos

client api doc:http://localhost/doc.html

代码提交规范

每次提交的代码必须保证是测试通过的, 且提交commit必须符合如下规范,便于其他开发者审阅与学习。

git commit 描述使用如下形式前缀,冒号后面加空格隔开

fix: 、feat: 、refactor: 、

示例:

1
2
3
makefile复制代码fix: 修复了字段缺失
feat: 新增短信验证码校验
refactor: 移除多余代码块

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python爬取高德地图各城市天气情况

发表于 2021-11-11

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
python复制代码
import time

import requests
from prettytable import PrettyTable #用来打印表格的库
'''
查询当前地点天气的url:https://www.amap.com/service/weather?adcode=110000
各城市对应code的url:https://www.amap.com/service/cityList?version=20207

备注:这两个url可以从Network中查看到
'''

# 获取地名对应的code
def get_location_dic(location_dic_url,province):

# 判断输入的省份名称是否正确
list = ['北京市', '天津市', '河北省', '山西省', '内蒙古自治区', '辽宁省', '吉林省', '黑龙江省', '上海市', '江苏省', '浙江省', '安徽省', '福建省', '江西省', '山东省', '河南省',
'湖北省', '湖南省', '广东省', '广西壮族自治区', '海南省', '重庆市', '四川省', '贵州省', '云南省', '西藏自治区', '陕西省', '甘肃省', '青海省', '宁夏回族自治区',
'新疆维吾尔自治区', '台湾省', '香港特別行政區', '澳門特別行政區']

if province not in list:
print('_'*100)
print(':( 输入错误!请输入正确的省份名称')
print('提示:可输入的省份名称为:')
print(list)
else:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36'}

response = requests.get(location_dic_url,headers=headers)
response.encoding = response.apparent_encoding

# 获取json数据
data = response.json()['data']['cityData']['provinces']

# 创建字典用于保存各省份的code
provinces_code_dic = {}
for d in data:
provinces_code_dic[data[d]['label']] = data[d]['adcode']

# 获取指定省份的所有城市的code等基础信息
cities_data = data[provinces_code_dic[province]]['cities']

# 直辖市没有城市,此时直接返回上一级包含直辖市code信息的内容。
if not cities_data:
cities_data = [data[provinces_code_dic[province]]]
return cities_data
return ''


# 从高德地图返回的json数据中获取关键部分
def get_today_data(url,location_code):
weather_url = url + location_code
response = requests.get(weather_url)
response.encoding = response.apparent_encoding
today_data = response.json()['data']['data'][0]
return today_data

# 从以上获取的数据中抽取出当前地点的天气信息并赋值给对象wheather
# 从json数据中可以看出,高德地图的天气数据分为白天和夜晚
def get_wheather(today_data):
wheather = {
'day_wheather': {
'max_temp': today_data['forecast_data'][0]['max_temp'],
'min_temp': today_data['forecast_data'][0]['min_temp'],
'weather_name': today_data['forecast_data'][0]['weather_name'],
'wind_power_desc': today_data['forecast_data'][0]['wind_power_desc'],
'wind_direction_desc': today_data['forecast_data'][0]['wind_direction_desc']
},
'night_wheather': {
'max_temp': today_data['forecast_data'][1]['max_temp'],
'min_temp': today_data['forecast_data'][1]['min_temp'],
'weather_name': today_data['forecast_data'][1]['weather_name'],
'wind_power_desc': today_data['forecast_data'][1]['wind_power_desc'],
'wind_direction_desc': today_data['forecast_data'][1]['wind_direction_desc']
}
}
return wheather



if __name__ == '__main__':
while True:
province = input('请输入省份名称:')
print('正在爬取,请稍后...')
print('')
url = 'https://www.amap.com/service/weather?adcode='
location_dic_url = 'https://www.amap.com/service/cityList?version=20207'

# 定义空列表用来存储所有城市的天气信息
all_info = []

# 获取各城市对应code及其他信息
location_dic_all = get_location_dic(location_dic_url,province)

if location_dic_all:
# 去除没用信息,只保留 城市:code
location_dic = [
{
base['name']:base['adcode'] for base in location_dic_all
}
]

# 提取城市名列表
locations = location_dic[0].keys()

# 遍历城市名称,将所需信息提取出来全部赋给all_info
for location in locations:
today_data = get_today_data(url,location_dic[0][location])
wheather = get_wheather(today_data)
all_info.append(
{
'location':location,
'day_wheather':wheather['day_wheather'],
'night_wheather':wheather['night_wheather']
}
)
today = today_data['forecast_date']
weekday = str(today_data['weekday'])

# 数据中含有1-7代表星期,通过字典将其和星期一一对应
weekday_dic = {
'1':'星期二',
'2':'星期三',
'3':'星期四',
'4':'星期五',
'5':'星期六',
'6':'星期日',
'7':'星期一',
}

# 调用此模块用于打印表格
tb = PrettyTable()
tb.field_names = ['城市','早晚','天气','最高温度','最低温度','风力']
for x in all_info:
tb.add_row([x['location'],'白天',x['day_wheather']['weather_name'],x['day_wheather']['max_temp'],x['day_wheather']['min_temp'],x['day_wheather']['wind_direction_desc'] + ' ' + x['day_wheather']['wind_power_desc']])
tb.add_row(['','夜晚',x['night_wheather']['weather_name'],x['night_wheather']['max_temp'],x['night_wheather']['min_temp'],x['night_wheather']['wind_direction_desc'] + ' ' + x['night_wheather']['wind_power_desc']])
print('今天是%s %s。%s天气如下:'%(today,weekday_dic[weekday],province))
print(tb)
print('*'*100)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

最后一个单词的生命周期 前言 解题思路 转变思路 总结

发表于 2021-11-11

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战」。

前言

给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中最后一个单词的长度。

单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。

解题思路

要得到最后一个单词的长度,那我们可以从后往前遍历,如果遇到空格说明最后一个单词遍历完成

注意:字符串前后有空格,我们可以调用字符串的trim()方法,去除前后空格,当然也有别的方法处理,我在这就用这个函数了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码class Solution {
public int lengthOfLastWord(String s) {
s = s.trim();
int res = 0;
int len = s.length();

char tmp = 'a';

for (int i = len - 1; i >= 0; i--) {
tmp = s.charAt(i);
if (tmp == ' ') break;
res++;
}
return res;
}
}

转变思路

标签:字符串遍历
从字符串末尾开始向前遍历,其中主要有两种情况
第一种情况,以字符串”Hello World”为例,从后向前遍历直到遍历到头或者遇到空格为止,即为最后一个单词”World”的长度5
第二种情况,以字符串”Hello World “为例,需要先将末尾的空格过滤掉,再进行第一种情况的操作,即认为最后一个单词为”World”,长度为5
所以完整过程为先从后过滤掉空格找到单词尾部,再从尾部向前遍历,找到单词头部,最后两者相减,即为单词的长度
时间复杂度:O(n),n为结尾空格和结尾单词总体长度

总结

  • 本题考查应该没有任何难点,我们只需要逆向查找就可以了。题目中明确提到是通过空格来进行隔离每个单词的标识的,并没有让我们加入语义的分析。当然了加入语义就不是考试算法相关了。

欢迎点赞哦

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

记一次fastjson升级产生的bug

发表于 2021-11-11

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

记录一次fastjson产生的bug

由于fastjson采白名单的方法来防御反序列化满洞,导致当黑客不断发屈新的反序列化Gadgets类时,在autoType关闭的情况下仍然可能可以绕过黑白名单防御机制,造成远程命令执行漏洞。经研究,该漏洞利用门槛较低,可绕过autoType限制,风险影响较大。阿里云应急响应中心提醒fastjson用户尽快采取安全措施阻止漏洞攻击。

但是在升级的过程中,有一个项目出现了问题。出现的问题是,使用大写字母作为属性命名的javaBean,在使用这个方法时出问题。

1
2
java复制代码//parseObject(String text, Class<T> clazz) 
Input input = JSONObject.parseObject(request.getData().toJSONString(), Input.class);

上面的代码中,得出来的input为空。

在fastjson的DefaultJSONParser这个类中两个不同版本的fastjson处理选择了不同的方式。

1.2.12

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
java复制代码public <T> T parseObject(Type type, Object fieldName) {
int token = this.lexer.token();
if (token == 8) {
this.lexer.nextToken();
return null;
} else {
if (token == 4) {
if (type == byte[].class) {
byte[] bytes = this.lexer.bytesValue();
this.lexer.nextToken();
return bytes;
}

if (type == char[].class) {
String strVal = this.lexer.stringVal();
this.lexer.nextToken();
return strVal.toCharArray();
}
}

ObjectDeserializer derializer = this.config.getDeserializer(type);

try {
//这里有所不同
return derializer.deserialze(this, type, fieldName);
} catch (JSONException var6) {
throw var6;
} catch (Throwable var7) {
throw new JSONException(var7.getMessage(), var7);
}
}
}

从调试中可以看到,1.2.12版本中属性赋值使用的是通过属性的set方法进行的。

clipboard.png

1.2.70版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
java复制代码public <T> T parseObject(Type type, Object fieldName) {
int token = this.lexer.token();
if (token == 8) {
this.lexer.nextToken();
return null;
} else {
if (token == 4) {
if (type == byte[].class) {
byte[] bytes = this.lexer.bytesValue();
this.lexer.nextToken();
return bytes;
}

if (type == char[].class) {
String strVal = this.lexer.stringVal();
this.lexer.nextToken();
return strVal.toCharArray();
}
}

ObjectDeserializer deserializer = this.config.getDeserializer(type);

try {
if (deserializer.getClass() == JavaBeanDeserializer.class) {
if (this.lexer.token() != 12 && this.lexer.token() != 14) {
throw new JSONException("syntax error,except start with { or [,but actually start with " + this.lexer.tokenName());
} else {
//更新后走了这里
return ((JavaBeanDeserializer)deserializer).deserialze(this, type, fieldName, 0);
}
} else {
//原来的版本走的是这里
return deserializer.deserialze(this, type, fieldName);
}
} catch (JSONException var6) {
throw var6;
} catch (Throwable var7) {
throw new JSONException(var7.getMessage(), var7);
}
}
}

而1.2.70却是通过这样的方式去解析的。

clipboard.png

最后经过查阅资料,发现可以使用@JSONField(name = "AAA001")这个注解来解决这个问题,在入参的类上进行标注,大写的入参也可以转换了。

1
2
java复制代码@JSONField(name = "AAA001)
private String AAA001; //属性1

同时有部分人因为使用lombok插件,导致@JSONField(name = "AAA001")标注在属性上依旧不起作用,此时可以通过显示声明属性的get方法来达成目的。

1
2
3
4
java复制代码    @JSONField(name = "AAA001")
public String getAAA001() {
return AAA001;
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

踩坑记:macos 下安装国内镜像源的homebrew 1

发表于 2021-11-11

macos下本身就自带homebrew,但还是尝试安装个完全体,并将镜像源更换到国内gitee的版本,所以进行了下面的各种尝试


  1. zsh?bash?

之前更新到了Catalina系统后,突然发现terminal就默认变成了zsh类型,而不是我们常见的bash版本。

(当然,现在已经更到了big sur,一个对15版mac负担挺大的版本)

当然,现在已经更到了big sur,一个对15版mac负担挺大的版本

关于两个terminal有什么区别,我的理解就是语法很相近的两个不同的shell,当然我查到有大佬已经很认真的探讨过几个shell各自的特点了:bash和zsh区别_Bash,Zsh和其他Linux Shell之间有什么区别?

不过,由于,homebrew官网上的安装命令行是以/bin/bash开头的,以及,个人更加习惯bash的各种样子,所以决定切换回bash的壳子。

如何切换回去,此处参考了这位大佬的帖子:Mac终端:The default interactive shell is now zsh.

  1. 查看不同的shell风格/壳子
1
bash复制代码more /etc/shells

macos上自带的各种shell

  1. 切换终端到bash,当然,如果要用别的shell,那么改变下面的路径就行
1
bash复制代码chsh -s /bin/bash

注意:切换之后需要重启一下终端terminal

  1. 安装homebrew

  1. 常规操作

按照官网教程来走:

1
bash复制代码/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

但是!如果是在国内下载,很有可能你会遇到一个和npm这些一样尴尬的一个问题。。。。速度特别慢!!!!

所以,聪明的国内同胞,也提供了类似淘宝镜像一样的,国内镜像资源!

(感谢大佬们)

  1. 使用国内镜像资源

参考文章:zhuanlan.zhihu.com/p/111014448

下载命令:(可以看到是通过gitee拉下来的)

1
bash复制代码/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"

使用国内镜像源下载

中途会很贴心的提示,需要选择哪个下载源,我尝试的时候,清华的是可以用的~如果一个失败了,建议换一个试一下

然后会有很多次需要使用sudo的命令,按照步骤一路等他下载下来就好,最后同样,重启终端terminal即可

最终结果

这里附一个homebrew的官网链接,罗列了可以通过brew命令给macos弥补一些包的清单:formulae.brew.sh/formula/

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

nginx实现本地http转https请求

发表于 2021-11-11

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」

引言

项目中有需要做接口回调,测试和dev环境都是https方式请求调用没有问题,但是本地调试不通。因为本地启动后端服务都是http请求,所以使用nginx代理,将http请求转换为https请求,方便调试。

安装nginx

在上家单位已经安装过nginx,为了写这篇文章又找了一下官网(nginx.org/en/download…),根据自己电脑系统安装。本人电脑是windows。
image.png
下载完成后解压文件,拷贝到不包含中文名的文件夹中,我习惯将软件安装统一放在software文件夹中。
image.png
通过nginx.exe文件或通过命令start nginx启动nginx。启动后访问localhost验证是否启动成功。
image.png

安装OpenSSL

访问官网(slproweb.com/products/Wi…)下载。
image.png
安装到对应文件夹中。

image.png
配置环境变量,配置OPENSSL_HOME。

image.png
配置path
image.png

生成https证书

创建ssl文件夹用于存放证书。创建私钥 (建议使用系统窗口,不要用gitBash 有涉及到选择的地方,gitBash无法选择)

1
csharp复制代码openssl genrsa -des3 -out shidian.key 1024 //shidian 自己取的名字

image.png

输入pass作为密码,两次一致。

文件夹中生成shidian.key文件
创建csr证书。

1
vbnet复制代码openssl req -new -key shidian.key -out shidian.csr

image.png
此时有两个文件

image.png
复制 shidian.key 并重命名 shidian.key.org
执行命令

1
vbnet复制代码openssl rsa -in shidian.key.org -out shidian.key

image.png
生成crt证书

1
csharp复制代码openssl x509 -req -days 365 -in shidian.csr -signkey shidian.key -out shidian.crt

最终文件夹的文件为
image.png

修改nginx.conf配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
yml复制代码upstream aglacwy {
server localfop.hanhang.com:11295;
keepalive 64;
}
server {
listen 443 ssl;
server_name localfop.chehejia.com; # 配置的https的域名

ssl_certificate D://software//nginx-1.21.4//ssl//shidian.crt; # 这个是证书的crt文件所在目录
ssl_certificate_key D://software//nginx-1.21.4//ssl//shidian.key; # 这个是证书key文件所在目录

ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;

ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_set_header Connection "";
proxy_http_version 1.1;
if ($request_method ~ ^(POST|DELETE|OPTIONS)$) {
proxy_pass http://aglacwy;
break ;
}
proxy_pass http://aglacwy;
}
}

localfop.hanhang.com为本机电脑修改host后的域名,如果需要可以在hosts文件中将localhost配置为想设置的域名
image.png

执行命令nginx -s reload重启nginx。通过https访问接localfop.hanhang.com/test/test/n…,通过断点可通。响应请求结果。

image.png
至此,成功将http请求转化为https请求。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…369370371…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%