第八周js-javaweb-servlet-jsp部分

JavaScript

JS入门

概述

JavaScript(简称JS),由netscape(网景)公司研发的一门客户端(浏览器)脚本语句,所谓脚本语言即需要嵌入到其他语言中运行,而无法独立运行(在NodeJS中可以直接运行javascript);javascript是基于事件驱动的,是一门弱类型语言,javascript不需要编译,是一门直译型语言。

强类型语言:在进行变量声明时,需要显示的指定该变量的数据类型,并且运行期间无法修改数据类型。

int i = 10;
String s = "hello";

i = "world"; // 编译错误

弱类型语言:在使用变量前不需要声明任何的数据类型,并且在运行期间可以任意的改变其数据类型(动态语句)

i = 10;
var s = "hello";

i = "world";//正常执行

直译型语言:程序编写完成之后无需编译,直接解释,常见的直译型语言包含:Javascript,python

JS和java的区别

  • java是强类型,js是弱类型
  • java是编译解释型,js是直译型
  • Java是服务端语言,js是客户端语言
  • java可以独立运行,js需要嵌入到客户端浏览器运行

Javascript组成部分

  • ECMAScript
  • DOM
  • BOM

JS基础语法

Javascript入门案例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>第一个javascript程序</title>
</head>
<body>

    <h1 id="h1">这是第一个JavaScript页面</h1>

    <!--推荐js编写位置,在body结束标签前-->
    <script>
        window.alert("hellojs1")
        document.write("hellojs2")
        console.log("hellojs3")
    </script>
</body>
</html>

JavaScript在html页面中使用三种方式

  1. 直接在html中使用script标签包裹js代码,参考以上入门案例

  2. 通过链接外部js文件引入

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>第一个javascript程序</title>
    </head>
    <body>
    
        <h1 id="h1">这是第一个JavaScript页面</h1>
    
      	<!-- 引入外部样式文件-->
        <script src="js/javascript01.js"></script>
    </body>
    </html>
    
    

    src和href区别:

    <link rel="stylesheets" href="css/index.css">
    <script src="js/index.js"></script>
    

    href:表示的是关联外部的样式文件,在外部样式文件中对其他资源的访问参考位置是样式文件所在位置

    src:替换当前位置的代码为js文件中的代码,在js中访问其他资源的时候参考的目标是当前的html文件

  3. 直接在内部html中使用js脚本

     <button onclick="alert('对不起,看不到片')">点我看片</button>
    

数据类型

JavaScript包含的数据类型主要分为以下:

  • number(整数类型和浮点类型)
  • string(字符和字符串类型)
  • boolean(布尔类型)
  • undefined(未定义)
  • object(对象类型)
    • null
    • 数组
    • 普通对象

分支与循环

javascript中的基础语法和其他语言(java,c,c++)非常类似,也包含分支和循环等特点

分支语句

javascript支持的分支包含:

  1. if…else…

  2. switch

    // javascript中的注释分为单行(//)和多行(/* */)
    // 条件分支
    // 判断一个变量age是否大于等于18
    var age = '18';
    if(age >= 18){
        alert('小帅哥,来玩啊啊!')
    }else{
        alert('小屁孩一边去。。。')
    }
    
    if(age){
        alert(age);
    }else{
        alert('age=0');
    }
    
    /*
                == 和 === 区别
                  == 用于判断运算符左右的的值是否一致,忽略数据类型
                  === 恒等于,除了判断运算数是否一致外还判断类型是否一致
            *
            * */
    if(age === 18){
        alert('是一个正确的年龄:'+age);
    }else{
        alert('不是一个正确的年龄');
    }
    
    //判断一个月份是属于哪一个季节
    var m = 10;
    switch(m){
        case 3:
        case 4:
        case 5:
            console.log('Spring...');
            break;
        case 6:
        case 7:
        case 8:
            console.log('Summer...');
            break;
        case 9:
        case 10:
        case 11:
            console.log('Autumn...');
            break;
        case 1:
        case 2:
        case 12:
            console.log('Winter...');
            break;
    }
    
    // 实现一个购物结算功能,根据购买的数量进行相应折扣,输出最终的价格
    // 商品单价 ¥10
    // 购买1件 不打折
    // 购买2~5件 打9折
    // 购买5件以上 打8折
    //单价
    var price = 10;
    //购买数
    var count = 5;
    if(count == 1){
        console.log(price);
    }else if(count>=2 && count < 5){
        console.log(count * price * 0.9);
    }else{
        console.log(count * price * 0.8);
    }
    

循环语句

js的循环分为四种类型:

  1. for循环
  2. while循环
  3. do…while循环
  4. for…in循环
for(var i = 1;i <= 10; i++){
    console.log(i);
}

console.log("====================")
    
var i = 1;
while(i <= 10){
    console.log(i);
    i++;
}

console.log("------------------");

do {
    i--;
    console.log(i);
}while(i>0);

console.log("+++++++++++++++++++++")
    
var arr = [10,20,30,40,50];
//for...in...
for(var a in arr){
    console.log(arr[a]);
}

函数与事件

函数

javascript中的函数类似于Java中方法

函数的声明语法:

function 函数名(参数名列表){
    //执行体
}

事件

javascript是一门基于事件驱动的语言,当html页面中的元素触发一些动作时,可以执行相关的js代码,常见的事件主要包含:

  1. 文档事件
  2. 鼠标事件
  3. 键盘事件

内置对象(续)

Date

在JavaScript中,Date对象用来表示日期和时间。

要获取系统当前时间,用:

var now = new Date();
now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
now.getFullYear(); // 2015, 年份
now.getMonth(); // 5, 月份,注意月份范围是0~11,5表示六月
now.getDate(); // 24, 表示24号
now.getDay(); // 3, 表示星期三
now.getHours(); // 19, 24小时制
now.getMinutes(); // 49, 分钟
now.getSeconds(); // 22, 秒
now.getMilliseconds(); // 875, 毫秒数
now.getTime(); // 1435146562875, 以number形式表示的时间戳

注意,当前时间是浏览器从本机操作系统获取的时间,所以不一定准确,因为用户可以把当前时间设定为任何值。

如果要创建一个指定日期和时间的Date对象,可以用:

var d = new Date(2015, 5, 19, 20, 15, 30, 123);
d; // Fri Jun 19 2015 20:15:30 GMT+0800 (CST)

JavaScript的Date对象月份值从0开始,牢记0=1月,1=2月,2=3月,……,11=12月。

