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等不同數據庫廠商之間的差異。

  1. 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官網扣出來的哈。
    mvc框架圖
    controller:這個即爲日常中需要開發的控制器,Front controller: DispatcherServlet 就是一個servlet實現類,只不過現在不在service方法中直接做業務處理,而是增加了一層controller,通過請求路徑與註解配置實現之間的映射,把實際的業務轉發給controller,業務完成後返回modelAndView給前置控制器。

    Spring MVC 有什麼好處呢? 
    框架麼,一般來講都是幫我們簡化了開發,提供了一系列方便好用的工具,以及強大的靈活性,使工程師可以更專注於業務開發,提高開發效率。如:原始的Servlet需要每個資源都需要創建一個servlet類,需要手工處理請求的參數解析,參數校驗等繁瑣操作,還有就是原始的Servlet生命週期是在servlet容器的掌管之下,而controller可以使用spring容器,由spring管理類的創建銷燬,業務service的注入等。
  2. JDBC(Java Database Connectivity)

    jdbc是用來與數據庫建立連接的,提供了操作數據庫的各種接口,jdbc設計了統一的數據庫連接規範,以分層的思想屏蔽了不同數據庫廠商之間的差異,使程序人員可以更專注於實際業務功能的實現。數據庫有什麼作用?略過……,簡單看下jdbc的使用吧。

    普通拼接SQL
    public 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取值問題。
  3. 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程序員啦。
  4. 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因爲沒有約束文件,所以變動,擴展較爲簡單,但同時也無法提供詳細的語意(節點是否必須存在,節點數量限制,數據的類型約束等)規範。
  5. 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消息發送成功,即可返回成功,而不需要關係後續所包含的的一些業務及其結果,例如發送郵件通知,贈送積分等等其他業務。

  6. 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的修改問題。各種服務的拆分問題?單人維護多個服務的問題?各位有什麼好的建議,或者書籍推薦歡迎在評論留言。

  7. 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日誌模式進行事務處理,降低了事務代碼的侵入。

     說明

     分佈式事務是分佈式系統中最核心的一個業務難題,上面僅列出了目前主流的一些解決方案,對於具體的技術細節,請自行搜索,研究。

  8. JTS

     對應用屏蔽,主要定義了JTA實現的細節,給JTA的提供者使用。JTS是一個組件事務監視器。JTS是CORBA OTS事務監控的基本實現。JTS規定了事務管理器的實現方式。JTS事務管理器爲應用服務器、資源管理器、獨立的應用以及通信資源管理器提供了事務服務。

  9. 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服務器等。

  10. 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連接池服務。

  11. 其他類 

    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):數據持久化相關。

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章