Maven

Maven介绍

IDEA配置Maven

1,结构

2,pom.xml

Maven工程就是由groupIdartifactIdversion作为唯一标识。我们在引用其他第三方库的时候,也是通过这3个变量确定。Maven通过对jar包进行PGP签名确保任何一个jar包一经发布就无法修改。修改已发布jar包的唯一方法是发布一个新版本。因此,某个jar包一旦被Maven下载过,即可永久地安全缓存在本地。

3,依赖管理

当声明了自己的项目需要abc,Maven会自动导入abc的jar包,再判断出abc需要xyz,又会自动导入xyz的jar包,不断循环自动导入所有依赖包。

Maven定义了几种依赖关系,分别是compiletestruntimeprovided

scope说明示例
compile编译时需要用到该jar包(默认),Maven会把这种类型的依赖直接放入classpathcommons-logging
test编译Test时需要用到该jar包junit
runtime编译时不需要,但运行时需要用到mysql
provided编译时需要用到,但运行时由JDK或某个服务器提供servlet-api

构建

Maven包含多种生命周期(lifecycle:default:构建的核心部分,编译,测试,打包,部署等,clean:在进行真正的构建之前进行一些清理工作,Site:生成项目报告,站点,发布站点),每个生命周期由一系列阶段(phase)构成,执行一个phase又会触发一个或多个goal,具体任务由一系列goal来完成,通常情况,我们总是执行phase默认绑定的goal,因此不必指定goal。。

mvn命令后面跟的是phase, maven自动根据phase确定对应的lifecycle(没有重名的phase),然后运行对应lifecycle中的第一个phase直到指定的phase

经常使用的命令有:

mvn clean:清理所有生成的class和jar;

mvn clean compile:先清理,再执行到compile

mvn clean test:先清理,再执行到test,因为执行test前必须执行compile,所以这里不必指定compile

mvn clean package:先清理,再执行到package

插件

1,Maven执行compile这个phase,这个phase会调用compiler插件执行关联的compiler:compile这个goal。实际上,执行每个phase,都是通过某个插件(plugin)来执行的,Maven本身其实并不知道如何执行compile,它只是负责找到对应的compiler插件,然后执行默认的compiler:compile这个goal来完成编译。所以,使用Maven,实际上就是配置好需要使用的插件,然后通过phase调用它们。

模块

1,在软件开发中,把一个大项目分拆为多个模块是降低软件复杂度的有效方法,Maven可以有效地管理多个模块,我们只需要把每个模块当作一个独立的Maven项目,它们有各自独立的pom.xml

mvnw

1,使用Maven Wrapper,它可以负责给这个特定的项目安装指定版本的Maven,而其他项目不受影响。给一个项目提供一个独立的,指定版本的Maven给它使用

2,

在项目的根目录(即pom.xml所在的目录)下:

安装后,查看项目结构,把项目的mvnwmvnw.cmd.mvn提交到版本库中,可以使所有开发人员使用统一的Maven版本。:

只需要把mvn命令改成mvnw就可以使用跟项目关联的Maven

网络

基础

TCP

1,Socket是一个抽象概念,一个应用程序通过一个Socket来建立一个远程连接,而Socket内部通过TCP/IP协议把数据传输到网络。Socket、TCP和部分IP的功能都是由操作系统提供的,不同的编程语言只是提供了对操作系统调用的简单的封装。Java提供的几个Socket相关的类就封装了操作系统提供的接口。每个应用程序需要各自对应到不同的Socket,数据包才能根据Socket正确地发到对应的应用程序。

2,使用Socket进行网络编程时,本质上就是两个进程之间的网络通信。其中一个进程必须充当服务器端,它会主动监听某个指定的端口,另一个进程必须充当客户端,它必须主动连接服务器的IP地址和指定端口,如果连接成功,服务器端和客户端就成功地建立了一个TCP连接,双方后续就可以随时发送和接收数据。对服务器端来说,它的Socket是指定的IP地址和指定的端口号;对客户端来说,它的Socket是它所在计算机的IP地址和一个由操作系统分配的随机端口号。