第二种创建一个指定日期和时间的方法是解析一个符合ISO 8601格式的字符串:

var d = Date.parse('2015-06-24T19:49:22.875+08:00');
d; // 1435146562875

但它返回的不是Date对象,而是一个时间戳。不过有时间戳就可以很容易地把它转换为一个Date

var d = new Date(1435146562875);
d; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
d.getMonth(); // 5

使用Date.parse()时传入的字符串使用实际月份0112,转换为Date对象后getMonth()获取的月份值为011。

RegExp

<input type="text" name="" id="phone" placeholder="请输入手机号">
<button onclick="check()">验证</button>
<script>
    function check(){
        var input = document.getElementById('phone');
        //获取输入框值
        var phone = input.value;
        //正则表达式
        var regex = /^1[345789]\d{9}$/;
        if(regex.test(phone)){
            alert('手机号格式正确')
        }else{
            alert('格式不正确')
        }
    }
</script>

JSON对象

//json对象
var user = {
    id:101,
    name:'softeem',
    age:18,
    langs:['java','c++','python','javascript']
}

//json字符串
var user2 = "{\"id\":101,\"name\":\"softeem\",\"age\":18,\"langs\":[\"java\",\"javascript\"]}";
console.log(user);
console.log(user2);

//json字符串(来自后台)转换为json对象(javascript)
user2 = JSON.parse(user2);
console.log(user2);

//json对象序列化为json字符串
user = JSON.stringify(user);
console.log(user);

DOM

DOM(Document Object Model),文档对象模型;在javascript中将html中的所有元素看作为一颗倒置文档树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HvzGCl2A-1598186399902)(D:\带班资料\2020\j2003\线下\part4-web前端\20200817\笔记\assets\1597633651121.png)]

DOM对象

//document表示整个html文档
// document.head 获取html文档的头部
// document.body 获取html中body部分
//获取文档中的所有元素
var alls = document.all;
//获取文档中所有img节点
var imgs = document.images;
//获取文档中所有的a元素(超链接)
var links = document.links;
//获取文档中所有的表单对象
var forms = document.forms;

常见DOM操作

//根据元素的id获取元素(返回唯一结果)
var img = document.getElementById('img1');
//根据css选择器获取单个元素
var box = document.querySelector('.box>div>p');

//根据css选择器获取所有符合条件的元素
var inputs = document.querySelectorAll('.txt');
//根据class名称获取所有元素
var txt = document.getElementsByClassName('txt');

//根据标签名获取所有的元素
var div = document.getElementsByTagName('div');
//根据元素的name属性获取所有元素
var favs = document.getElementsByName('fav');

表单操作

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <input type="checkbox" name="fav" value="java">学java
        <input type="checkbox" name="fav" value="js">学javascript
        <input type="checkbox" name="fav" value="mysql">学数据库
        <input type="checkbox" name="fav" value="jdbc">学jdbc <br>
        <input type="checkbox" 
               name="" 
               id="selectAll" 
               onclick="selectAll(this.checked)">全选1
        
        <input type="checkbox" name="" id="selectAll2">全选2
        
        <script>
            //全选实现1
            function selectAll(isSelected){
                var cks = document.getElementsByName('fav');
                for(var i = 0;i<cks.length;i++){
                    cks[i].checked = isSelected;
                }
            }

          //全选实现2  document.getElementById('selectAll2').addEventListener('click',function(){
                var cks = document.getElementsByName('fav');
                for(var i = 0;i<cks.length;i++){
                    cks[i].checked = this.checked;
                }
            })

        </script>
    </body>
</html>

Audio

//创建音频元素
var player = document.createElement('audio');
//设置属性
player.src = 'music/taotai.mp3';

document.getElementById('btnPlay').onclick=function(){
    //播放
    player.play();
}
document.getElementById('btnPause').onclick=function(){
    //暂停播放
    player.pause();

}
//为播放器绑定当前播放时间改变事件,一旦currentTime值产生变化就执行以下函数
player.addEventListener('timeupdate',function(){
//获取当前播放进度(秒)
var now = player.currentTime;
//获取歌曲的总播放时长(秒)
var total = player.duration;
var w = now / total * 100 + '%';
    //调用dom元素的css属性
    document.querySelector('.progress').style.width = w;
})

//当歌曲播放完成之后执行
player.addEventListener('ended',function(){

})

javascript-BOM

BOM对象

BOM(Browser Object Model),javascript内置一些能够跟本机浏览器交互的对象,这些对象都隶属于顶层对象window

  1. 获取浏览器的版本相关信息使用navigator
  2. 获取浏览器窗口信息使用window
  3. 获取本机屏幕相关信息适应screen
  4. 获取导航信息,链接跳转使用location
  5. 需要获取历史记录以及跳转使用history

window

window对象是js中的全局作用域,也表示浏览器窗口信息

window对象中包含的常见属性:

  • innerHeight 获取浏览器内部高度

  • innnerWidth 获取浏览器内部宽度

  • outerHeight 获取浏览器外部高度

  • outerWidth 获取浏览器外部宽度

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KuUcNtAr-1598186399904)(D:\带班资料\2020\j2003\线下\part4-web前端\20200818\笔记\assets\1597712513131.png)]

window对象常见方法:

  • alert:警告框
  • confirm:确认框
  • prompt:输入提示框
  • open:打开新的窗口
  • setInterval
  • setTimeout

navigator

navigator对象表示浏览器的信息,最常用的属性包括:

  • navigator.appName:浏览器名称;
  • navigator.appVersion:浏览器版本;
  • navigator.language:浏览器设置的语言;
  • navigator.platform:操作系统类型;
  • navigator.userAgent:浏览器设定的User-Agent字符串。
console.log('appName = ' + navigator.appName);
console.log('appVersion = ' + navigator.appVersion);
console.log('language = ' + navigator.language);
console.log('platform = ' + navigator.platform);
console.log('userAgent = ' + navigator.userAgent);

请注意navigator的信息可以很容易地被用户修改,所以JavaScript读取的值不一定是正确的。很多初学者为了针对不同浏览器编写不同的代码,喜欢用if判断浏览器版本,例如:

var width;
if (getIEVersion(navigator.userAgent) < 9) {
    width = document.body.clientWidth;
} else {
    width = window.innerWidth;
}

但这样既可能判断不准确,也很难维护代码。正确的方法是充分利用JavaScript对不存在属性返回undefined的特性,直接用短路运算符||计算:

