【方向盘】版本历史&代码示例之:Servelt、JSP、EL表达式

简介: 工具好,也用用得巧
工具好,也要用得巧。
本文已被 https://yourbatman.cn收录; 女娲Knife-Initializr工程可公开访问啦;程序员专用网盘 https://wangpan.yourbatman.cn;公号后台回复“ 专栏列表”获取全部小而美的 原创技术专栏

你好,我是方向盘(YourBatman、方哥)。笔者的公号(Java方向盘)是保留地,只分享原创,不转载、不发商务广告!!!

✍前言

若你还不太清楚Java EE是什么,可先移步这里:什么是Java EE?

最近一段时间,将Java EE/Jakarta EE进行了科普,almost也许可能大概是全网最全的,颇受好评。本专栏全部内容可点击上方专栏标题查看,这里做简单陈列:

本以为科普到这就差不多了告一段落的,直到有小伙伴私信我说:看了这么多但依旧不知道Java EE如何使用?当头棒喝,作为一名Javaer,其实天天都在使用着Java EE。只是在Spring框架的襁褓之下,它优秀的抽象能力甚至让开发者感知不到底层技术的存在。

在这里插入图片描述
本着管生管养,管杀管埋的初心,决定再续写几篇,针对每一项(主流)Java技术给出示例,主要包括这几个方面:

  • 版本历史
  • 生存现状
  • 实现(框架)
  • 代码示例

所属专栏

相关下载

版本约定

  • Java EE:6、7、8
  • Jakarta EE:8、9、9.1

✍正文

在这里插入图片描述

Servlet

Servlet是一种基于Java的动态Web资源动态Web资源技术,类似的技术还有ASP、PHP等。

<!-- javax命名空间版本(Tomcat 9.x及以下版本支持) -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

<!-- jakarta命名空间版本(Tomcat 10.x及以上版本支持) -->
<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>5.0.0</version>
    <!-- <version>4.0.4</version> 此版本命名空间同javax -->
    <scope>provided</scope>
</dependency>

✌版本历史

Servlet规范由Sun Microsystems公司创建,1.0版于1997年6月完成。从2.3版开始,该规范是在JCP下开发。

版本 发布日期 隶属于 JSR版本 焦点说明
1.0 1997.06 - - 首个版本,由Sun公司发布
2.0 1997.08 - -
2.1 1998.11 - - 新增了RequestDispatcher, ServletContext等
2.2 1999.08 J2EE 1.2 - 成为J2EE的一部分。在.war文件中引入了self-contained Web applications的概念
2.3 2001.08 J2EE 1.3 JSR 53 增加了Filter,增加了关于Session的Listener(如HttpSessionListener)
2.4 2003.08 J2EE 1.4 JSR 154 没增加大的新内容,对不严格的地方加了些校验,如:对web.xml使用XML Schema
2.5 2005.09 Java EE 5 JSR 154 最低要求JDK 5。注解支持(如@WebService、@WebMethod等,注意不是@WebServlet这种哦)
3.0 2009.12 Java EE 6 JSR 315 史上最大变革。动态链接库和插件能力(Spring MVC利用此能力通过ServletContainerInitializer进行全注解驱动开发)、模块化开发、异步Servlet、安全性、新的文件上传API、支持WebSocket,新的注解(@WebServlet、@WebFilter、@WebListener),可脱离web.xml全注解驱动,此版本功能已经很完整了,应用的主流
3.1 2013.5 Java EE 7 JSR 340 新增非阻塞式IO。Spring的Web Flux若要运行在Servlet容器,至少需要此版本,因为从此版本起才有非阻断输入输出的支持
4.0 2017.09 Java EE 8 JSR 369 支持Http/2。从而支持服务器推技术,新的映射发现接口HttpServletMapping可用来提高内部的运行效率
5.0 2020.11 Jakarta EE 9 JSR 369 同Servlet 4.0(只是命名空间从javax.*变为了jakarta.*而已)