3,使用Java进行TCP编程时,需要使用Socket模型:

UDP

1,UDP没有创建连接,数据包也是一次收发一个,所以没有流的概念。

2,UDP端口和TCP端口虽然都使用0~65535,但他们是两套独立的端口,即一个应用程序用TCP占用了端口1234,不影响另一个应用程序用UDP占用端口1234。

3,服务器端

客户端

HTTP

1,HTTP请求的格式是固定的,它由HTTP Header和HTTP Body两部分构成。第一行总是请求方法 路径 HTTP版本,例如,GET / HTTP/1.1表示使用GET请求,路径是/,版本是HTTP/1.1

如果是GET请求,那么该HTTP请求只有HTTP Header,没有HTTP Body,GET请求的参数必须附加在URL上,并以URLEncode方式编码,因为URL的长度限制,GET请求的参数不能太多。如果是POST请求,那么该HTTP请求带有Body,以一个空行分隔,body中就是请求的参数,`POST请求的参数就没有长度限制,因为POST请求的参数必须放到Body中。

后续的每一行都是固定的Header: Value格式,我们称为HTTP Header,服务器依靠某些特定的Header来识别客户端请求,例如:

2,HTTP响应也是由Header和Body两部分组成,响应的第一行总是HTTP版本 响应代码 响应说明,例如,HTTP/1.1 200 OK表示版本是HTTP/1.1,响应代码是200。客户端只依赖响应代码判断HTTP响应是否成功。HTTP有固定的响应代码:

当浏览器收到第一个HTTP响应后,它解析HTML后,根据解析的内容又会发送一系列HTTP请求,请求需要的资源,例如,GET /logo.jpg HTTP/1.1请求一个图片,服务器响应图片请求后,会直接把二进制内容的图片发送给浏览器。

服务器总是被动地接收客户端的一个HTTP请求,然后响应它。客户端则根据需要发送若干个HTTP请求。

3,客户端的HTTP编程。

Get请求

Post请求:要准备好发送的Body数据并正确设置`Content-Type

RMI

1,RMI:一个JVM中的代码可以通过网络实现远程调用另一个JVM的某个方法。提供服务的一方我们称之为服务器,而实现远程调用的一方我们称之为客户端。

2,服务器和客户端必须共享同一个接口。Java的RMI规定此接口必须派生自java.rmi.Remote,并在每个方法声明抛出RemoteException

通过Java RMI提供的一系列底层支持接口,把上面编写的服务以RMI的形式暴露在网络上,客户端才能调用:

3,先运行服务器,再运行客户端。RMI通过自动生成stub和skeleton实现网络调用,客户端只需要查找服务并获得接口实例,服务器端只需要编写实现类并注册为服务;从运行结果可知,因为客户端只有接口,并没有实现类,因此,客户端获得的接口方法返回值实际上是通过网络从服务器端获取的。对客户端来说,客户端持有的WorldClock接口实际上对应了一个“实现类”,它是由Registry内部动态生成的,并负责把方法调用通过网络传递到服务器端。而服务器端接收网络调用的服务并不是我们自己编写的WorldClockService,而是Registry自动生成的代码。我们把客户端的“实现类”称为stub,而服务器端的网络服务类称为skeleton,它会真正调用服务器端的WorldClockService,获取结果,然后把结果通过网络传递给客户端。整个过程由RMI底层负责实现序列化和反序列化,Java的RMI严重依赖序列化和反序列化,而这种情况下可能会造成严重的安全漏洞,因为Java的序列化和反序列化不但涉及到数据,还涉及到二进制的字节码,即使使用白名单机制也很难保证100%排除恶意构造的字节码。因此,使用RMI时,双方必须是内网互相信任的机器,不要把1099端口暴露在公网上作为对外服务。Java的RMI调用机制决定了双方必须是Java程序,其他语言很难调用Java的RMI。如果要使用不同语言进行RPC调用,可以选择更通用的协议,例如gRPC

image-20210504122055342

XML

结构