var width = window.innerWidth || document.body.clientWidth;

screen

screen对象表示屏幕的信息,常用的属性有:

  • screen.width:屏幕宽度,以像素为单位;
  • screen.height:屏幕高度,以像素为单位;
  • screen.colorDepth:返回颜色位数,如8、16、24。
console.log('Screen size = ' + screen.width + ' x ' + screen.height);

location

location对象表示当前页面的URL信息。例如,一个完整的URL:

http://www.example.com:8080/path/index.html?a=1&b=2#TOP

可以用location.href获取。要获得URL各个部分的值,可以这么写:

location.protocol; // 'http'
location.host; // 'www.example.com'
location.port; // '8080'
location.pathname; // '/path/index.html'
location.search; // '?a=1&b=2'
location.hash; // 'TOP'

要加载一个新页面,可以调用location.assign()。如果要重新加载当前页面,调用location.reload()方法非常方便。

if (confirm('重新加载当前页' + location.href + '?')) {
    location.reload();
} else {
    location.assign('/'); // 设置一个新的URL地址
}

history

history对象保存了浏览器的历史记录,JavaScript可以调用history对象的back()forward (),相当于用户点击了浏览器的“后退”或“前进”按钮。

这个对象属于历史遗留对象,对于现代Web页面来说,由于大量使用AJAX和页面交互,简单粗暴地调用history.back()可能会让用户感到非常愤怒。

新手开始设计Web页面时喜欢在登录页登录成功时调用history.back(),试图回到登录前的页面。这是一种错误的方法。

localStorage

// cookie :存在安全风险
// 在html5中新增了浏览器本地存储的api,主要包含两个对象:
// + localStorage    本地存储
// + sessionStorage  基于一次会话存储
// 以上两个对象存储数据的方式类似java中的Map:以键值对的形式存储数据
console.log(localStorage)
//向本地存储中存储数据
localStorage.setItem('name','softeem');
localStorage.setItem('sex','男');
//获取存储的元素个数
console.log(localStorage.length);
//获取本地存储的数据
console.log(localStorage.getItem('name'));

document.getElementById('del').addEventListener('click',function(){
    //移除某一个元素
    localStorage.removeItem('sex');
})
document.getElementById('clearAll').addEventListener('click',function(){
    //清除本地存储
    localStorage.clear();
})

//获取指定索引对应的键名
console.log(localStorage.key(1))

综合案例开发:音乐播放器客户端实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hiDwf1FL-1598186399908)(D:\带班资料\2020\j2003\线下\part4-web前端\20200818\笔记\assets\1597832594588.png)]

核心js代码

数据(data.js)
var musics = [
    {
        ablum: "海阔天空",
        artist: "Beyond",
        id: 1,
        name: "大地",
        path: "musics/1592373302464.mp3",
        size: 4147913,
        style: "摇滚",
        uptime: 1592373302000
    },
    {
        ablum: "xxx",
        artist: "GALA ",
        id: 2,
        name: "追梦赤子心",
        path: "musics/1592373330188.mp3",
        size: 8357216,
        style: "摇滚",
        uptime: 1592373330000
    },
    {
        ablum: "123",
        artist: "筷子兄弟",
        id: 3,
        name: "父亲",
        path: "musics/1592373363687.mp3",
        size: 12050851,
        style: "怀旧",
        uptime: 1592373363000
    }
]
功能实现
//创建音频播放器对象
var player = document.createElement('audio');
//记录当前正在播放的歌曲的索引
var currentIndex = 0;
//声明一个标记,记录歌曲的播放状态(是否播放)
var isplay = false;

//绑定数据到页面中
(function() {
    var html = '';
    //循环遍历歌曲列表,根据歌曲数目在页面中生成对应的html代码
    for (var i = 0; i < musics.length; i++) {
        var m = musics[i];
        //根据循环的次数创建对应的歌曲项
        html += `<tr class="music-item" data-index="${i}">
                <td class="tb-space" style="text-align: center"></td>
                <td><a href="javascript:;">${m.name}</a></td>
                <td><a href="javascript:;">${m.artist}</a></td>
                <td><a href="javascript:;">${m.ablum}</a></td>
                <td>${fmtSize(m.size)}</td>
                <td class="tb-space"></td> 
            </tr>`;
    }
    //将生成html插入到指定的dom节点中
    document.getElementById('tbody').innerHTML = html;
    //初始化播放源
    player.src=musics[currentIndex].path;
})();

//为列表项触发点击事件
var trs = document.querySelectorAll('.music-item');
for(var i = 0 ;i < trs.length;i++){

    trs[i].addEventListener('click',function(){
        //清除播放状态
        clearStatus();
        //获取元素上data-index属性的属性值(获取需要播放的歌曲列表项)
        var index = this.dataset.index;
        //记录当前需要播放的歌曲索引
        currentIndex = index;
        //获取需要播放的歌曲对象
        var m = musics[index];
        //为播放器设置播放源
        player.src = m.path;
        //开始播放
       startPlay();
    })
}
//开始播放
function startPlay(){
    //将状态标记为正在播放
    isplay = true;
    //播放
    player.play();
    //修改当前行的背景色
    trs[currentIndex].style.backgroundColor='#f0f0f0';
    trs[currentIndex].getElementsByTagName('td')[0].innerHTML = '<img src="imgs/playing.gif">';
    //将播放按钮的背景图片设置为暂停图标
    document.getElementById('btnPlay').className='btn-pause';
    //将正在播放的歌曲歌曲名显示到底部控制区域
    document.getElementById('playingName').innerText = musics[currentIndex].name;
}
//清除上一首正在播放的歌曲状态
function clearStatus(){
    //还原上一首正在播放的歌曲列表项背景色
    trs[currentIndex].style.backgroundColor='';
    //清除当前行下的第一个单元格的内容(清除图标)
    trs[currentIndex].getElementsByTagName('td')[0].innerHTML='';
}

//暂停播放
function pausePlay(){
    //将播放状态标记为false
    isplay = false;
    //暂停播放
    player.pause();
    //将当前播放按钮的类名称修改
    document.getElementById('btnPlay').className='btn-play';
}

//播放控制
document.getElementById('btnPlay').addEventListener('click',function(){
    if(isplay){
        pausePlay();
    }else{
        startPlay();
    }
})