Spring Boot相关

  • 2.0.0.RELEASE版本(2018.05):正式内置Servlet 3.1,毕竟Spring Web Flux从此版本开始(Spring 5)
  • 2.1.0.RELEASE版本(2018.10):升级到Servlet 4.x,直到现在(2.6.x)也依旧是4.x版本
  • 2.2.0.RELEASE版本(2019.10):开始支持jakarta.servlet这个GAV,(和javax.servlet)二者并行
  • 2.5.0/2.6.0版本(2021.05):无变化
  • 3.0.0版本(预计2022.12):基于Spring 6.x、Jakarta EE 9,基于GraalVM全面拥抱云原生的新一代框架
说明:Spring Boot 2.6和2.7都还会基于Spring Framework 5.3.x内核。 Spring Framework 6.0版本在2021年9月正式拉开序幕,将基于全新的 Jakarta EE 9(命名空间为jakarta.*,不向下兼容)平台开发,相应的Spring Boot 3也会基于此内核

✌生存现状

随着Spring 5的发布推出WebFlux,Servlet技术从之前的必选项变为可选项

但考虑到业务开发使用WebFlux收益甚微但开发调试成本均增加,因此实际情况是基于Servlet的Spring MVC技术依旧是主流,暂时地位不可撼动,依旧非常活跃

✌实现(框架)

由于Servlet由Web容器负责创建并调用,因此只要实现了Servlet规范的Web容器均可作为它的实现(框架),如Tomcat、Jetty、Undertow、JBoss、Glassfish等。

✌代码示例

导入依赖包:

  1. scope一般provided即可,因为Web容器里会自带此Jar
  2. Spring Boot场景下无需显示导入,因为Tomcat已内嵌(相关API)
servlet-api的GAV

继承HttpServlet写一个用于处理Http请求的Servlet处理器

/**
 * 在此处添加备注信息
 *
 * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a>
 * @site https://yourbatman.cn
 * @date 2021/9/12 06:23
 * @since 0.0.1
 */
@WebServlet(urlPatterns = {"/hello"})
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello servlet...");
    }
}

IDEA添加(外置)Tomcat 9.x版本,以war包形式部署到Tomcat(小提示:<packaging>war</packaging>),并启动Tomcat
在这里插入图片描述
浏览器http://localhost:8080/hello即可完成正常访问。

说明:自Servlet 3.0之后,web.xml部署描述符并非必须(全注解即可搞定)

工程源代码https://github.com/yourbatman/FXP-java-ee

JSP

Java Server Page的简称。那么,有了Servlet为何还需要JSP?其实它俩都属于动态Web技术,只是Servlet它用于输出页面简直太繁琐了(每一句html都需要用resp.getWriter()逐字逐句的输出),所以才出现了JSP技术来弥补其不足。

它使用JSP标签在HTML网页中插入Java代码。语法格式为:<% Java代码 %>。它有九大内置对象这么一说:

1、request:请求对象。javax.servlet.http.HttpServletRequest
2、response:响应对象。javax.servlet.http.HttpServletResponse
3、session:会话对象。javax.servlet.http.HttpSession
4、application:应用程序对象。javax.servlet.ServletContext
5、config:配置对象。javax.servlet.ServletConfig
6、page:页面对象。当前jsp程序本身,相当于this
7、pageContext:页面上下文对象。javax.servlet.jsp.PageContext
8、out:输出流对象,用于输出内容到浏览器。javax.servlet.jsp.jspWriter
9、exception:异常对象,只有在包含isErrorPage=”true”的页面中才可以被使用。java.lang.Throwable

除了Servlet。与JSP 强相关 的技术还有EL表达式和JSP标签(JSTL),下面会接着介绍。

<!-- javax命名空间版本(Tomcat 9.x及以下版本支持) -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
    <scope>provided</scope>
</dependency>

<!-- jakarta命名空间版本(Tomcat 10.x及以上版本支持) -->
<dependency>
    <groupId>jakarta.servlet.jsp</groupId>
    <artifactId>jakarta.servlet.jsp-api</artifactId>
    <version>3.0.0</version>
    <!-- <version>2.3.6</version> 此版本命名空间同javax -->
    <scope>provided</scope>
