Java编程基础
一、数据类型
1、基础类型
整型:byte 、short 、int 、long
浮点型:float、double
字节型:char
2、包装类型
Byte,Short,Integer,Long
Float,Double
Character
3、String字符
常用方法
StringBuilder、StringBuffer
4、类型转换
自动转换:范围小类型->范围大类型
强制转换:范围大类型->范围小类型
二、流程控制
1、分支语句
if 语句
else...if 语句
switch 语句
for 循环
while 循环
2、分支判断
true 和 false
3、流程中断
return 关键字
continue 关键字
break 关键字
4、基础算法
冒泡排序
排列组合
递归
三、数组
初始化
数据与集合容器区别
底层数据结构
四、操作符
1、算术运算
加减乘除 、优先级
取余、四舍五入、100%补偿
2、关系运算
大于、小于、大于等于、小于等于、不等于
3、赋值运算
等号赋值
自动加加、自动减减
4、移位运算
左移位、右移位
5、逻辑运算
与、或、非
运算流程到条件整体成立
整体结构
一、Executor框架简介
1、基础简介
Executor系统中,将线程任务提交和任务执行进行了解耦的设计,Executor有各种功能强大的实现类,提供便捷方式来提交任务并且获取任务执行结果,封装了任务执行的过程,不再需要Thread().start()方式,显式创建线程并关联执行任务。
2、调度模型
线程被一对一映射为服务所在操作系统线程,启动时会创建一个操作系统线程;当该线程终止时,这个操作系统线程也会被回收。
3、核心API结构
Executor框架包含的核心接口和主要的实现类如下图所示:
线程池任务:核心接口:Runnable、Callable接口和接口实现类;
任务的结果:接口Future和实现类FutureTask;
任务的执行:核心接口Executor和ExecutorService接口。在Executor框架中有两个核心类实现了ExecutorService接口,ThreadPoolExecutor和ScheduledThreadPoolExecutor。
二、用法案例
1、API基础
ThreadPoolExecutor基础构造
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
参数名 | 说明 |
---|---|
corePoolSize | 线程池的核心大小,队列没满时,线程最大并发数 |
maximumPoolSize | 最大线程池大小,队列满后线程能够容忍的最大并发数 |
keepAliveTime | 空闲线程等待回收的时间限制 |
unit | keepAliveTime时间单位 |
workQueue | 阻塞的队列类型 |
threadFactory | 创建线程的工厂,一般用默认即可 |
handler | 超出工作队列和线程池时,任务会默认抛出异常 |
2、初始化方法
ExecutorService :Executors.newFixedThreadPool();
ExecutorService :Executors.newSingleThreadExecutor();
ExecutorService :Executors.newCachedThreadPool();
ThreadPoolExecutor :new ThreadPoolExecutor() ;
通常情况下,线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险。
3、基础案例
package com.multy.thread.block08executor;
import java.util.concurrent.*;
public class Executor01 {
// 定义线程池
private static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
3,10,5000,TimeUnit.SECONDS,
new SynchronousQueue<>(),Executors.defaultThreadFactory(),new ExeHandler());
public static void main(String[] args) {
for (int i = 0 ; i < 100 ; i++){
poolExecutor.execute(new PoolTask(i));
//带返回值:poolExecutor.submit(new PoolTask(i));
}
}
}
// 定义线程池任务
class PoolTask implements Runnable {
private int numParam;
public PoolTask (int numParam) {
this.numParam = numParam;
}
@Override
public void run() {
try {
System.out.println("PoolTask "+ numParam+" begin...");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
}
public int getNumParam() {
return numParam;
}
public void setNumParam(int numParam) {
this.numParam = numParam;
}
}
// 定义异常处理
class ExeHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) {
System.out.println("ExeHandler "+executor.getCorePoolSize());
executor.shutdown();
}
}
流程分析
- 线程池中线程数小于corePoolSize时,新任务将创建一个新线程执行任务,不论此时线程池中存在空闲线程;
- 线程池中线程数达到corePoolSize时,新任务将被放入workQueue中,等待线程池中任务调度执行;
- 当workQueue已满,且maximumPoolSize>corePoolSize时,新任务会创建新线程执行任务;
- 当workQueue已满,且提交任务数超过maximumPoolSize,任务由RejectedExecutionHandler处理;
- 当线程池中线程数超过corePoolSize,且超过这部分的空闲时间达到keepAliveTime时,回收该线程;
- 如果设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收;
三、线程池应用
应用场景:批量账户和密码的校验任务,在实际的业务中算比较常见的,通过初始化线程池,把任务提交执行,最后拿到处理结果,这就是线程池使用的核心思想:节省资源提升效率。
public class Executor02 {
public static void main(String[] args) {
// 初始化校验任务
List<CheckTask> checkTaskList = new ArrayList<>() ;
initList(checkTaskList);
// 定义线程池
ExecutorService executorService ;
if (checkTaskList.size() < 10){
executorService = Executors.newFixedThreadPool(checkTaskList.size());
}else{
executorService = Executors.newFixedThreadPool(10);
}
// 批量处理
List<Future<Boolean>> results = new ArrayList<>() ;
try {
results = executorService.invokeAll(checkTaskList);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 查看结果
for (Future<Boolean> result : results){
try {
System.out.println(result.get());
// System.out.println(result.get(10000,TimeUnit.SECONDS));
} catch (Exception e) {
e.printStackTrace() ;
}
}
// 关闭线程池
executorService.shutdownNow();
}
private static void initList (List<CheckTask> checkTaskList){
checkTaskList.add(new CheckTask("root","123")) ;
checkTaskList.add(new CheckTask("root1","1234")) ;
checkTaskList.add(new CheckTask("root2","1235")) ;
}
}
// 校验任务
class CheckTask implements Callable<Boolean> {
private String userName ;
private String passWord ;
public CheckTask(String userName, String passWord) {
this.userName = userName;
this.passWord = passWord;
}
@Override
public Boolean call() throws Exception {
// 校验账户+密码
if (userName.equals("root") && passWord.equals("123")){
return Boolean.TRUE ;
}
return Boolean.FALSE ;
}
}
线程池主要用来解决线程生命周期开销问题和资源不足问题,通过线程池对多个任务线程重复使用,线程创建也被分摊到多个任务上,多数任务提交就有空闲的线程可以使用,所以消除线程频繁创建带来的开销。
===============================================================================
一、概念简介
1、线程通信
在操作系统中,线程是个独立的个体,但是在线程执行过程中,如果处理同一个业务逻辑,可能会产生资源争抢,导致并发问题,通常使用互斥锁来控制该逻辑。但是在还有这样一类场景,任务执行是有顺序控制的,例如常见的报表数据生成:
- 启动数据分析任务,生成报表数据;
- 报表数据存入指定位置数据容器;
- 通知数据搬运任务,把数据写入报表库;
该场景在相对复杂的系统中非常常见,如果基于多线程来描述该过程,则需要线程之间通信协作,才能有条不紊的处理该场景业务。
2、等待通知机制
如上的业务场景,如果线程A生成数据过程中,线程B一直在访问数据容器,判断该过程的数据是否已经生成,则会造成资源浪费。正常的流程应该如图,线程A和线程B同时启动,线程A开始处理数据生成任务,线程B尝试获取容器数据,数据还没过来,线程B则进入等待状态,当线程A的任务处理完成,则通知线程B去容器中获取数据,这样基于线程等待和通知的机制来协作完成任务。
3、基础方法
等待/通知机制的相关方法是Java中Object层级的基础方法,任何对象都有该方法:
- notify:随机通知一个在该对象上等待的线程,使其结束wait状态返回;
- notifyAll:唤醒在该对象上所有等待的线程,进入对象锁争抢队列中;
- wait:线程进入waiting等待状态,不会争抢锁对象,也可以设置等待时间;
线程的等待通知机制,就是基于这几个基础方法。
二、等待通知原理
1、基本原理
等待/通知机制,该模式下指线程A在不满足任务执行的情况下调用对象wait()方法进入等待状态,线程B修改了线程A的执行条件,并调用对象notify()或者notifyAll()方法,线程A收到通知后从wait状态返回,进而执行后续操作。两个线程通过基于对象提供的wait()/notify()/notifyAll()等方法完成等待和通知间交互,提高程序的可伸缩性。
2、实现案例
通过线程通信解决上述数据生成和存储任务的解耦流程。
public class NotifyThread01 {
static Object lock = new Object() ;
static volatile List<String> dataList = new ArrayList<>();
public static void main(String[] args) throws Exception {
Thread saveThread = new Thread(new SaveData(),"SaveData");
saveThread.start();
TimeUnit.SECONDS.sleep(3);
Thread dataThread = new Thread(new AnalyData(),"AnalyData");
dataThread.start();
}
// 等待数据生成,保存
static class SaveData implements Runnable {
@Override
public void run() {
synchronized (lock){
while (dataList.size()==0){
try {
System.out.println(Thread.currentThread().getName()+"等待...");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("SaveData .."+ dataList.get(0)+dataList.get(1));
}
}
}
// 生成数据,通知保存
static class AnalyData implements Runnable {
@Override
public void run() {
synchronized (lock){
dataList.add("hello,");
dataList.add("java");
lock.notify();
System.out.println("AnalyData End...");
}
}
}
}
注意:除了dataList满足写条件,还要在AnalyData线程执行通知操作。
三、管道流通信
1、管道流简介
基本概念
管道流主要用于在不同线程间直接传送数据,一个线程发送数据到输出管道,另一个线程从输入管道中读取数据,进而实现不同线程间的通信。
实现分类
管道字节流:PipedInputStream和PipedOutputStream;
管道字符流:PipedWriter和PipedReader;
新IO管道流:Pipe.SinkChannel和Pipe.SourceChannel;
2、使用案例
public class NotifyThread02 {
public static void main(String[] args) throws Exception {
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
// 链接输入流和输出流
pos.connect(pis);
// 写数据线程
new Thread(new Runnable() {
public void run() {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 将从键盘读取的数据写入管道流
PrintStream ps = new PrintStream(pos);
while (true) {
try {
System.out.print(Thread.currentThread().getName());
ps.println(br.readLine());
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}, "输入数据线程:").start();
// 读数据线程
new Thread(new Runnable() {
public void run() {
BufferedReader br = new BufferedReader(new InputStreamReader(pis));
while (true) {
try {
System.out.println(Thread.currentThread().getName() + br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}, "输出数据线程:").start();
}
}
写线程向管道流写入数据,读线程读取数据,完成基本通信流程。
四、生产消费模式
1、业务场景
基于线程等待通知机制:实现工厂生产一件商品,通知商店卖出一件商品的业务流程。
2、代码实现
public class NotifyThread03 {
public static void main(String[] args) {
Product product = new Product();
ProductFactory productFactory = new ProductFactory(product);
ProductShop productShop = new ProductShop(product);
productFactory.start();
productShop.start();
}
}
// 产品
class Product {
public String name ;
public double price ;
// 产品是否生产完毕,默认没有
boolean flag ;
}
// 产品工厂:生产
class ProductFactory extends Thread {
Product product ;
public ProductFactory (Product product){
this.product = product;
}
@Override
public void run() {
int i = 0 ;
while (i < 20) {
synchronized (product) {
if (!product.flag){
if (i%2 == 0){
product.name = "鼠标";
product.price = 79.99;
} else {
product.name = "键盘";
product.price = 89.99;
}
System.out.println("产品:"+product.name+"【价格:"+product.price+"】出厂...");
product.flag = true ;
i++;
// 通知消费者
product.notifyAll();
} else {
try {
// 进入等待状态
product.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
// 产品商店:销售
class ProductShop extends Thread {
Product product ;
public ProductShop (Product product){
this.product = product ;
}
@Override
public void run() {
while (true) {
synchronized (product) {
if (product.flag == true ){
System.out.println("产品:"+product.name+"【价格"+(product.price*2)+"】卖出...");
product.flag = false ;
product.notifyAll(); //唤醒生产者
} else {
try {
product.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
流程描述:ProductFactory生成一件商品,通知商店售卖,通过flag标识判断控制是否进入等待状态,商店卖出商品后,再次通知工厂生产商品。
======================================================================================
一、Servlet简介
Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。使用Servlet,可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。
二、实现方式
1、继承HttpServlet
- API简介
继承自 GenericServlet. 遵守 HTTP协议实现,以设计模式的角度看,HttpServlet担任抽象模板角色,模板方法:由service()方法担任。基本方法:由doPost()、doGet()等方法担任。service()方法流程,省略了部分判断逻辑。该方法调用七个do方法中的一个或几个,完成对客户端请求的响应。这些do方法需要由HttpServlet的具体子类提供,这种API封装是典型的模板方法模式。
- 代码案例
public class ServletOneImpl extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("执行:doGet");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("执行:doPost");
}
}
2、继承GenericServlet
- API 简介
Servlet 接口和 ServletConfig 接口的实现类. 一个抽象类. 其中的 service 方法为抽象方法。
- 代码案例
public class ServletTwoImpl extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException {
HttpServletResponse response = (HttpServletResponse)servletResponse ;
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("执行:service");
}
}
3、实现Servlet接口
- API 简介
Servlet是一个接口,其中包含init、getServletConfig、service、getServletInfo、destroy几个核心方法。
- 代码案例
public class ServletThreeImpl implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
servletConfig.getServletName();
System.out.println("init 被调用...");
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException {
System.out.println("ThreadId:"+Thread.currentThread().getId());
System.out.println("service 被调用...");
HttpServletResponse response = (HttpServletResponse)servletResponse ;
response.getWriter().print("Servlet.Life");
}
@Override
public void destroy() {
System.out.println("destroy 被调用...");
}
@Override
public ServletConfig getServletConfig() {
System.out.println("getServletConfig 被调用...");
return null;
}
@Override
public String getServletInfo() {
System.out.println("getServletInfo 被调用...");
return null;
}
}
三、生命周期
- 加载和实例化
当Servlet容器启动或客户端发送请求时,Servlet容器会查找是否存在该Servlet实例,若存在,则直接读取该实例响应请求;如果不存在,就创建一个Servlet实例(属于单例设计模式)。load-on-startup 可以配置创建时序。
- 初始化:init()
实例化后,Servlet容器将调用init方法一次,初始化当前 Servlet。
- 服务:service()
初始化后,Servlet处于响应请求的就绪状态。当接收到客户端请求时,调用service()的方法处理客户端请求,HttpServlet的service()方法会根据不同的请求 调用不同的模板方法。
- 销毁:destroy()
当Servlet容器关闭时,Servlet实例也随时销毁。关闭 Tomcat 服务时可以通过日志打印看到该方法的执行。
四、运行配置
1、web.xml配置
<servlet>
<servlet-name>servletOneImpl</servlet-name>
<servlet-class>com.node01.servlet.impl.ServletOneImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletOneImpl</servlet-name>
<url-pattern>/servletOneImpl</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>servletTwoImpl</servlet-name>
<servlet-class>com.node01.servlet.impl.ServletTwoImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletTwoImpl</servlet-name>
<url-pattern>/servletTwoImpl</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>servletThreeImpl</servlet-name>
<servlet-class>com.node01.servlet.impl.ServletThreeImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletThreeImpl</servlet-name>
<url-pattern>/servletThreeImpl</url-pattern>
</servlet-mapping>
请求:http://localhost:6003/servletOneImpl
测试。
- servlet-name:Servlet 注册名称。
- servlet-class:Servlet 全路径类名。
- serlvet-mapping:同一个Servlet可以被映射到多个URL上。
- url-pattern:Servlet 访问的映射路径。
2、线程池运行
观察上述第三种Servlet实现方式的日志打印:Thread.currentThread().getId());
。
ThreadId:32
ThreadId:33
ThreadId:32
ThreadId:31
ThreadId:32
这里不难发现,Servlet以线程池的方式执行的。
======================================================================================
一、核心API简介
1、Servlet执行流程
Servlet是JavaWeb的三大组件之一(Servlet、Filter、Listener),它属于动态资源。Servlet的作用是处理请求,服务器会把接收到的请求交给Servlet来处理,在Servlet中通常需要:接收请求数据;处理请求;完成响应。
2、核心API简介
API | 作用描述 |
---|---|
ServletConfig | 获取servlet初始化参数和servletContext对象。 |
ServletContext | 在整个Web应用的动态资源之间共享数据。 |
ServletRequest | 封装Http请求信息,在请求时创建。 |
ServletResponse | 封装Http响应信息,在请求时创建。 |
二、ServletConfig接口
1、接口简介
容器在初始化servlet时,为该servlet创建一个servletConfig对象,并将这个对象通过init()方法来传递并保存在此Servlet对象中。核心作用:1.获取初始化信息;2.获取ServletContext对象。
2、代码案例
- 配置文件
<servlet>
<init-param>
<param-name>my-name</param-name>
<param-value>cicada</param-value>
</init-param>
<servlet-name>servletOneImpl</servlet-name>
<servlet-class>com.node02.servlet.impl.ServletOneImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletOneImpl</servlet-name>
<url-pattern>/servletOneImpl</url-pattern>
</servlet-mapping>
- API用法
public class ServletOneImpl implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
String servletName = servletConfig.getServletName() ;
System.out.println("servletName="+servletName);
String myName = servletConfig.getInitParameter("my-name") ;
System.out.println("myName="+myName);
Enumeration paramNames = servletConfig.getInitParameterNames() ;
while (paramNames.hasMoreElements()){
String paramKey = String.valueOf(paramNames.nextElement()) ;
String paramValue = servletConfig.getInitParameter(paramKey) ;
System.out.println("paramKey="+paramKey+";paramValue="+paramValue);
}
ServletContext servletContext = servletConfig.getServletContext() ;
servletContext.setAttribute("cicada","smile");
}
}
三、ServletContext接口
1、接口简介
一个项目只有一个ServletContext对象,可以在多个Servlet中来获取这个对象,使用它可以给多个Servlet传递数据,该对象在Tomcat启动时就创建,在Tomcat关闭时才会销毁!作用是在整个Web应用的动态资源之间共享数据。
- 获取方式
1、ServletConfig#getServletContext();
2、GenericServlet#getServletContext();
3、HttpSession#getServletContext()
4、ServletContextEvent#getServletContext()
2、四大域对象
ServletContext是JavaWeb四大域对象之一:
1、PageContext;
2、ServletRequest;
3、HttpSession;
4、ServletContext;
所有域对象都有存取数据的功能,因为域对象内部有一个Map,用来存储数据。
3、代码案例
- 配置文件
<context-param>
<param-name>my-blog</param-name>
<param-value>2019-11-19</param-value>
</context-param>
<servlet>
<servlet-name>servletTwoImpl</servlet-name>
<servlet-class>com.node02.servlet.impl.ServletTwoImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletTwoImpl</servlet-name>
<url-pattern>/servletTwoImpl</url-pattern>
</servlet-mapping>
- API用法
public class ServletTwoImpl extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
// 1、参数传递
ServletContext servletContext = this.getServletContext() ;
String value = String.valueOf(servletContext.getAttribute("cicada")) ;
System.out.println("value="+value);
// 2、获取初始化参数
String myBlog = servletContext.getInitParameter("my-blog") ;
System.out.println("myBlog="+myBlog);
// 3、获取应用信息
String servletContextName = servletContext.getServletContextName() ;
System.out.println("servletContextName="+servletContextName);
// 4、获取路径
String pathOne = servletContext.getRealPath("/") ;
String pathTwo = servletContext.getRealPath("/WEB-INF/") ;
System.out.println("pathOne="+pathOne+";pathTwo="+pathTwo);
response.getWriter().print("执行:doGet; value:"+value);
}
}
四、ServletRequest接口
1、接口简介
HttpServletRequest接口继承ServletRequest接口,用于封装请求信息,该对象在用户每次请求servlet时创建并传入servlet的service()方法,在该方法中,传入的servletRequest将会被强制转化为HttpservletRequest对象来进行HTTP请求信息的处理。核心作用:1.获取请求报文信息;2.获取网络连接信息;3.获取请求域属性信息。
2、代码案例
- 配置文件
<servlet>
<servlet-name>servletThreeImpl</servlet-name>
<servlet-class>com.node02.servlet.impl.ServletThreeImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletThreeImpl</servlet-name>
<url-pattern>/servletThreeImpl</url-pattern>
</servlet-mapping>
- API用法
public class ServletThreeImpl extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// http://localhost:6003/servletThreeImpl?myName=cicada
String method = request.getMethod();
System.out.println("method="+method); // GET
String requestURL = request.getRequestURL().toString();
// http://localhost:6003/servletThreeImpl
System.out.println("requestURL="+requestURL);
String requestURI = request.getRequestURI();
System.out.println("requestURI="+requestURI); // /servletThreeImpl
String queryString = request.getQueryString() ;
System.out.println("queryString="+queryString); // myName=cicada
String myName = request.getParameter("myName");
System.out.println("myName="+myName); // cicada
}
}
五、ServletResponse接口
1、接口简介
HttpServletResponse继承自ServletResponse,封装了Http响应信息。客户端每个请求,服务器都会创建一个response对象,并传入给Servlet.service()方法。核心作用:1.设置响应头信息;2.发送状态码;3.设置响应正文;4.重定向;
2、代码案例
- 配置文件
<servlet>
<servlet-name>servletFourImpl</servlet-name>
<servlet-class>com.node02.servlet.impl.ServletFourImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletFourImpl</servlet-name>
<url-pattern>/servletFourImpl</url-pattern>
</servlet-mapping>
- API用法
public class ServletFourImpl extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8") ;
response.setCharacterEncoding("UTF-8");
response.setStatus(200) ;
response.getWriter().print("Hello,知了");
}
}
3、转发和重定向
- 转发
服务器端进行的页面跳转的控制 ;
request.getRequestDispatcher("/转发地址").forward(request, response);
- 重定向
服务端响应跳转信息,浏览器端进行的页面跳转 ;
response.sendRedirect("重定向地址");
- 转发和重定向对比
区别 | 转发 | 重定向 |
---|---|---|
地址栏 | 不变 | 变化 |
跳转 | 服务端跳转 | 浏览器端跳转 |
请求次数 | 一次 | 两次 |
域中数据 | 不会丢失 | 丢失 |
======================================================================================
一、异常简介
优秀的程序代码,都在追求高效,安全,和低错误率,但是程序中的异常是无法避免的,降低异常出现的频率是关键,异常出现如何处理是另一个重要方面,Java体系中异常框架对于系统开发是十分重要的。
面对系统异常时,不要慌乱,异常虽然是错误,也是系统发出的消息,标识系统的缺陷和需要改进的地方。
二、API体系
Java的API中已经定义许多异常类,分为两大类,错误Error和异常Exception,Throwable作为所有异常的超类,如图:
Error:一般为底层的不可恢复的类,一般此类错误都比较严重,JVM将终止其运行的线程;
- VirtualMachineError:虚拟机运行错误;
- OutOfMemoryError:内存溢出;
Exception:程序本身可以捕获并且可以预处理的异常,例如捕获或者抛出;
- RuntimeException:运行时异常;
- CheckException:已检查异常,编译阶段必须处理;
几个经典的常见的RunTimeException如下:空指针NullPointerException;数组下标越界ArrayIndexoutofBoundsException等。
三、异常处理
Java异常处理关键字,分别是:try、catch、finally、throw、throws。
应该在合适的位置处理异常,异常的处理准则如下:谁知情谁处理,谁负责谁处理,谁导致谁处理。
1、抛出异常
即异常在当前流程下不处理,一种是直接通过方法传递给调用者,throws关键字是用于在方法声明上声明抛出异常类型的,并且一次可以声明抛出多种类型的异常。throw关键字是用于方法的内部抛出一个异常对象,常在业务校验时抛出提示。
需要特别说明的一点,在Spring框架中,事务触发多数是以是否抛出异常为标识来处理的,如果方法在事务控制内,方法内异常捕获但是最终没有抛出,那该事务则无效。
2、捕获异常
通常捕获异常会使用try-catch-finally关键字三连操作:
Try尝试捕获异常:
如果语句依次执行结束,则跳过catch,在存在finally代码块时,则执行否则执行后续流程;
如果捕获异常,则匹配catch中的类型,如果没有与之匹配的catch类型,则该异常交给JVM处理,finally代码会被执行,流程之后的代码不会被执行;
如果捕获异常且存在相匹配的catch类型,则跳到catch代码块执行,finally代码会被执行,执行完finally代码块之后继续执行后续代码;
Catch匹配可能出现的异常类型,并在其中做补偿处理,例如出现异常情况,需要更新一个异常状态等,如果没有catch块,后必须跟finally块,处理资源释放;
Finally无论是否捕获异常,finally代码会被执行,也是面试中常见的异常问题之一,例如在finally代码块return,或者修改返回值等,主要涉及到值传递和引用传递方面。
3、异常日志
复杂的业务系统必备功能,异常日志体系,用来分析运行问题,作为系统不断优化的核心依据,通常会记录如下几块:
- 异常类型:分析异常发生的关键原因;
- 异常信息:通常会简单记录e.getMsg输出的内容;
- 异常位置:快速定位异常发生的位置[类.方法];
- 业务参数:特定业务参数场景才能复现的问题;
- 时间节点:有的并发问题是在特定时间段出现;
异常日志记录下来之后,还会定期进行任务分析,不断发现系统容易出问题的地方,然后再不断的改进和优化。
4、熔断降级
在微服务架构系统下,某个服务故障或者异常,触发熔断该服务,避免引发整个微服务链路异常,防止整个系统服务的雪崩。以此缓解服务器资源的的压力,以保证核心业务的正常运行。
======================================================================================
一、会话跟踪
1、场景描述
比如登录某个购物网站,身份识别成功后,在网站下单,支付 等操作,这些操作中当前登录用户信息必须是共享的,这样这些操作结果才能和登录用户做关联。
2、概念简介
可以把会话理解为客户端与服务器之间的一次交互,在一次交互中可能会包含多次请求和响应。在JavaWeb中,从客户端向服务器发出第一个请求开始,会话就开始了,直到客户端关闭浏览器会话结束。在一个会话的多个请求中共享数据,这就是会话跟踪技术。
二、Cookie用法详解
1、Cookie简介
Cookie在HTTP中通常是用来辨别用户身份,进行会话跟踪而储存在用户本地终端上的数据,一般会加密处理,由用户客户端计算机暂时或永久保存的信息。其结构就是一个键和一个值构成的。随着服务器端的响应发送给客户端浏览器。然后客户端浏览器会把Cookie保存起来,当下一次再访问服务器时把Cookie再发送给服务器。
Cookie是由服务器创建,然后通过响应发送给客户端的键值对。客户端会保存Cookie,并会标注出Cookie的来源。当客户端向服务器发出请求时会把Cookie包含在请求中发送给服务器,这样服务器就可以识别客户端。
2、Cookie用法
- 创建Cookie
JavaWeb中,可以基于Servlet创建Cookie,并设置属性。
public class CookieServletOne extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
// 创建Cookie
Cookie cookie = new Cookie("author","cicada");
// 设置生命周期 1小时
cookie.setMaxAge(60*60);
response.addCookie(cookie) ;
response.getWriter().print("Hello:Cookie");
}
}
访问:http://localhost:6002/cookieServletOne
查看响应头:
Response Header
Set-Cookie: author=cicada; Max-Age=3600;
这样,服务器创建的Cookie在客户端就拿到了。
- 获取Cookie
public class CookieServletOne extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("doPost...");
Cookie[] cookies = request.getCookies() ;
for (Cookie cookie:cookies){
System.out.println("Name:"+cookie.getName());
System.out.println("Value:"+cookie.getValue());
}
response.setContentType("text/html;charset=utf-8");
String userName = request.getParameter("userName") ;
response.getWriter().print("Hello:"+userName);
}
}
通过测试,控制台输出:Name:author;Value:cicada
。
- 更新Cookie
更新就是指Cookie的覆盖,如果服务器端发送重复的Cookie那么会覆盖原有的Cookie。
public class CookieServletTwo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
// 创建Cookie
Cookie cookie = new Cookie("author","smile");
// 设置生命周期 2小时
cookie.setMaxAge(60*60*2);
response.addCookie(cookie) ;
response.getWriter().print("Hello:Cookie");
}
}
可以通过上面方法测试Cookie的获取结果。
- 删除Cookie
cookie.setMaxAge(0):生命等于0是一个特殊的值,它表示cookie被作废。
public class CookieServletTwo extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
Cookie[] cookies = request.getCookies() ;
for (Cookie cookie:cookies){
if (cookie.getName().equals("author")){
cookie.setMaxAge(0);
response.addCookie(cookie);
}
}
String userName = request.getParameter("userName") ;
response.getWriter().print("Hello:"+userName);
}
}
这样再测试Cookie的获取方法,发现上面删除的Cookie就没有了。
3、Cookie相关API
- setMaxAge()
设置 cookie 过期的时间,秒为单位。默认情况cookie 只会在当前 session 会话中有效。
- getMaxAge()
获取 cookie 的最大生存周期。
- getName()
获取 cookie 的名称。名称在创建后不能改变。
- getValue()
获取与 cookie 关联的值。
- setValue(String value)
设置与cookie关联的value值。相同的name多次设置会覆盖。
三、Session 跟踪
1、Session简介
会话管理,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。Servlet中可以把一个会话内需要共享的数据保存到HttSession对象中。四大域对象:PageContext、ServletRequest、HttpSession、ServletContext。
2、Session运行原理
- 首次使用
首次使用session时,服务器端要创建session,session是保存在服务器端,数据是保存在session中,sessionId通过Cookie发送给客户端,且只在浏览器本次会话中存在,也就是说如果用户关闭了浏览器,那么这个Cookie就丢失。
- 客户端访问
客户端再次访问服务器时,在请求中会带上sessionId,服务器会通过sessionId找到对应的session,而无需再创建新的session。
- 时效性
当一个session长时间没人使用的话,服务器会把session删除了,这个时长在Tomcat中配置是30分钟,可以在${CATALANA}/conf/web.xml找到这个配置,也可以在的web.xml中覆盖这个配置!
<session-config>
<session-timeout>30</session-timeout>
</session-config>
3、相关API用法
- getSesssion()
当前会话已经存在session对象那么直接返回,如果当前会话还不存在,创建session对象并返回 。
- getAttribute(String name)
返回该 session 会话中具有指定名称的对象 。
- getId()
分配给该 session 会话的唯一标识符的字符串。
- setAttribute(String name,Object value)
使用指定的名称绑定一个对象到该 session 会话。
- removeAttribute(String name)
从该 session 会话移除指定名称的对象。
4、应用案例
在网站中,经常可见的一个功能就是上次登录时间,这个功能基于Session可以很便捷的实现。
public class SessionServletOne extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
HttpSession session = request.getSession(true) ;
Date createTime = new Date(session.getCreationTime());
Date lastAccessTime = new Date(session.getLastAccessedTime());
session.setAttribute("author","cicada");
response.getWriter().print(
"SessionId:" + session.getId() + "<br/>"+
"User Author:" + session.getAttribute("author")+"<br/>"+
"Create Time:" + dateFormat.format(createTime)+"<br/>"+
"Last Access Time:"+dateFormat.format(lastAccessTime));
}
}
访问http://localhost:6002/sessionServletOne
页面打印,多次访问,查看效果。
SessionId:40C12C367CBFA7469D57E72C5C091300
User Author:cicada
Create Time:2019-12-14 15:34:10
Last Access Time:2019-12-14 15:35:13
======================================================================================
一、Listener监听器
1、概念简介
JavaWeb三大组件:Servlet,Listener,Filter。监听器就是指在应用程序中监听相关对象状态变化的组件。
2、事件源对象
指被监听对象。
- ServletContext
ServletContextListener
生命周期监听,它有两个方法,出生时调用contextInitialized()
,销毁时调用contextDestroyed()
;
ServletContextAttributeListener
属性监听,它有三个方法,添加属性attributeAdded()
,替换属性attributeReplaced()
,移除属性时attributeRemoved()
。
- HttpSession
HttpSessionListener
生命周期监听:它有两个方法,出生时调用sessionCreated()
,销毁时调用sessionDestroyed()
;
HttpSessioniAttributeListener
属性监听:它有三个方法,添加属性attributeAdded()
,替换属性attributeReplaced()
,移除属性attributeRemoved()
。
- ServletRequest
ServletRequestListener
生命周期监听:它有两个方法,出生时调用requestInitialized()
,销毁时调用requestDestroyed()
;
ServletRequestAttributeListener
属性监听:它有三个方法,添加属性attributeAdded()
,替换属性attributeReplaced()
,移除属性attributeRemoved()
。
3、编码案例
- 相关监听器
TheContextListener
public class TheContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("初始化:TheContextListener");
ServletContext servletContext = servletContextEvent.getServletContext() ;
servletContext.setAttribute("author","cicada");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("销毁:TheContextListener");
}
}
TheRequestListener
public class TheRequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
System.out.println("初始化:TheRequestListener");
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println("销毁:TheRequestListener");
}
}
TheSessionListener
public class TheSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
System.out.println("初始化:TheSessionListener");
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
System.out.println("销毁:TheSessionListener");
}
}
RequestAttributeListener
public class RequestAttributeListener implements ServletRequestAttributeListener {
@Override
public void attributeAdded(ServletRequestAttributeEvent evt) {
System.out.println("Request添加属性:"+evt.getName()+";"+evt.getValue());
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent evt) {
System.out.println("Request移除属性:"+evt.getName()+";"+evt.getValue());
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent evt) {
System.out.println("Request替换属性:"+evt.getName()+";"+evt.getValue());
}
}
- web.xml配置文件
<!-- 监听器相关配置 -->
<listener>
<listener-class>com.node05.servlet.listener.TheContextListener</listener-class>
</listener>
<listener>
<listener-class>com.node05.servlet.listener.TheSessionListener</listener-class>
</listener>
<listener>
<listener-class>com.node05.servlet.listener.TheRequestListener</listener-class>
</listener>
<listener>
<listener-class>com.node05.servlet.listener.RequestAttributeListener</listener-class>
</listener>
<session-config>
<!-- 设置session失效时间为1分钟 -->
<session-timeout>1</session-timeout>
</session-config>
- 测试接口
public class ListenerServletImpl extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
// 1、获取TheContextListener初始化数据
ServletContext servletContext = this.getServletContext() ;
String author = String.valueOf(servletContext.getAttribute("author")) ;
System.out.println("TheContextListener Author:"+author);
// 2、Request属性设置
request.setAttribute("mood","smile");
request.setAttribute("mood","agitated");
// 3、Session创建,1分钟失效,调用销毁
HttpSession session = request.getSession(true) ;
session.setAttribute("casually","casually");
response.getWriter().print("Hello:Listener");
}
}
二、Filter过滤器
1、过滤器简介
客户端请求Servlet时,先执行相关Filter,如果Filter通过,则继承执行请求的Servlet;如果Filter不通过,则不会执行用户请求的Servlet。过滤器可以动态地拦截请求和响应。
2、Filter接口
Filter接口定义了三个核心方法。
- init()
应用程序启动时,服务器实例化Filter对象,并调用其init方法,读取web.xml配置,完成对象的初始化加载。
- doFilter()
实际的过滤操作,请求达到服务器时,Servlet容器将先调用过滤器的doFilter方法。
- destroy()
容器在销毁过滤器前调用该方法,释放过滤器占用的资源。
3、编码案例
- 编写过滤器
public class ThePrintLogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String myName = filterConfig.getInitParameter("myName") ;
System.out.println("myName:"+myName);
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest ;
HttpServletResponse response = (HttpServletResponse)servletResponse ;
String name = request.getParameter("name") ;
if (!name.equals("cicada")){
response.getWriter().print("User Error !");
return ;
}
chain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
System.out.println("ThePrintLogFilter destroy()");
}
}
- web.xml配置文件
<!-- 过滤器相关配置 -->
<filter>
<filter-name>thePrintLogFilter</filter-name>
<filter-class>com.node05.servlet.filter.ThePrintLogFilter</filter-class>
<init-param>
<param-name>myName</param-name>
<param-value>cicada</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>thePrintLogFilter</filter-name>
<url-pattern>/filterServletImpl</url-pattern>
</filter-mapping>
- 测试接口
public class FilterServletImpl extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("Hello:Filter");
}
}
三、Interceptor拦截器
Spring框架中的拦截器Interceptor类似于Servlet中的过滤器Filter,主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。请求转发不执行拦截、过滤;重定向执行拦截和过滤。
======================================================================================
一、基础概念
1、单服务
所有业务服务和应用组件部署在一台服务上,节省成本,这是单服务结构,适用于并发低,业务单一的场景。
2、集群模式
业务量逐渐增大,并发高,把一台服务进行水平扩展,做一个服务群,请求压力分散到不同的服务上处理,每台服务称为集群的一个节点,到这就是集群服务。
3、分布式架构
分布式结构就是按照业务功能,拆分成独立的子服务,独立的库表,可以独立运行,且服务之间通信和交互,带来的好处降低业务间的耦合度,方便开发维护,水平扩展,复用性高等等。
4、技术体系
服务基础架构:Dubbo框架,SpringCloud框架;
容器化运维:Docker、Kubernetes;
数据存储:关系型MySQL,NoSQL数据库,OLAP引擎;
常用组件:Zookeeper协调,MQ异步,Redis缓存;
二、分布式框架
1、Dubbo框架
垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。Dubbo框架的核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
2、SpringCloud框架
分布式架构下最成熟的框架,SpringCloud是一系列框架的有序集合。它利用SpringBoot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用SpringBoot的开发风格做到一键启动和部署。
https://github.com/cicadasmile/spring-cloud-base
核心组件
注册中心:具备服务发现、服务记录、查询、动态管理的机制。常用的注册中心,Zookeeper、Eureka、Consul、Nacos等。
熔断降级:限制流量突然高并发冲垮系统,使这类报文以比较均匀的速度流动发送,达到保护系统相对稳定的目的。常用算法令牌桶、漏斗;常用组件Nginx、CDN、Hystrix、Sentinel,通过不同节点控制流量。
服务网关:在整个架构体系上也是一个服务,作为请求的唯一入口,与外观模式十分类似,在网关层处理所有的非业务功能,为客户端提供定制的API。常用组件Zuul、Tyk、Kong。
https://github.com/cicadasmile/husky-spring-cloud
3、业务型组件
消息中间件:RocktMQ、Kafka、RabbitMQ等;
缓存中间件:Redis、Eache等;
分布式事务:Seata、Hmily、TCC-transaction等;
三、架构细节
1、全局ID策略
业务场景产生的数据,都需要一个唯一ID作为核心标识,用来流程化管理。比如常见的:UUID、雪花算法、自增主键、ID容器等。
2、接口幂等性
幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。就是说,一次和多次请求某一个资源会产生同样的作用影响。在接口、重试、补偿的场景下尤其要保证操作的幂等性。
3、缓存处理
业务系统中,查询时最容易出现性能问题的模块,查询面对的数据量大,筛选条件复杂,所以在系统架构中引入缓存层,则是非常必要的,用来缓存热点数据、归档数据、首页查询等,达到快速响应的目的。
4、异步处理流程
异步是一种设计理念,异步操作不等于多线程,MQ中间件,或者消息广播,这些是可以实现异步处理的方式,异步处理不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程。
5、高并发与资源锁
高并发业务核心还是流量控制,控制流量下沉速度,或者控制承接流量的容器大小,多余的直接溢出,这是相对复杂的流程。一方面可以通过流量整形的方式解决请求量,另一方面可以通过加锁解决并发访问资源的问题。
6、分布式事务
不同的服务不同数据库的多个细节操作组成,这些无感知的细节操作分布在不同服务上,甚至属于不同的地区和应用,事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点,如何保证这些操作全部成功或者全部失败,即保证不同数据库间的数据一致性,这就是分布式事务需要解决的核心问题。
https://github.com/cicadasmile/data-manage-parent
四、数据源组件
1、关系型数据库
采用了关系模型来组织数据的数据库,其以行和列的形式存储数据,例如MySQL、Oracle。在分布式系统下,为了保证核心流程的稳定性,在关键业务上基本都采用关系型数据库,当业务完成后,如果数据量大,会把数据同步到其他查询性能高组件中。
2、NoSQL数据库
NoSQL意即"不仅仅是SQL"。对不同于传统的关系型数据库的数据库管理系统的统称。NoSQL用于超大规模数据的存储。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。例如MongoDB、Cassandra等。
3、数据管理策略
读写库分离、查询数据分库分表、分布式下业务分库、基于用户流量分库。
https://github.com/cicadasmile/data-manage-parent
五、服务监控
1、生产故障
在分布式的复杂架构下,应用服务、软件服务、硬件服务,任何层面出问题都可能导致请求不能完整执行,引发一系列效应,做好全链路的监控,快速定位问题是非常关键的。
2、应用层监控
应用层为开发的业务逻辑服务,也是最容易突发问题的一个层面,通常从请求流量、服务链路熔断、系统异常日志几个方面做监控指标,观察系统是否稳定。
3、软件层监控
这里通常指,数据库层面,例如Druid的监控分析;常用中间件,例如RocketMQ的控制台;Redis缓存:提供命令获取相关监控数据等。
4、硬件层监控
硬件层面,关注的三大核心内容:CPU、内存、网络。底层硬件资源爆发的故障,来自上层的应用服务或者中间件服务触发的可能性偏高。成熟的监控框架,例如zabbix,grafana等。
======================================================================================