//记录歌曲的当前播放时间
var now = 0;
//记录歌曲的总播放时长
var total = 0;
//当播放器数据被加载时触发
player.addEventListener('loadeddata',function(){
    //获取当前播放器的播放位置以及总播放时长(单位:秒)
    now = player.currentTime;
    total = player.duration;
    //将歌曲的播放时间显示到控制区域
    document.querySelector('.play-current').innerText = fmtTime(now);
    document.querySelector('.play-duration').innerText = fmtTime(total);
})

//为播放器绑定播放进度改版实践
player.addEventListener('timeupdate',function(){
    //获取最新的播放进度
    now = player.currentTime;
    //计算进度的百分比
    var p = now / total * 100 + '%';
    //为进度条元素设置宽度
    document.querySelector('.progress-control').style.width=p;
    //更新最新播放时间
    document.querySelector('.play-current').innerText = fmtTime(now);
})

//为播放器绑定播放完成事件
player.addEventListener('ended',function(){
    //清除上一首播放状态
    clearStatus();
    currentIndex++;
    if(currentIndex >= musics.length){
        currentIndex = 0;
    }
    //重新为播放器设置播放源
    player.src = musics[currentIndex].path;
    //继续播放
    startPlay();
})

//工具函数,格式化歌曲的播放时间为 mm:ss
function fmtTime(time){
    //将秒为单位时间转换为毫秒单位
    time *= 1000;
    //使用毫秒数构建一个日期对象
    time = new Date(time);
    var m = time.getMinutes();
    var s = time.getSeconds();
    m = m < 10 ? '0' + m : m;
    s = s < 10 ? '0' + s : s;
    return m + ':' + s;
}

//工具函数,格式化歌曲大小
function fmtSize(size){
    size = size / (1024*1024);
    size = size.toFixed(1);
    return size + 'MB';
}

JavaWeb入门

Web请求结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n58KURkK-1598186399916)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597885098877.png)]

JavaWeb体系介绍

  • web服务器
  • Servlet
  • JSP(轻量级Servlet)
  • JDBC

Tomcat服务器(软件)

什么是Web服务器

web服务器通常也称之为web容器,提供了对javaweb项目的运行环境,内部可以实现对客户端的请求接收,通过web应用中对应的java程序进行处理(访问数据库,进行业务逻辑操作等)之后,向客户端响应运行结果。

目前javaweb开发中常见的服务器包含以下几种:

  • tomcat(开源免费的web服务器)
  • jetty(开源免费的web服务器,常见于一些web聊天应用)
  • weblogic(Oracle开发收费的web服务器)
  • jboss(开源免费的web服务器,与EJB结合良好)

服务器和服务器软件

**服务器(硬件):**实际上就是一台主机(一台电脑),一般情况下服务器配置要求要高于普通的个人电脑(PC机);在服务器中通常运行的是一些服务软件(web服务器软件,mysql数据库服务,http服务器软件等)

**服务器软件:**是运行在服务器主机上的一些应用程序,比如tomcat服务器,mysql服务器等。

Tomcat

​ tomcat有apache(阿帕奇)开源组织对外开源免费的一个web容器,提供了对javaweb应用中jsp和servlet等web技术的运行环境,通过http协议接收以及响应客户端请求。

tomcat下载与安装

  1. 访问tomcat官网:http://tomcat.apache.org/

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-859nDDdo-1598186399917)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597886547214.png)]

  2. 下载tomcat

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qh2gApnU-1598186399918)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597886591850.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GAVrF0wP-1598186399920)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597886836787.png)]

启动与访问

  1. 由于tomcat是由java技术编写而成,因此需要在主机中具备java的运行环境,即配置JAVA_HOME(具体参考java环境变量配置)

  2. 打开tomcat解压缩之后的目录,找到bin目录:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aDWo09YD-1598186399921)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597887005250.png)]

  3. 找到bin目录中,名字叫startup.bat文件(linux下使用startup.sh)双击启动;

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2lzN5n9P-1598186399922)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597887135362.png)]

  4. 启动正常时,命令行没有任何错误信息,并显示如下信息

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SMfdXsMG-1598186399923)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597891204184.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FNVgCaDE-1598186399924)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597891381717.png)]

bat:windows中的批处理文件

目录结构

tomcat的目录结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RaXefaQ9-1598186399925)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597891645338.png)]

以上目录中,经常操作的主要包含以下:

conf:配置目录

webapps:web项目的发布目录

注意事项:

在tomcat访问应用程序时,如果不指定具体的资源,tomcat会默认请求名为index.htmlindex.htmindex.jsp等相关的资源,因为在conf/web.xml配置中默认有指定,并且该配置也允许程序员修改;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RBIvcRqp-1598186399926)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597895218636.png)]

常用配置

tomcat管理账号密码设置

当需要涉及到对tomcat中应用程序管理时,通常需要提供访问的账号密码,此时可以通过在conf/tomcat-user.xml进行用户信息配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IePtSe3v-1598186399927)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597894846652.png)]

以上配置中:

第一行:配置角色

第二行:配置用户并指定角色

端口号配置

​ 由于计算机中不同的网络应用需要对外通信就必须指定各自的访问端口,但是有可能端口会出现冲突;tomcat服务器默认占据的8080端口,即需要访问指定主机的服务器只需要通过主机名+端口即可;http协议默认端口是80,如果将tomcat服务器的端口配置为80,则访问时就无需手动指定端口号。

修改端口号

使用文本编辑器(推荐vscode)打开apache-tomcat-9.0.37>conf>server.xml,第69~71行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NAN0sl4W-1598186399928)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597891053160.png)]

将以上port值修改为指定数值即可

虚拟访问路径配置

​ 所谓虚拟访问路径配置,即将主机中指定的一些资源通过tomcat服务器配置虚拟访问路径之后对外提供可访问的能力

  1. 准备本地的文件夹(存储的需要对外公开的资源)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iqHriM8z-1598186399929)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597893946434.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-67dR9QOs-1598186399931)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597893964395.png)]

  2. 打开tomcat服务器所在目录的conf目录,打开server.xml文件,配置以下标签:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sp22ZRYb-1598186399932)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597894029537.png)]

    docBase:表示对外提供的资源所在的本机绝对路径

    path:设置虚拟的访问地址

  3. 打开浏览器,访问指定资源,浏览器地址栏中输入:http://192.168.7.2/mp3/2.mp3

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e0pCGJ6y-1598186399935)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597894116747.png)]

    地址栏中的资源路径:/mp3/2.mp3通过tomcat服务器解析之后找到服务器所在主机中虚拟路径映射的绝对路劲地址:

    D:\bak\2.mp3

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N7xpPORJ-1598186399936)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597894251521.png)]