</dependency>

✌版本历史

由于JSP的本质就是Servlet,它的的版本号需要与Servlet对应看待。

版本 发布日期 JSR版本 对应Servlet版本
JSP 1.1 2000.07 JSR 906 Servlet 2.2
JSP 1.2 2002.06 JSR 53 Servlet 2.3
JSP 2.0 2003.11 JSR 152 Servlet 2.4
JSP 2.1 2005.09 JSR 245 Servlet 2.5
JSP 2.2 2009.12 JSR 245(升级版) Servlet 3.0
JSP 2.3 2013.05 JSR 372(升级版) Servlet 3.1
JSP 3.0 2020.11 ----(Jakarta旗下) Servlet 5.x

Spring Boot相关:Spring Boot从1.x版本开始就一直没有“带”JSP一起玩,若要Spring Boot支持JSP需要特殊开启。

JSP 2.0是个重要版本,最重要的特性就是开始支持EL表达式了,可以用它来访问应用程序数据。JSP 2.3版本可断定是最后一个版本,因为JSP已走到尽头,成为历史。

✌生存现状

JSP诞生之后,程序员写页面写得确实很爽了。但是,它带来了坏处:很多程序员同学将业务逻辑、页面展示逻辑都往JSP塞,耦合在一起,导致JSP扛不住了,更重要的是程序员扛不住了,非常凌乱。

虽然后面出现了EL表达式和JSTL标签来帮助程序员不要在JSP里写Java代码,但只要不是强制的你能限制住自由的程序员么?然后呢,后来出现了Freemarker和Velocity这种模板引擎,使得程序员没有办法在页面上写Java代码了,达到了分离的效果。

模板引擎出现后,JSP的地位已经岌岌可危了。但真正杀死它的还是前端的崛起,从而进入前后端完全分离的状态,至此基本可以宣布JSP(甚至包括模板引擎)的死亡。

所以JSP目前的生存状态是:基本死亡状态。你看,这不Spring Boot(默认)都不带他玩了嘛~

✌实现(框架)

与Servlet相同的Web容器。

✌代码示例

导包。由于我们不可能直接使用JSP的API,因此99.9999%情况下无需导包。

无需导包

创建webapp内容文件夹。这点很重要,因为是要创建一个web文件夹,以IDEA为例:在jsp-demo工程下添加web模块
在这里插入图片描述
在这里插入图片描述
完成后工程目录结构如下:
在这里插入图片描述
值得一提的是:web目录名称叫什么无所谓(只是很多喜欢叫webapp、webroot等),重要的是要有这个小圆点。不乏听见不少小伙伴说这个目录名必须叫webapp,其实它名字叫什么、甚至位置放在哪都无所谓,重要是找得到就行。掌握原理,一通百通。

这里附上HelloJsp的内容:

/**
 * 在此处添加备注信息
 *
 * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a>
 * @site https://yourbatman.cn
 * @date 2021/9/12 06:26
 * @since 0.0.1
 */
@WebServlet(urlPatterns = {"/hellojsp"})
public class HelloJsp extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        RequestDispatcher requestDispatcher = request.getRequestDispatcher("hello.jsp");
        // 放在WBE-INF下面的.jsp页面必须通过Servlet转发才能访问到,更加安全
        // RequestDispatcher requestDispatcher = request.getRequestDispatcher("/WEB-INF/hello.jsp");
        requestDispatcher.forward(request, response);
    }
}

以war包形式部署至Tomcat
在这里插入图片描述
浏览器访问下面两个路径均可得到响应结果:

  • http://localhost:8080/hellojsp:请求 -> Servlet转发 -> jsp页面(即使jsp页面放到WEB-INF目录下依旧可访问)
  • http://localhost:8080/hello.jsp:请求 -> jsp页面(此直接方式只能访问非WEB-INF目录下的jsp文件)

页面响应:
在这里插入图片描述

再强调一遍:自Servlet 3.0之后,web.xml部署描述符并非必须。即使有jsp页面也是一样~~~