1,XML有几个特点:一是纯文本,默认使用UTF-8编码,二是可嵌套,适合表示结构化数据。

2,XML有固定的结构,首行必定是<?xml version="1.0"?>,可以加上可选的编码。紧接着,如果以类似<!DOCTYPE note SYSTEM "book.dtd">声明的是文档定义类型(DTD:Document Type Definition),DTD是可选的。接下来是XML的文档内容,一个XML文档有且仅有一个根元素,根元素可以包含任意个子元素,元素可以包含属性,例如,<isbn lang="CN">1234567</isbn>包含一个属性lang="CN",且元素必须正确嵌套。如果是空元素,可以用<tag/>表示。

由于使用了<>以及引号等标识符,如果内容出现了特殊符号,需要使用&???;表示转义。例如,Java<tm>必须写成:

常见的特殊字符如下:

字符表示
<<
>>
&&
""
''

格式正确的XML(Well Formed)是指XML的格式是正确的,可以被解析器正常读取。而合法的XML是指,不但XML格式正确,而且它的数据结构可以被DTD或者XSD验证。

3,XML是一种树形结构的文档,它有两种标准的解析API:

4,DOM,顶层的是document代表XML文档,它是真正的“根”

XML的内容:

5,SAX解析会触发一系列事件:

关键代码SAXParser.parse()除了需要传入一个InputStream外,还需要传入一个回调对象,这个对象要继承自DefaultHandler

由于SAX没有文件结构,如果要读取<name>节点的文本,我们就必须在解析过程中根据startElement()endElement()定位当前正在读取的节点,可以使用栈结构保存,每遇到一个startElement()入栈,每遇到一个endElement()出栈,这样,读到characters()时我们才知道当前读取的文本是哪个节点的

6,XML文件结构可以对应到一个定义好的JavaBean,Jackson可以实现XML到JavaBean的转换。

Maven的依赖:

7,JSON是JavaScript Object Notation的缩写,它去除了所有JavaScript执行代码,只保留JavaScript的对象格式

几个显著的优点:

JSON也可以转换为Java Bean,依赖:com.fasterxml.jackson.core:jackson-databind:2.10.0

JSON还支持自定义序列化与反序列化,例如isbn类型不匹配,需要自定义序列化与反序列化器,

JDBC

简介

1,使用Java程序访问数据库时,Java代码通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。注意到JDBC接口是Java标准库自带的,所以可以直接编译。而具体的JDBC驱动是由数据库厂商提供的,通过引入该厂商提供的JDBC驱动,就可以通过JDBC接口来访问,这样保证了Java程序编写的是一套数据库访问代码,却可以访问各种不同的数据库。Java程序编译期仅依赖java.sql包,不依赖具体数据库的jar包,可随时替换底层数据库,访问数据库的Java代码基本不变。

2,

3,类型转换

SQL数据类型Java数据类型
BIT, BOOLboolean
INTEGERint
BIGINTlong
REALfloat
FLOAT, DOUBLEdouble
CHAR, VARCHARString
DECIMALBigDecimal
DATEjava.sql.Date, LocalDate
TIMEjava.sql.Time, LocalTime

6,插入

查询

1,第一步,通过Connection提供的createStatement()方法创建一个Statement对象,用于执行一个查询,Statment是需要关闭的资源;

第二步,执行Statement对象提供的executeQuery("SELECT * FROM students")并传入SQL语句,执行查询并获得返回的结果集,使用ResultSet来引用这个结果集,ResultSet是需要关闭的资源,无论是什么查询语句JDBC查询的返回值总是ResultSet,即使使用聚合查询也不例外。

第三步,反复调用ResultSetnext()方法并读取每一行结果。ResultSet获取列时,索引从1开始而不是0;必须根据SELECT的列的对应位置来调用getLong(1)getString(2)这些方法,否则对应位置的数据类型不对,将报错。

使用Statement拼字符串非常容易引发SQL注入的问题,这是因为SQL参数往往是从方法参数传入的。