配置Tomcat到系统服务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lcfNRfMx-1598186399938)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597896364059.png)]

安装完成后,可在系统服务中看到该服务(需刷新)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mp4WIiv9-1598186399939)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597896426666.png)]

Idea中集成Tomcat

Eclipse中集成Tomcat

  1. 确保Eclipse是JEE版(JavaSE无法集成,需要安装web插件才可以)

Javaweb项目创建

Idea中创建Web项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FmyoeE2E-1598186399941)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597903175986.png)]

部署web项目到服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lJknlTEc-1598186399942)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597903278797.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8pNR9tuK-1598186399943)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597903397068.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-em2zo6WP-1598186399943)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597903500931.png)]

Eclipse中创建web项目

运行javaweb应用的前提:

  1. 在开发工具中有集成Tomcat服务器(如果已经集成过了可以跳过)
  2. 创建javaweb项目
    1. idea中创建: web Application
    2. eclipse中创建:Dynamic Web Project
  3. 将web程序部署到服务器中
  4. 启动服务器

Servlet入门

Servlet 概述

Servlet(Server+Applet),服务端小程序;是一项运行在服务器端的java程序,可以接受来自客户端的http请求,并且对请求的信息作出相应。Servlet是一项接口技术,任何时候创建一个Servlet应用都必须从Servlet接口实现,实际上就是一个java类

Hello Servlet

创建一个Servlet包含三个基本步骤:

  1. 创建一个类从Servlet实现(继承HttpServlet)

    public class HelloServlet implements Servlet {
    
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {}
    
        @Override
        public ServletConfig getServletConfig() {return null;}
    
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            System.out.println("HelloServlet....");
        }
    
        @Override
        public String getServletInfo() {return null;}
    
        @Override
        public void destroy() {}
    }
    
    
  2. 实现service的方法

        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            //实现具体的操作
        }
    
  3. 配置Servlet(基于xml/基于注解)

    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.softeem.servlets.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    

    serlvet:

    配置servlet名称以及类路径

    servlet-mapping:

    servlet映射配置,配置servlet名称以及虚拟的访问路径

Servlet创建方式对比

  1. 实现javax.servlet.Servlet接口
  2. 继承javax.servlet.GenericServlet类
  3. 继承javax.servlet.HttpServlet类(推荐)

关于405错误状态码:

客户端请求服务端的method与服务端servlet接收处理的方法不一致,比如:

客户端发送的get请求,服务端使用doPost处理

Servlet执行原理

  1. 当web容器启动时,首先对web.xml初始化检查配置是否正确,比如url-pattern
  2. 客户端浏览器发起对servlet请求,通过请求的资源路径寻找对应的url-pattern
  3. 通过url-pattern对应的servlet-name寻找到servlet标签中的servlet-name
  4. 根据servlet-name所在标签找到servlet-class并且执行对应类中的service方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4dzlun7s-1598186399944)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200820\笔记\assets\1597910810065.png)]

Servlet生命周期

任何一个Servlet类从初始化到使用完毕销毁都有一套完整的执行流程,这一套执行流程我们称之为servlet的生命周期.

生命周期

  1. 当请求第一次到达时,容器会对被请求的servlet初始化,并调用init方法,完成初始操作,而且只会初始化一次(单例)
  2. 紧接着执行service方法,service默认会根据客户端的请求方法选择调用对应的doXXX方法
  3. 一旦web容器停止服务,此时所有的servlet都会被销毁,同时执行destroy完成收尾工作(先执行destroy再销毁)

load-on-startup

通过对servlet设置load-on-startup配置(默认值-1),可以控制servlet的初始化顺序,一旦为servlet指定了load-on-startup配置后,则即便,客户端不请求该servlet,web容器也会默认根据设置的数值大小对指定的servlet进行初始化,并调用init方法,关于配置方式分为两种:

  1. 基于注解的配置

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDj70apn-1598186399945)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200821\笔记\assets\1597974722172.png)]

  2. 基于xml文件配置(web.xml)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b6sezfK7-1598186399946)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200821\笔记\assets\1597974763125.png)]

Servlet配置详解

基于注解的servlet配置

servlet3.0之后允许使用注解的方式配置servlet,相比在web.xml配置来说,代码得到了极大的简化,具体使用方式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QCJ1kFzv-1598186399947)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200821\笔记\assets\1597982802628.png)]

客户端访问方式与之前保持一致:

http://localhost/javaweb02/msg

即可

url-pattern配置

//配置访问映射地址(虚拟访问路径)
//@WebServlet("/hello")
//@WebServlet({"/hello","/helloServlet","/HelloServlet"})
//@WebServlet("/user/hello")
//@WebServlet("/user/*")
//@WebServlet("/hello.do")
@WebServlet("*.do")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Hello....get");
    }
}

对于以上配置方式,对应的访问地址如下:

@WebServlet("/hello")
http://localhost/javaweb02/hello  

@WebServlet({"/hello","/helloServlet","/HelloServlet"})
http://localhost/javaweb02/hello
http://localhost/javaweb02/helloServlet
http://localhost/javaweb02/HelloServlet

@WebServlet("/user/hello")
http://localhost/javaweb02/user/hello

@WebServlet("/user/*")
http://localhost/javaweb02/user/XXX

@WebServlet("/hello.do")
http://localhost/javaweb02/hello.do

注意事项:

在一个web应用中不能出现重复的url-pattern,否则服务器启动时会抛出异常

ServletConfig与初始化参数

Idea配置Tomcat热部署

  1. 配置tomcat服务器使用热部署模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yxCulGni-1598186399949)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200821\笔记\assets\1597979864095.png)]

  1. 使用debug启动web应用

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L5cuTlwP-1598186399950)(D:\带班资料\2020\j2003\线下\part5-javaweb\20200821\笔记\assets\1597980105654.png)]

获取客户端提交数据

HttpServletRequest

HttpServletRequest对象表示客户端请求,当客户端通过Http协议请求到Servlet时,所有的请求数据都会封装到HttpServletRequest对象中,通过该对象提供的相关方法可以获取客户端提交过来的数据信息。

获得客户机信息:

方法解释
getRequestURL()返回客户端发出请求时的完整URL。
getRequestURI()返回请求行中的参数部分。
getQueryString ()方法返回请求行中的参数部分(参数名+值)
getRemoteHost()返回发出请求的客户机的完整主机名。
getRemoteAddr()返回发出请求的客户机的IP地址。
getPathInfo()返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以"/"开头。
getRemotePort()返回客户机所使用的网络端口号。
getLocalAddr()返回WEB服务器的IP地址。
getLocalName()返回WEB服务器的主机名。

获得客户机请求头

方法解释
getHeader(string name)以 String 的形式返回指定请求头的值。如果该请求不包含指定名称的头,则此方法返回 null。如果有多个具有相同名称的头,则此方法返回请求中的第一个头。头名称是不区分大小写的。可以将此方法与任何请求头一起使用
getHeaders(String name)以 String 对象的 Enumeration 的形式返回指定请求头的所有值
getHeaderNames()返回此请求包含的所有头名称的枚举。如果该请求没有头,则此方法返回一个空枚举

获得客户机请求参数

方法解释
getParameter(String name)根据name获取请求参数(常用)
getParameterValues(String name)根据name获取请求参数列表(常用)
getParameterMap()返回的是一个Map类型的值,该返回值记录着前端(如jsp页面)所提交请求中的请求参数和请求参数值的映射关系。(编写框架时常用)
getParameterNames()获取所有请求参数的name名称
案例
  1. 表单结构

    <form action="msg">
        用户名: <input type="text" name="name"><br>
        
        密码:<input type="password" name="pwd"><br>
        
        年龄:<input type="number" name="age"><br>
        
        <!--对于单选按钮,如果未指定value时,后台获取到的是on;如果未进行选择则获取null-->
        性别:<input type="radio" name="sex" id="sex_male" checked value=""><label for="sex_male"></label>
        <input type="radio" name="sex" id="sex_famale" value=""> <label for="sex_famale"></label> <br>
        
        兴趣爱好: <input type="checkbox" name="hobby" value="java">java
        <input type="checkbox" name="hobby" value="game">打游戏
        <input type="checkbox" name="hobby" value="doudou">打豆豆
        <input type="checkbox" name="hobby" value="sleep">睡觉 <br>
        
        所在城市:<select name="city">
        <option value="武汉">武汉</option>
        <option value="北京">北京</option>
        <option value="上海">上海</option>
        <option value="广州">广州</option>
        <option value="深圳">深圳</option>
        </select><br>
        
        个性签名: <textarea name="mark"  cols="50" rows="5"></textarea><br>
        
        出生日期: <input type="date" name="birth"><br>
    
        <button type="submit">注册</button>
    </form>
    
  2. servlet中doGet方法实现

    //设置请求编码(解决提交数据时的乱码问题)
    request.setCharacterEncoding("utf-8");
    //获取请求参数中指定参数名的参数值(参数即为表单控件的name属性)
    String name = request.getParameter("name");
    String pwd = request.getParameter("pwd");
    String age = request.getParameter("age");
    String sex = request.getParameter("sex");
    
    //        String hobby = request.getParameter("hobby");
    //获取多个参数名一致的参数值,并以数组的形式返回(复选框)
    String[] hobbies = request.getParameterValues("hobby");
    
    String city = request.getParameter("city");
    String mark = request.getParameter("mark");
    String birth = request.getParameter("birth");
    
    System.out.println(name);
    System.out.println(pwd);
    System.out.println(age);
    System.out.println(sex);
    
    for(String h:hobbies){
        System.out.println(h);
    }
    
    System.out.println(city);
    System.out.println(mark);
    System.out.println(birth);
    System.out.println("====================================");
    //获取请求中所有提交数据
    Map<String, String[]> parameterMap = request.getParameterMap();
    parameterMap.forEach((k,v)->{
        for(String s:v){
            System.out.println(k+"---->"+s);
        }
    });
    

Servlet进阶和Jsp

浅谈Servlet
重定向与转发
Redirect

重定向是指当浏览器请求一个URL时,服务器返回一个重定向指令,告诉浏览器地址已经变了,麻烦使用新的URL再重新发送新请求。

例如,我们已经编写了一个能处理/firstFirstServlet,如果收到的路径为/first,希望能重定向到/second,可以再编写一个SecondServlet

@WebServlet("/first")
public class FirstServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");
        resp.sendRedirect("/second"+(name==null?"":"?name="+name));
    }
}

如果浏览器发送GET /hi请求,RedirectServlet将处理此请求。由于RedirectServlet在内部又发送了重定向响应,因此,浏览器会收到如下响应:

HTTP/1.1 302 Found
Location: /hello

当浏览器收到302响应后,它会立刻根据Location的指示发送一个新的GET /hello请求,这个过程就是重定向:

┌───────┐   GET /hi     ┌───────────────┐
│Browser│ ────────────> │RedirectServlet│
│       │ <──────────── │               │
└───────┘   302         └───────────────┘


┌───────┐  GET /hello   ┌───────────────┐
│Browser│ ────────────> │ HelloServlet  │
│       │ <──────────── │               │
└───────┘   200 <html>  └───────────────┘

观察Chrome浏览器的网络请求,可以看到两次HTTP请求:

重定向有两种:一种是302响应,称为临时重定向,一种是301响应,称为永久重定向。两者的区别是,如果服务器发送301永久重定向响应,浏览器会缓存/hi/hello这个重定向的关联,下次请求/hi的时候,浏览器就直接发送/hello请求了。

重定向有什么作用?重定向的目的是当Web应用升级后,如果请求路径发生了变化,可以将原来的路径重定向到新路径,从而避免浏览器请求原路径找不到资源。

HttpServletResponse提供了快捷的redirect()方法实现302重定向。如果要实现301永久重定向,可以这么写:

resp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301
resp.setHeader("Location", "/second");
Forward

Forward是指内部转发。当一个Servlet处理请求的时候,它可以决定自己不继续处理,而是转发给另一个Servlet处理。

例如,我们已经编写了一个能处理/firstFirstServlet,继续编写一个能处理/secondSecondServlet

@WebServlet("/first")
public class FirstServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/second").forward(req,resp);
    }
}

FirstServlet在收到请求后,它并不自己发送响应,而是把请求和响应都转发给路径为/second的Servlet,即下面的代码:

req.getRequestDispatcher("/second").forward(req, resp);