工程源代码https://github.com/yourbatman/FXP-java-ee

EL表达式

Expression Language表达式语言。EL表达式语言的灵感来自于ECMAScriptXPath表达式语言(表达式语言当然还有比较著名的Spring的SpEL,以及OGNL),它提供了在 JSP 中简化表达式的方法,目的是替代掉在Jsp里写Java代码,让Jsp的代码更加简化。

基本语法为:${EL表达式 },只能读取数据不能设置数据(设置数据用JSP内或者Servlet里的Java代码均可)

请务必注意,基本语法中右边的 }的前面有个空格,使用时请务必注意

在EL中有四大域对象11大内置对象这么一说:

  • 请求参数
1、param 包含所有的参数的Map,可以获取参数返回String。其底层实际调用request.getParameter()
    - name=${param.name }
2、paramValues 包含所有参数的Map,可以获取参数的数组返回String[]。其底层实际调用request.getParameterValues()
    - hobby[0]=${paramValues.hobby[0] }
  • 头信息
3、header 包含所有的头信息的Map,可以获取头信息返回String。
    - ${header.Connection }
4、headerValues 包含所有的头信息的Map,可以获取头信息数组返回String[]。
    - ${headerValues["user-agent"][0] }
  • Cookie
5、cookie包含所有cookie的Map,key为Cookie的name属性值
    - ${cookie.JSESSIONID.name }
  • 初始化参数
6、iniParam 包含所有的初始化参数(一般配在web.xml里)的Map,可以获取初始化的参数
    - ${initParam.username} ${initParam.password}
  • 四大作用域(重点)
7、pageScope 包含page作用域内的Map
    - ${pageScope.name }
8、requestScope 包含request作用域内的Map
    - ${requestScope.name }
9、 包含session作用域内的Map
    - ${sessionScope.name }
10、applicationScope 包含application作用域内的Map
    - ${applicationScope.name }
  • 页面上下文
11、pageContext 包含页面内的变量的Map,可获取JSP中的九大内置对象
    - ${pageContext.request.scheme }
    - ${pageContext.session.id}
<!-- javax命名空间版本(Tomcat 9.x及以下版本支持) -->
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.0</version>
</dependency>

<!-- jakarta命名空间版本(Tomcat 10.x及以上版本支持) -->
<dependency>
    <groupId>jakarta.el</groupId>
    <artifactId>jakarta.el-api</artifactId>
    <version>4.0.0</version>
    <!-- <version>3.0.3</version> 此版本命名空间同javax -->
</dependency>

除此之外,还可以通过Tomcat的GAV直接导入,版本号同Tomcat
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-el-api</artifactId>
    <version>Tomcat版本号</version> <!-- 9.x版本是javax.*,10.x以及后面是jakarta.* -->
</dependency>
嵌入式Tomcat提供的实现
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-el</artifactId>
    <version>Tomcat版本号</version> <!-- 9.x版本是javax.*,10.x以及后面是jakarta.* -->
</dependency>

另外,还有二合一的GAV:3.x版本的API和impl实现都在一个jar里。
4.x使用jakarta.*命名空间,并且API分离(依赖于)jakarta.el-api
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.el</artifactId>
    <version>4.0.2</version>
    <!-- <version>3.0.3</version> 此版本命名空间同javax -->
</dependency>

值得注意的是,EL并非Web独享而是可独立使用,因此它的scope用默认的即可。另外,这只是API,并非Impl实现,是不能直接运行的,否则会遇到类似如下异常:

Caused by: javax.el.ELException: Provider com.sun.el.ExpressionFactoryImpl not found
    at javax.el.FactoryFinder.newInstance(FactoryFinder.java:101)
    ...

✌版本历史

EL从JSP 2.0版本开始引入,用于在JSP页面获取数据的简单方式。因此它是随着JSP的发展而出现的,只是可独立使用而已。

版本 发布日期 JSR版本 对应JSP版本 对应Servlet版本
EL 2.0 2003.11 JSR 152 JSP 2.0 Servlet 2.4
EL 2.2 2009.12 JSR 245 JSP 2.2 Servlet 2.5
EL 3.0 2013.05 JSR 341 JSP 2.3 Servlet 3.1
EL 4.0 2020.10 纳入Jakarta JSP 3.0 Servlet 5.0