使用PreparedStatement可以完全避免SQL注入的问题,因为PreparedStatement始终使用?作为占位符,并且把数据连同SQL本身传给数据库,这样可以保证每次传给数据库的SQL语句是相同的,只是占位符的数据不同,还能高效利用数据库本身对查询的缓存。使用Java对数据库进行操作时,必须使用PreparedStatement,严禁任何通过参数拼字符串的代码!

更新

1,插入

2,删除,更新

事务

1,数据库事务(Transaction)是由若干个SQL语句构成的一个操作序列,有点类似于Java的synchronized同步。数据库系统保证在一个事务中的所有SQL要么全部执行成功,要么全部不执行,ACID准则,Atomicity:原子性,事务的全部修改全部执行完成,或者全部修改失败。,Consistency:一致性,从一个合理的状态转移到另一个合理的状态,Isolation:隔离性,事务感知不到其它事务的存在,Durability:持久性,修改永久反映在DB中。

2,SQL标准定义了4种隔离级别,分别对应可能出现的数据不一致的情况:

Isolation Level脏读(Dirty Read)不可重复读(Non Repeatable Read)幻读(Phantom Read)
Read UncommittedYesYesYes
Read Committed-YesYes
Repeatable Read--Yes
Serializable---

Read Uncommitted:可以读取其它事务已经修改但未提交的数据。不存在视图,直接到数据库中读取。事务B修改数据但未提交,事务A读取到B修改的数据,如果B事务回滚或者继续修改,那么之前事务A读到的数据就是脏数据是不存在的,这就是脏读(Dirty Read)。

Read Committed:可以读取其它事务已经提交的数据,每次执行语句前都要重新构建视图。不可重复读是指,在一个事务A内,第一次读一数据,在这个事务A还没有结束时,如果另一个事务B恰好修改并提交了这个数据,第二次A读取的是新的数据,与第一次不一致。通过对读取的行数据加锁解决问题。

Repeatable Read:事务一开始就构建要用到的数据的视图,即使其他事务更改了数据,也不会对视图产生影响,事务类读取的数据始终一致。幻读是指,在一个事务A中,第一次查询(select)某条记录发现没有,之后如果其它事务B添加了该数据并提交,此时A第二次查询(select)某条记录发现还是没有,但当A试图更新这条不存在的记录时竟然能成功,并且A第三次读取同一条记录,它就神奇地出现了。可以通过对表加锁解决问题。

Serializable:所有事务按照次序依次串行执行,因此,脏读、不可重复读、幻读都不会出现。

3,事务的思想

默认情况下,我们获取到Connection连接后,总是处于“自动提交”模式,也就是每执行一条SQL都是作为事务自动执行的。

批处理

1,SQL数据库对SQL语句相同,但只有参数不同的若干语句可以作为batch执行,把同一个SQL但参数不同的若干次操作合并为一个batch执行,即批量执行,这种操作有特别优化,速度远远快于循环执行每个SQL。

连接池

1,在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。JDBC连接池有一个标准的接口javax.sql.DataSource,要使用JDBC连接池,必须选择一个JDBC连接池的实现。常用的JDBC连接池有:HikariCP,C3P0等。

一开始,连接池内部并没有连接,所以,第一次调用ds.getConnection(),会迫使连接池内部先创建一个Connection,再返回给客户端使用。当我们调用conn.close()方法时(在try(resource){...}结束处),不是真正“关闭”连接,而是释放到连接池中,以便下次获取连接时能直接返回。因此,连接池内部维护了若干个Connection实例,如果调用ds.getConnection(),就选择一个空闲连接,并标记它为“正在使用”然后返回,如果对Connection调用close(),那么就把连接再次标记为“空闲”从而等待下次调用。

函数式编程

基础

1,Java不支持单独定义函数,但可以把静态方法视为独立的函数,把实例方法视为自带this参数的函数。

2,函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

3,函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!函数式编程(Functional Programming)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。

4,用Lambda表达式替换单方法接口

5,只定义了单抽象方法(default方法或static方法不算入接口定义的抽象方法)的接口称之为FunctionalInterface,用注解@FunctionalInterface标记

方法引用

1,可以向方法传入Lambda表达式和方法引用。