后续请求的处理实际上是由HelloServlet完成的。这种处理方式称为转发(Forward),我们用流程图画出来如下:

                          ┌────────────────────────┐
                          │      ┌───────────────┐ │
                          │ ────>│ FirstServlet  │ │
┌───────┐  GET /first     │      └───────────────┘ │
│Browser│ ──────────────> │              │         │
│       │ <────────────── │              ▼         │
└───────┘    200 <html>   │      ┌───────────────┐ │
                          │ <────│ SecondServlet │ │
                          │      └───────────────┘ │
                          │       Web Server       │
                          └────────────────────────┘

转发和重定向的区别在于,转发是在Web服务器内部完成的,对浏览器来说,它只发出了一个HTTP请求:

使用Session和Cookie

在Web应用程序中,我们经常要跟踪用户身份。当一个用户登录成功后,如果他继续访问其他页面,Web程序如何才能识别出该用户身份?

因为HTTP协议是一个无状态协议,即Web应用程序无法区分收到的两个HTTP请求是否是同一个浏览器发出的。为了跟踪用户状态,服务器可以向浏览器分配一个唯一ID,并以Cookie的形式发送到浏览器,浏览器在后续访问时总是附带此Cookie,这样,服务器就可以识别用户身份。

Session

我们把这种基于唯一ID识别用户身份的机制称为Session。每个用户第一次访问服务器后,会自动获得一个Session ID。如果用户在一段时间内没有访问服务器,那么Session会自动失效,下次即使带着上次分配的Session ID访问,服务器也认为这是一个新用户,会分配新的Session ID。

JavaEE的Servlet机制内建了对Session的支持。我们以登录为例,当一个用户登录成功后,我们就可以把这个用户的名字放入一个HttpSession对象,以便后续访问其他页面的时候,能直接从HttpSession取出用户名:

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password= req.getParameter("password");
        System.out.println(username+" : "+password);
        if("admin".equals(username)&&"123".equals(password)){
            //登录成功
            //记录当前登录状态
            req.getSession().setAttribute("currentUser",username);
            resp.sendRedirect("/index");
        }else{
            resp.sendError(404);
        }
    }
}

上述LoginServlet在判断用户登录成功后,立刻将用户名放入当前HttpSession中:

HttpSession session = req.getSession();
session.setAttribute("currentUser", username);

IndexServlet中,可以从HttpSession取出用户名:

@WebServlet("/index")
public class IndexServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        HttpSession session = req.getSession();
        String currentUser = null;
        if(session.getAttribute("currentUser")!=null){
            currentUser = session.getAttribute("currentUser").toString();
            out.write("<h1>欢迎登陆 "+currentUser+"</h1>");
            out.write("<h2><a href='logout'>登出</a></h2>");
        }else{
            resp.sendRedirect("index.html");
        }
    }
}

如果用户已登录,可以通过访问/logout登出。登出逻辑就是从HttpSession中移除用户相关信息:

@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getSession().removeAttribute("currentUser");
        resp.sendRedirect("/index");
    }
}

对于Web应用程序来说,我们总是通过HttpSession这个高级接口访问当前Session。如果要深入理解Session原理,可以认为Web服务器在内存中自动维护了一个ID到HttpSession的映射表,我们可以用下图表示:

           ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

           │      ┌───────────────┐                │
             ┌───>│ IndexServlet  │<──────────┐
           │ │    └───────────────┘           ▼    │
┌───────┐    │    ┌───────────────┐      ┌────────┐
│Browser│──┼─┼───>│ LoginServlet  │<────>│Sessions││
└───────┘    │    └───────────────┘      └────────┘
           │ │    ┌───────────────┐           ▲    │
             └───>│LogoutServlet  │<──────────┘
           │      └───────────────┘                │

           └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

而服务器识别Session的关键就是依靠一个名为JSESSIONID的Cookie。在Servlet中第一次调用req.getSession()时,Servlet容器自动创建一个Session ID,然后通过一个名为JSESSIONID的Cookie发送给浏览器:

这里要注意的几点是:

  • JSESSIONID是由Servlet容器自动创建的,目的是维护一个浏览器会话,它和我们的登录逻辑没有关系;
  • 登录和登出的业务逻辑是我们自己根据HttpSession是否存在一个"user"的Key判断的,登出后,Session ID并不会改变;
  • 即使没有登录功能,仍然可以使用HttpSession追踪用户,例如,放入一些用户配置信息等。

除了使用Cookie机制可以实现Session外,还可以通过隐藏表单、URL末尾附加ID来追踪Session。这些机制很少使用,最常用的Session机制仍然是Cookie。

使用Session时,由于服务器把所有用户的Session都存储在内存中,如果遇到内存不足的情况,就需要把部分不活动的Session序列化到磁盘上,这会大大降低服务器的运行效率,因此,放入Session的对象要小,通常我们放入一个简单的User对象就足够了:

public class User {
    public long id; // 唯一标识
    public String email;
    public String name;
}

在使用多台服务器构成集群时,使用Session会遇到一些额外的问题。通常,多台服务器集群使用反向代理作为网站入口:

                                     ┌────────────┐
                                ┌───>│Web Server 1│
                                │    └────────────┘
┌───────┐     ┌─────────────┐   │    ┌────────────┐
│Browser│────>│Reverse Proxy│───┼───>│Web Server 2│
└───────┘     └─────────────┘   │    └────────────┘
                                │    ┌────────────┐
                                └───>│Web Server 3│
                                     └────────────┘

如果多台Web Server采用无状态集群,那么反向代理总是以轮询方式将请求依次转发给每台Web Server,这会造成一个用户在Web Server 1存储的Session信息,在Web Server 2和3上并不存在,即从Web Server 1登录后,如果后续请求被转发到Web Server 2或3,那么用户看到的仍然是未登录状态。

要解决这个问题,方案一是在所有Web Server之间进行Session复制,但这样会严重消耗网络带宽,并且,每个Web Server的内存均存储所有用户的Session,内存使用率很低。

另一个方案是采用粘滞会话(Sticky Session)机制,即反向代理在转发请求的时候,总是根据JSESSIONID的值判断,相同的JSESSIONID总是转发到固定的Web Server,但这需要反向代理的支持。

无论采用何种方案,使用Session机制,会使得Web Server的集群很难扩展,因此,Session适用于中小型Web应用程序。对于大型Web应用程序来说,通常需要避免使用Session机制。

Cookie