EL表达式3.0于2013年4月份发布(可认为是最后一次功能升级),它的新特性包括:字符串拼接操作符、赋值(以前只能读取,现在可以赋值啦)、分号操作符、对象方法调用(以前只能用JavaBean属性导航)、Lambda表达式、静态字段/方法调用、构造器调用、Java8集合操作。具体就不一一举例了,详细情况可阅读我收录的JSR文档

✌生存现状

随着JSP的消亡,EL的存在感越来越弱。

好在它可以作为单独的表达式语言使用,有Hibernate Validator对它是强依赖,所以生命力还行。但由于Hibernate Validator里使用得简单,所以EL并没有必要再更新(动力不足)。

✌实现(框架)

EL大部分情况下伴随着JSP一起使用,所以交由Web容器去解析实现。

另外,EL作为一种表达式语言,也可以作为”工具“供以使用,比如著名的Hibernate Validator内部就依赖于EL表达式语言来书写校验规则(所以它在编译期就强依赖于EL的API)。

✌代码示例

在JSP中使用EL是由org.apache.tomcat:tomcat-jasper-el或者org.apache.tomcat.embed:tomcat-embed-jasper完成和JSP的整合,以及解析支持的。在JSP页面里使用方式由于已经过时(主要是使用示例一搜一大把),这里为了节约篇幅,就略了哈。

如果把EL当做工具使用的话(比如Hibernate Validator用来错误消息里插值用),需要了解一些API和常见用法,演示一下:

导包:

上面的GAV随便选一个(记得太impl实现,推荐org.glassfish:jakarta.el)

直接使用API书写Demo

/**
 * 在此处添加备注信息
 *
 * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a>
 * @site https://yourbatman.cn
 * @date 2021/9/12 10:12
 * @since 0.0.1
 */
public class ElDemo {

    public static void main(String[] args) {
        ExpressionFactory factory = ELManager.getExpressionFactory();
        StandardELContext elContext = new StandardELContext(factory);

        // 将instance转为对应类型
        ValueExpression valueExpression = factory.createValueExpression("18", Integer.class);
        System.out.println(valueExpression.getValue(elContext));

        // 计算表达式的值
        valueExpression = factory.createValueExpression(elContext, "${1+1}", Integer.class);
        System.out.println(valueExpression.getValue(elContext));

        // 方法调用
        // MethodExpression methodExpression = factory.createMethodExpression(elContext, "${Math.addExact()}", Integer.class, new Class[]{Integer.class, Integer.class});
        // System.out.println(methodExpression.invoke(elContext, new Object[]{1, 2}));
    }

}

运行,结果输出:
18
2

工程源代码https://github.com/yourbatman/FXP-java-ee

✍总结

现在越来越卷的IT行业,衡量一个求职者的专业能力,深度往往比广度更为重要

正所谓这辈子听过很多大道理,却依旧过不好这一生;技术也一样,听过/知道过/使用过很多技术,但依旧写不出好的代码。究其原因,就是理解不深刻。

自上而下的用,自底向上的学,这是我个人一直秉承的一个观念。知道一门技术、使用一门技术一般几个小时or几天就能大概搞定(毕竟如果一门技术入门很难的话也几乎不太可能大众化的流行起来),而理解一门技术的单位可能就是月、甚至是年了,这需要静下心来学习和研究。

本专栏文章

推荐阅读

