Java EE 的核心技术规范(介绍)
Posted LonelyTraveler
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java EE 的核心技术规范(介绍)相关的知识,希望对你有一定的参考价值。
JAVA EE简介
Java 平台企业版(Java Platform Enterprise Edition),java EE平台旨在帮助开发人员创建大规模,多层,可伸缩,可靠和安全的网络应用程序。此类应用程序的简称是“企业应用程序”,之所以这么称呼是因为这些应用程序旨在解决大型企业遇到的问题。但是,企业应用程序不仅对大型公司,代理机构和政府有用。对于日益联网的世界中的个人开发人员和小型组织,企业应用程序的好处是有益的,甚至是必不可少的。
Java EE服务器
Java EE服务器是实现Java EE平台API并提供标准Java EE服务的服务器应用程序,例如:tomcat,weblogic,jboss等等。
JAVA EE规范
个人认知,java EE规范 为软件开发提供了一系列的解决方案。屏蔽实际提供者之间的差异,统一了软件开发规范,使开发者可以专注于业务开发。例如:jdbc规范屏蔽了mysql,oracle等不同数据库厂商之间的差异。
-
Java Servlet(Server Applet)
Servlet 称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态web内容。 这个规范可以说是javaEE最核心的一个规范,是web服务的基础,用它就可以实现web服务功能。接下来介绍下该接口的使用以及现在在spring mvc框架下的使用。
简单看下Servlet接口提供的几个方法
public interface Servlet {
/*该servlet初始化时调用,默认首次访问时初始化,可通过web.xml load-on-startup控制初始化时机,
注意,同一个servlet类,配置n个servlet,会被初始化n次,从此点可以看出Servlet是单例模式的
一般用于在初始化时获取web.xml内servlet节点下init-param参数,或者一些web公共参数
Called by the servlet container to indicate to a servlet that the servlet is being placed into service.
*/
void init(ServletConfig config);
/*返回服务器相关参数信息,具体可参考ServletConfig
Returns a ServletConfig object, which contains initialization and startup parameters for this servlet.
*/
ServletConfig getServletConfig();
/*基本不用,按照sun规范是应该返回servlet的作者,版本,版权等相关信息
Returns information about the servlet, such as author, version, and copyright.
*/
String getServletInfo();
/*servlet核心方法,当web容器接收到请求后会调用该方法,通过该方法即可实现HTTP1.1规范的8种请求方法
一般使用时直接继承自javax.servlet.http.HttpServlet类,重写要处理的HTTP请求方法即可,例如GET,POST,HEAD等方法
Called by the servlet container to allow the servlet to respond to a request.
*/
void service(ServletRequest req, ServletResponse res);
/*一般情况下是当web服务器退出时调用该方法,一般用于非java管理的相关资源释放。如:Jdbc连接池。
Called by the servlet container to indicate to a servlet that the servlet is being taken out of service.
*/
void destroy();}
样例:web.xml
<?xml version="1.0" encoding="UTF-8"?> <!-- 样例web.xml --> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<!-- 一个servlet节点对应一个servlet-mapping,它们的servlet-name要保持一致 --> <servlet> <servlet-name>hello-world</servlet-name> <servlet-class>org.example.TestServlet</servlet-class> <init-param> <param-name>param</param-name> <param-value>hello-world</param-value> </init-param> <!-- 1)load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)。 2)它的值必须是一个整数,表示servlet应该被载入的顺序 2)当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet; 3)当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载。 4)正数的值越小,该servlet的优先级越高,应用启动时就越先加载。 5)当值相同时,容器就会自己选择顺序来加载。 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hello-world</servlet-name> <url-pattern>/hello-world</url-pattern> </servlet-mapping> <servlet> <servlet-name>hello-world-2</servlet-name> <servlet-class>org.example.TestServlet</servlet-class> <init-param> <param-name>param</param-name> <param-value>hello-world-2</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>hello-world-2</servlet-name> <url-pattern>/hello-world-2</url-pattern> </servlet-mapping> </web-app>基本上来讲就是这个样子了,具体的实现在不同的web服务器之间可能会略有差异,但接口一定是保持一致的。因为这个只是一个介绍性资料,所以只是简单的介绍了servlet的生命周期,主要的service方法,略过了filter,request,response,ServletContext,cookie,session 等相关资料 。
Spring MVC 框架结构图,spring官网扣出来的哈。
controller:这个即为日常中需要开发的控制器,Front controller: DispatcherServlet 就是一个servlet实现类,只不过现在不在service方法中直接做业务处理,而是增加了一层controller,通过请求路径与注解配置实现之间的映射,把实际的业务转发给controller,业务完成后返回modelAndView给前置控制器。
Spring MVC 有什么好处呢?
框架么,一般来讲都是帮我们简化了开发,提供了一系列方便好用的工具,以及强大的灵活性,使工程师可以更专注于业务开发,提高开发效率。如:原始的Servlet需要每个资源都需要创建一个servlet类,需要手工处理请求的参数解析,参数校验等繁琐操作,还有就是原始的Servlet生命周期是在servlet容器的掌管之下,而controller可以使用spring容器,由spring管理类的创建销毁,业务service的注入等。 -
JDBC(Java Database Connectivity)
jdbc是用来与数据库建立连接的,提供了操作数据库的各种接口,jdbc设计了统一的数据库连接规范,以分层的思想屏蔽了不同数据库厂商之间的差异,使程序人员可以更专注于实际业务功能的实现。数据库有什么作用?略过……,简单看下jdbc的使用吧。
普通拼接SQLpublic class TestServlet extends HttpServlet { //旧版本jdbc驱动--com.mysql.jdbc.Driver private static final String DRIVER_CLASS_NAME ="com.mysql.cj.jdbc.Driver"; private static final String URL = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false"; private static final String NAME = "root"; private static final String PSD = "root"; public void service(ServletRequest req, ServletResponse res){ String name = req.getParameter("name"); String age = req.getParameter("age"); String sex = req.getParameter("sex"); Connection conn = null; try { //注册数据库驱动 Class.forName(DRIVER_CLASS_NAME); //获取数据库连接 conn = DriverManager.getConnection(URL, NAME, PSD); } catch (ClassNotFoundException e) { e.printStackTrace(); writerResponse(res, "未提供对应的数据库驱动:" + DRIVER_CLASS_NAME); return; } catch (SQLException e) { e.printStackTrace(); writerResponse(res, "获取数据库连接发生异常!"); return; } //存在sql注入--不应该使用 String sql = "INSERT INTO student (name, age, sex) VALUES (\'" + name + "\', " + age + ",\'" + sex + "\')"; try (Statement statement = conn.createStatement();){ //该方式无法获取insert结果 result 为false //boolean result = statement.execute(sql); //result为null //ResultSet resultSet = statement.getResultSet(); //获取执行成功条数 int row = statement.executeUpdate(sql); writerResponse(res, 1==row); return; } catch (SQLException e) { e.printStackTrace(); writerResponse(res, "执行sql发生异常!" + DRIVER_CLASS_NAME); return; } finally { //关闭数据库连接 if(null != conn) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } private void writerResponse(ServletResponse res, Object msg){ try { res.setCharacterEncoding("utf-8"); res.getWriter().print(msg); } catch (IOException e) { e.printStackTrace(); } } }
上边的代码演示了一个添加学生的功能,从request中获取请求参数,拼接sql然后执行,最后返回执行结果。此方式存在SQL注入,不应该使用该方式,不应该使用该方式,不应该使用该方式,重要的事情说三遍。
SQL注入问题:SQL注入是什么?额…… 请自行百度。
预编译SQL//解决普通sql拼接产生的sql注入 String prepareSql = "INSERT INTO student (name, age, sex) VALUES (?, ?, ?)"; PreparedStatement pStatement = conn.prepareStatement(prepareSql) pStatement.setString(1, name); pStatement.setInt(2, Integer.valueOf(age)); pStatement.setString(3, sex); int row = pStatement.executeUpdate(); writerResponse(res, 1==row);
预编译sql可以解决普通sql拼接产生的sql注入,使用中不应该再手动拼接不可信来源的数据,不然还是会造成sql注入。还有一点:预编译sql无法正确的处理order by子句,接下来简单看下吧。
order by 子句
基本信息:数据库版本:mysql 5.7
jdbc驱动:<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
测试数据:SELECT id,name,age FROM student;
id,name,age
13,李四,16
14, 张三,13测试一:正常测试
//程序编写
//String sql = "SELECT id, name, age FROM student ORDER BY" + orderColumn;
//实际传参
String sql= "SELECT id, name, age FROM student ORDER BY " + "IF(1=1, age, id)";
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
System.out.println(statement.toString());while (resultSet.next()) {
System.out.println(resultSet.getString("id") + ":"
+ resultSet.getString("name") + ":"
+ resultSet.getInt("age"));
}
执行结果一:SELECT id, name, age FROM student ORDER BY IF(1=1, age, id)
14:张三:13
13:李四:16执行结果二:
SELECT id, name, age FROM student ORDER BY IF(1=2, age, id)
13:李四:16
14:张三:13结果说明:
此时存在sql注入
orderColumn 可以修改为其他更有意义的sql,如实现拒绝服务,表名,字段名猜解,数据库用户名,密码猜解等。
测试二:预编译测试
//测试order By 排序问题, 此时可以正常执行,但是数据未进行排序 String prepareSql = "SELECT id, name, age FROM student ORDER BY ?";
//正常拼接 //String prepareSql = "SELECT id, name, age FROM student ORDER BY" + orderColumn; PreparedStatement pStatement = conn.prepareStatement(prepareSql); pStatement.setString(1, "IF(1=1, age, id)"); ResultSet resultSet = pStatement.executeQuery(); System.out.println(pStatement.toString()); while (resultSet.next()) { System.out.println(
resultSet.getString("id") + ":"
+ resultSet.getString("name") + ":"
+ resultSet.getInt("age")); } /* 测试结果: com.mysql.cj.jdbc.PreparedStatement@598c19fb: SELECT id, name, age FROM student ORDER BY \'IF(1=1, age, id)\' 13:李四:16 14:张三:13 */
结果说明:
可以防止SQL注入,但同时也使order by 无法生效。如果想生效只能把占位符 “?” 修改成字符串拼接方式。对于两种测试结果的说明:如果需要进行排序时只能使用SQL拼接方式,如何避免SQL注入了,不要使用外部不可信来源的参数,可以事先以枚举类型的名字进行约定,取到参数时进行枚举类型转换,转换成功传入,不成功返回错误。
数据连接池
在计算机性能飞速发展今天,程序执行速度一般不再是性能问题的主要原因,磁盘IO、网络IO的性能问题越来越突出。在上边的示例代码中,每一次的servlet请求处理中都创建了一个新的connection连接,使用后把连接给关闭,这是一种较为耗时的操作,所以出现了数据连接池,用于提供对数据库连接的管理。使用时从池子中获取,使用后还给池子,以便其他线程可以再次使用,用以提高性能。当前时间2020-1,在16,17年以及之前用的比较多的是c3p0以及dbcp连接池,最近这两年阿里巴巴的Druid(德鲁伊)数据连接池被越来越多的公司所采用的。据说在性能,扩展性等方面都优于其他链接池,同时还提供了了日志监控功能。
orm框架(object relation mapping)
在上边的代码中可以看出,对于sql的预编译操作,sql返回的结果集处理都是些重复性的工作,所以呢就出现了一些框架帮我们解决这些重复的体力劳动,提高了代码可读性,同时还提供了一些如缓存之类的优化措施,提高开发效率。主要的有遵循sun JPA规范的 hibernate, 以及半orm的mybatis框架,其中hibernate 基本上不需要写sql,但同时灵活性上就差一些,也较为复杂,学习成本较高,而mybatis中是通过mapper中配置sql语句的方式实现,所以较为灵活,可控性更强,同时学习成本低,所以现在mybatis在中国的使用较为广泛。
mybatis
简单提一下mybatis两种取值方式的差别:#{},${}, "#" 井号mybatis会把其处理成预编译的方式进行查询,对应着PreparedStatement 方式,而$方式会进行原值拼接,对应Statement方式,所以要注意$取值时的sql注入问题,以及order by取值问题。 -
JSP(JavaServer Pages)
JSP(全称JavaServer Pages)是由Sun公司主导创建的一种动态网页技术标准。JSP部署于网络服务器上,可以响应客户端发送的请求,并根据请求内容动态地生成html、XML或其他格式文档的Web网页,然后返回给请求者。JSP技术以Java语言作为脚本语言,为用户的HTTP请求提供服务,并能与服务器上的其它Java程序共同处理复杂的业务需求。(百科)jsp本质上还是servlet,还是通过重写service方法来实现的,但是为什么会有jsp这个东西呢?其实主要是为了解决servlet中不便处理返回的动态网页问题,在Servlet时代,想返回一个动态网页,需要在servlet中使用字符串拼接的方式进行返回,书写的代码不易阅读、维护,且在代码编写过程中极易出错,不易验证,只能通过实际运行程序来确认是否正确。所以就出现了jsp技术,通过分离静态网页代码部分与动态数据部分,在一定程度上提高了可读性与减少了无用代码的干扰。
在最初时,jsp中的动态数据部分都是通过直接写java代码的方式实现的,在循环处理数据时,编写的代码就相对繁杂,需要自行处理好html静态代码与java代码的组合问题,代码阅读与维护也是相对困难,所以之后又发展出了EL表达式技术,避免在jsp中直接写java代码,以一种更可读,更易维护的方式来提高开发效率与降低维护成本。
其他的一些模板引擎技术:
Freemarker, Velocity, Thymeleaf。
jsp编译后的源文件位置:tomcat下目录位置:$TOMCAT_HOME\\work\\Catalina\\localhost{域名一般是localhost}\\{项目名称}\\org\\apache\\jsp\\{文件目录}\\{jsp文件名}.java
小结
Servlet,JDBC, JSP可以说是JAVAEE中最核心的三个技术了,一般我们学习javaWeb开发主要都是学习这三种技术,使用他们基本上可以满足各式各样的功能需求,但是在今天为了提高开发效率,提高性能,提高维护性等,我们还要学习一些框架技术作为辅助,例如上边提到的,Spring Mvc, 数据连接池,mybatis,以及很重要但没有提及的 Spring框架,学会这些框架常用功能的使用,了解框架核心的技术思想,基本上可以成为一名入门级的java Web程序员啦。 -
XML(EXtensible Markup Language)
XML 可扩展标记语言,这个东西是W3C组织设计出来用于数据传输使用的,不太清楚为什么跑进了JAVA EE的技术规范(是因为JAXB的xml注解么?)。XML是一个与编程语言平台无关的技术规范,XML被应用到了很多地方,比如WebService的数据传输,spring,mybatis等框架的配置文件。
XML的约束文件:
在接口交互之前,需要接口双方对接口交互的数据格式进行约定,而这个约定的文件就是XML的约束文件。xml主要有两种约束方式,一种是DTD(Documnet Type Definition),一种是XSD(XML Schema Definition),DTD主要通过预定义的方式实现的,所以不够灵活,无法进行扩展,所以后来出现了XSD方式,采用XML语言的方式,提供了更高的灵活性,且语意较DTD方式也更为清晰,现在基本上都应该是XSD方式进行约束。
java中的xml解析技术
SAX:JDK提供的简单工具,主要是通过逐行的方式处理xml文件,所以适合大文件的解析,但是据说是有bug。
DOM4j:据说是java中xml解析工具中效率最好的,但是使用上不是特别方便(没用过)。
JAXB:JDK1.7版本中已内置了JAXB实现。jaxb提供了一些相对好用的工具,例如xjc工具可以通过xml的xsd约束文件直接创建出对应javaBean对象。通过通过注解或xml配置文件,实现xml与javaBean对象的互相转换,同时还提供了了对xml数据的有效性校验,性能上好像不如DOM4j。
JSON(javascript Object Notation)
json也是一种数据传输的格式规范,但是相较于xml来讲更加轻量级,数据量更小,所以在如今使用的也非常广泛。json因为没有约束文件,所以变动,扩展较为简单,但同时也无法提供详细的语意(节点是否必须存在,节点数量限制,数据的类型约束等)规范。 -
JMS(Java Message Service)
JMS java消息服务,是软件组件或应用程序之间进行通信的一种方法。Java消息服务是一种Java API,允许应用程序创建,发送,接收和读取消息。JMS API定义了一组通用的接口和关联的语义,这些接口和关联的语义允许以Java编程语言编写的程序与其他消息传递实现进行通信。JMS的主要作用是降低系统之间的耦合度。JMS也是一项技术规范,与JDBC一样屏蔽了不同消息队列(MQ)提供者之间的差异,实现提供者有:weblogic提供了JMS的实现,ActiveMQ,RabbitMQ(通过插件封装的方式支持)。
JMS提的两种模式
P2P(Point to Point)
点对点概念主要在于消息生产者,queue,消息消费者之上,主要是说明,消息生产者将消息发送至特定的队列,有一个特定的消息消费者会去消费队列中的消息,一条消息仅被消费一次。
Topic(publish/subscribe)
发布订阅模式,主要说明同一个消息可以被多个消费者所消费。例如:网页聊天群的实现。
应用场景
流量削峰(超大流量),例如:在抢购业务中,为了避免大量的请求超过服务器处理的能力,可以设置一定长度的消息队列,超出后直接返回错误。
异步,应用解耦(不关心后续业务结果),例如:用户注册后,只要数据保存成功、JMS消息发送成功,即可返回成功,而不需要关系后续所包含的的一些业务及其结果,例如发送邮件通知,赠送积分等等其他业务。
-
RMI(Remote Method Invocation)
RMI 远程方法调用,主要用于分布式系统之间的系统调用,RMI可以理解为一个专用于java语言的RPC(Remote Procedure Call )实现。RPC:一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议,RMI也是同样的屏蔽了网络之间通讯的细节,使java中调用远程的一个方法就像调用本地的一个方法一样。
一些类似的技术对比:
RMI: 与java平台耦合严重,无法与其他语言平台结合使用。Dubbo : 基于dubbo协议,还有一些其他协议。使用上相对简单,dubbo协议采用长连接,异步IO的方式实现,适合小数据量的数据传输,传输效率高。dubbo采用接口API方式实现,各个服务之间的耦合性相对严重。
Spring Cloud:基于HTTP协议,spring cloud对分布式系统的各种业务提供了较为完整的组件,http协议数据量高于dubbo协议,各个服务之间耦合度较低。
思考:
分布式系统中,各种业务对象的维护问题?比如:接口之间的参数约定问题,增加,删除,修改参数时,两边系统的维护问题,这之间可能还牵连各种中间对象,VO,DTO,DAO等各种javaBean的修改问题。各种服务的拆分问题?单人维护多个服务的问题?各位有什么好的建议,或者书籍推荐欢迎在评论留言。
-
JTA(Java Transaction API)
JTA java事务api,主要用于解决分布式系统之间分布式事务问题,是一个基于X/A的两阶段提交的事务模型,JTA定义了一套接口规范,JTA中约定了几种主要的程序角色,分别是事务管理器、事务客户、应用服务器、资源管理器。然后由JTS约定这些角色之间的交互细节。
分布式事务技术
MQ:基于日志的最终一致性的柔性补偿性事务处理方案。
TCC:Try,Commit,Cancel。try阶段对资源进行预留,commit阶段进行提交,如果失败进入Cancel阶段,对数据进行try阶段预留的资源进行回滚。
GTX(seata): 解决MQ,和TCC模式下的事务处理代码与业务代码之间的高侵入问题,基于类似MySql的redo,undo日志模式进行事务处理,降低了事务代码的侵入。说明
分布式事务是分布式系统中最核心的一个业务难题,上面仅列出了目前主流的一些解决方案,对于具体的技术细节,请自行搜索,研究。
-
JTS
对应用屏蔽,主要定义了JTA实现的细节,给JTA的提供者使用。JTS是一个组件事务监视器。JTS是CORBA OTS事务监控的基本实现。JTS规定了事务管理器的实现方式。JTS事务管理器为应用服务器、资源管理器、独立的应用以及通信资源管理器提供了事务服务。
-
EJB(Enterprise JavaBean)
EJB 企业级javaBean。企业Bean用Java编程语言编写,是一种服务器端组件,封装了应用程序的业务逻辑。该业务逻辑是满足应用目的的代码。
EJB组件: Session Bean,Message-Driven。
EJB容器:管理各种EJB组件中的bean生命周期,提供了通过java目录服务(JNDI)的方式获取bean,注入bean等等,同时提供了RMI,JTA服务组件,可以方便的进行分布式系统开发,且不需关心通讯细节,以及分布式系统之间的事务处理。同时可以对比一下spring容器,spring容器也是管理了开发中的各种bean对象,提供了控制翻转(IOC)或者说是依赖注入(DI)的方式对spring管理bean的获取。但是spring容器不包含对分布式模块的支持。
EJB服务器:我的理解是类似提供了EJB实现的Weblogic,Jboss服务器等。
-
JNDI (Java Name and Directory Interface)
JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。
简单的可以理解为一种通讯录,通过姓名可以查询联系人电话,通讯地址等信息。
weblogic中通过JNDI提供jdbc连接池服务。 -
其他类
JAVA MAIL: 处理邮件相关。
JAF(JavaBeans Activation Framework):也是与处理邮件相关
Java IDL(Interface Description Language)/CORBA(Common Object Broker Architecture):主要用于与其他语言平台的接口交互。
JCA(Java EE Connector Architecture)它注重的是将Java程序连接到非Java程序和软件包中间件的开发。
JPA(Java Persistence API):数据持久化相关。
参考资料
以上是关于Java EE 的核心技术规范(介绍)的主要内容,如果未能解决你的问题,请参考以下文章