实际上,Servlet提供的HttpSession本质上就是通过一个名为JSESSIONID的Cookie来跟踪用户会话的。除了这个名称外,其他名称的Cookie我们可以任意使用。

如果我们想要设置一个Cookie,例如,记录用户选择的语言,可以编写一个LanguageServlet

@WebServlet(urlPatterns = "/pref")
public class LanguageServlet extends HttpServlet {

    private static final Set<String> LANGUAGES = Set.of("en", "zh");

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String lang = req.getParameter("lang");
        if (LANGUAGES.contains(lang)) {
            // 创建一个新的Cookie:
            Cookie cookie = new Cookie("lang", lang);
            // 该Cookie生效的路径范围:
            cookie.setPath("/");
            // 该Cookie有效期:
            cookie.setMaxAge(8640000); // 8640000秒=100天
            // 将该Cookie添加到响应:
            resp.addCookie(cookie);
        }
        resp.sendRedirect("/");
    }
}

创建一个新Cookie时,除了指定名称和值以外,通常需要设置setPath("/"),浏览器根据此前缀决定是否发送Cookie。如果一个Cookie调用了setPath("/user/"),那么浏览器只有在请求以/user/开头的路径时才会附加此Cookie。通过setMaxAge()设置Cookie的有效期,单位为秒,最后通过resp.addCookie()把它添加到响应。

如果访问的是https网页,还需要调用setSecure(true),否则浏览器不会发送该Cookie。

因此,务必注意:浏览器在请求某个URL时,是否携带指定的Cookie,取决于Cookie是否满足以下所有要求:

  • URL前缀是设置Cookie时的Path;
  • Cookie在有效期内;
  • Cookie设置了secure时必须以https访问。

我们可以在浏览器看到服务器发送的Cookie:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZMQn5JQH-1598186399952)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\1598025875650.png)]

如果我们要读取Cookie,例如我们在ParseCookies读取名为lang的Cookie以获取用户设置的语言

package com.softeem.server;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/parseCookies")
public class ParseCookie extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie [] cookies = req.getCookies();
        String currentLang= "EN"; //set default
        if(cookies!=null){
            for (Cookie cookie : cookies) {
                System.out.println(cookie.getName());
                if(cookie.getName().equals("lang")){
                    System.out.println(cookie.getValue());
                }
            }
        }
        resp.sendRedirect("/");
    }
}

可见,读取Cookie主要依靠遍历HttpServletRequest附带的所有Cookie。

JSP开发

我们从前面的章节可以看到,Servlet就是一个能处理HTTP请求,发送HTTP响应的小程序,而发送响应无非就是获取PrintWriter,然后输出HTML:

PrintWriter pw = resp.getWriter();
pw.write("<html>");
pw.write("<body>");
pw.write("<h1>Welcome, " + name + "!</h1>");
pw.write("</body>");
pw.write("</html>");
pw.flush();

只不过,用PrintWriter输出HTML比较痛苦,因为不但要正确编写HTML,还需要插入各种变量。如果想在Servlet中输出一个类似新浪首页的HTML,写对HTML基本上不太可能。

那有没有更简单的输出HTML的办法?

有!

我们可以使用JSP。

JSP是Java Server Pages的缩写,它的文件必须放到/src/main/webapp下,文件名必须以.jsp结尾,整个文件与HTML并无太大区别,但需要插入变量,或者动态输出的地方,使用特殊指令<% ... %>

我们来编写一个hello.jsp,内容如下:

<html>
<head>
    <title>Hello World - JSP</title>
</head>
<body>
    <%-- JSP Comment --%>
    <h1>Hello World!</h1>
    <p>
    <%
         out.println("Your IP address is ");
    %>
    <span style="color:red">
        <%= request.getRemoteAddr() %>
    </span>
    </p>
</body>
</html>

整个JSP的内容实际上是一个HTML,但是稍有不同:

  • 包含在<%----%>之间的是JSP的注释,它们会被完全忽略;
  • 包含在<%%>之间的是Java代码,可以编写任意Java代码;
  • 如果使用<%= xxx %>则可以快捷输出一个变量的值。

JSP页面内置了几个变量:

  • out:表示HttpServletResponse的PrintWriter;
  • session:表示当前HttpSession对象;
  • request:表示HttpServletRequest对象。

这几个变量可以直接使用。

访问JSP页面时,直接指定完整路径。例如,http://localhost:8080/hello.jsp,浏览器显示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HyoUPfhT-1598186399952)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\1598026083986.png)]

SP和Servlet有什么区别?其实它们没有任何区别,因为JSP在执行前首先被编译成一个Servlet。在Tomcat的临时目录下,可以找到一个hello_jsp.java的源文件,这个文件就是Tomcat把JSP自动转换成的Servlet源码:

package org.apache.jsp;
import ...

public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
               org.apache.jasper.runtime.JspSourceImports {

    ...

    public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {
        ...
        out.write("<html>\n");
        out.write("<head>\n");
        out.write("    <title>Hello World - JSP</title>\n");
        out.write("</head>\n");
        out.write("<body>\n");
        ...
    }
    ...
}

可见JSP本质上就是一个Servlet,只不过无需配置映射路径,Web Server会根据路径查找对应的.jsp文件,如果找到了,就自动编译成Servlet再执行。在服务器运行过程中,如果修改了JSP的内容,那么服务器会自动重新编译。

JSP高级功能

JSP的指令非常复杂,除了<% ... %>外,JSP页面本身可以通过page指令引入Java类:

<%@ page import="java.io.*" %>
<%@ page import="java.util.*" %>

这样后续的Java代码才能引用简单类名而不是完整类名。

使用include指令可以引入另一个JSP文件:

<html>
<body>
    <%@ include file="header.jsp"%>
    <h1>Index Page</h1>
    <%@ include file="footer.jsp"%>
</body>
JSP Tag

JSP还允许自定义输出的tag,例如:

<c:out value = "${sessionScope.user.name}"/>

JSP Tag需要正确引入taglib的jar包,并且还需要正确声明,使用起来非常复杂,对于页面开发来说,不推荐使用JSP Tag,因为我们后续会介绍更简单的模板引擎

小结

JSP是一种在HTML中嵌入动态输出的文件,它和Servlet正好相反,Servlet是在Java代码中嵌入输出HTML;

JSP可以引入并使用JSP Tag,但由于其语法复杂,不推荐使用;

JSP本身目前已经很少使用,我们只需要了解其基本用法即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值