我是方向盘(YourBatman):前25年不会写Hallo World、早已毕业的大龄程序员。高中时期《梦幻西游》骨灰玩家,网瘾失足、清考、延期毕业、房产中介、保险销售、送外卖...是我不可抹灭的黑标签

  • 🎓2013.07 清考、毕业答辩3次未通过、延期毕业
  • 🏷2013.08-2014.07 宁夏中介公司卖二手房1年,毕业后第1份工作
  • ️️🏷2014.07-2015.05 荆州/武汉,泰康人寿卖保险3月、饿了么送外卖2月,还有炸鸡排、直销等第2345份工作
  • 🏷2015.08 开始从事Java开发,闯过外包,呆过大厂!擅长抽象思维,任基础架构团队负责人
  • 🏷2021.08 因“双减政策”失业!历经9面,终获美团外卖L8的offer
  • 🙅🏻‍♀️Java架构师、Spring开源贡献者、CSDN博客之星年度Top 10、领域建模专家、写作大赛1/2届评委
  • 📚高质量代码、规范践行者;DDD领域驱动深度实践;即将出版书籍《Spring奇淫巧技》

在这里插入图片描述

序号 专栏名称 简介
01 【方向盘】-程序人生 程序人生,人生程序
02 【方向盘】-资讯/新特性 IDEA、JDK、Spring技术栈......新特性
03 【方向盘】-IntelliJ IDEA 熟练使用IDEA就相当拥有物理外挂,助你高效编码
04 【方向盘】-Bean Validation 熟练掌握数据校验,减少90%的垃圾代码
05 【方向盘】-日期时间 帮你解决JDK Date、JSR 310日期/其实 的一切问题
06 【方向盘】-Spring类型转换 Spring类型转换-框架设计的基石
07 【方向盘】-Spring static static关键字在Spring里的应用
08 【方向盘】-Cors跨域 关于跨域请求问题,本专栏足矣
09 【方向盘】-Jackson Almost Maybe是最好的Jackson专栏
10 【方向盘】-Spring配置类 专讲@Configuration配置类,你懂的
11 【方向盘】-Spring技术栈 暂无所属小分类的,Spring技术栈大分类
12 【方向盘】-JDK 暂无所属小分类的,JDK技术栈大分类
13 【方向盘】-Servlet Servlet规范、Web相关内容专题
14 【方向盘】-Java EE 从Java EE到Jakarta EE,30年弹指一挥间
15 【方向盘】-工具/提效 开发工具、软件工具,目标是提效
16 【方向盘】-Spring技术栈新特性 Spring Framework、Spring Boot、Spring Cloud、Spring其它技术
17 【方向盘】-基本功 每个Javaer,都需要有扎实的基本功
... ... ...
99 源代码库 大多数专栏均配有源代码,都在这里
目录
相关文章
|
4天前
|
存储 XML SQL
jsp、EL表达式、Jstl使用
jsp、EL表达式、Jstl使用
|
10月前
|
Java
欢迎来到Jsp编程课时六——EL表达式(JSP第十五课时)
欢迎来到Jsp编程课时六——EL表达式(JSP第十五课时)
82 0
|
10月前
|
Java 数据库
JSP第十八课让你体验不一样的代码世界《笔记内容三》(二)
JSP第十八课让你体验不一样的代码世界《笔记内容三》(二)
49 0
|
10月前
|
druid Java 关系型数据库
JSP第十八课让你体验不一样的代码世界《笔记内容三》(一)
JSP第十八课让你体验不一样的代码世界《笔记内容三》(一)
52 0
JSP学习(一):声明脚本、表达式脚本和代码脚本
虽然现在JSP用得不多了 ,但是很多学习JSP也就需要几天时间,而且很多项目仍然使用JSP,因此我也花几天时间学习一下JSP,要求自己能看懂代码就行。
|
Java
JSP EL中的函数
JSP EL中的函数
61 0
|
Java
JSP学习——EL表达式和JSTL学习小结
JSP学习——EL表达式和JSTL学习小结
90 0
|
前端开发 Java
JSP简介&&EL表达式&& JSTL
JSP简介&&EL表达式&& JSTL
JSP简介&&EL表达式&& JSTL
IDEA写jsp代码报错,但是正常运行解决
首先说明,jsp是一个很老的技术了,用的比较少了,但是难免会接触到。
246 0
|
Java
strus2中页面的s标签,替换jsp的el和jstl标签
strus2中页面的s标签,替换jsp的el和jstl标签
86 0
strus2中页面的s标签,替换jsp的el和jstl标签