所谓方法引用,是指如果某个方法签名(参数列表和返回值列表)和接口恰好一致,就可以直接传入方法引用。(鸭子类型)

2,某些方法要求传入一个FunctionalInterface的实现类,可以向他传递该FunctionalInterface中定义方法的方法引用。FunctionalInterface不强制继承关系,不需要方法名称相同,只要求方法参数(类型和数量)与方法返回类型相同,即认为方法签名相同。

可以传入静态方法,实例方法,构造方法,

Stream

1,和文件流区别

 java.iojava.util.stream
存储顺序读写的bytechar顺序输出的任意Java对象实例
用途序列化至文件或网络内存计算/业务逻辑

2,和集合区别

List存储的每个元素都是已经存储在内存中的某个Java对象,而Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。List的用途是操作一组已存在的Java对象,而Stream实现的是惰性计算,真正的计算通常发生在最后结果的获取,也就是惰性计算。一个Stream转换为另一个Stream时,实际上只存储了转换规则,并没有任何计算发生。它可以“存储”有限个或无限个元素。这里的存储打了个引号,是因为元素有可能已经全部存储在内存中,也有可能是根据需要实时计算出来的。

java.util.Listjava.util.stream 
元素已分配并存储在内存可能未分配,实时计算
用途操作一组已存在的Java对象惰性计算

3,创建Stream

Map

1,通过吧一个Steam中的每个元素通过某种运算得到结果,把结果加入另一个Stream,实现把一个Stream转换为另一个Streammap()方法接收的对象是Function接口对象,它定义了一个apply()方法,负责把一个T类型转换成R类型,可以传入现有方法,或者是Lambda表达式:

Filter

1,filter()操作,就是对一个Stream的所有元素一一进行测试,不满足条件的就被“滤掉”了,剩下的满足条件的元素就构成了一个新的Stream

2,filter()方法接收的对象是Predicate接口对象,它定义了一个test()方法,负责判断元素是否符合条件。传入lambda表达式或者满足满足该方法签名的方法或者该接口的实现类。

Reduce

1,Stream.reduce()则是Stream的一个聚合方法,它可以把一个Stream的所有元素按照聚合函数聚合成一个结果。

2,reduce()方法传入的对象是BinaryOperator接口,它定义了一个apply()方法,负责把上次累加的结果和本次的元素 进行运算,并返回累加的结果,reduce()方法将一个Stream的每个元素依次作用于BinaryOperator,并将结果合并。reduce()是聚合方法,聚合方法会立刻对Stream进行计算。流的特点是每个元素只遍历一次,聚合操作无非就是一个遍历+累加计算的过程,所以使用reduce后流就已经关闭,无法再次遍历流。

导出Stream

1,对于Stream来说,对其进行转换操作(map()filter()sorted()distinct())并不会触发任何计算,转换操作只是保存了转换规则,无论我们对一个Stream转换多少次,都不会有任何实际计算发生。聚合操作(reduce()collect()count()max()min()sum()average())会立刻促使Stream输出它的每一个元素,并依次纳入计算,以获得最终结果。所以,对一个Stream进行聚合操作,会触发一系列连锁反应。聚合方法会立刻对Stream进行计算。流的特点是每个元素只遍历一次,聚合操作无非就是一个遍历+累加计算的过程,所以使用聚合操作后流就已经关闭,无法再次遍历流。

2,把Stream变为List/Array不是一个转换操作,而是一个聚合操作,它会强制Stream输出每个元素。

3,输出为Map,方法接受两个方法参数,第一个返回key,第二个返回value。

4,分组输出,需要提供两个函数:一个是返回单个元素用于分组的key,第二个是如何处理聚集到一起的多个元素,可以再次对他们分组。

常用方法

1,

2,分类

转换操作:map()filter()sorted()distinct()

合并操作:concat()flatMap()

并行处理:parallel()

聚合操作:reduce()collect()count()max()min()sum()average()

其他操作:allMatch(), anyMatch(), forEach()

其他

可被传入当作满足函数接口要求的参数可分为四类