閱讀目錄

   隨著公司業(yè)務(wù)的不斷變化,幾年前的 A 項(xiàng)目和底層 DB_A 數(shù)據(jù)庫(kù)華麗轉(zhuǎn)身為核心業(yè)務(wù)服務(wù)和核心數(shù)據(jù)庫(kù)。

   想從  DB_A  數(shù)據(jù)庫(kù)獲取數(shù)據(jù)的 web 服務(wù)越來(lái)越多,項(xiàng)目之間的關(guān)系逐漸演變?yōu)橄旅孢@樣:

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   很容易看出來(lái)按上圖這樣的發(fā)展趨勢(shì)會(huì)存在很多問(wèn)題(項(xiàng)目關(guān)系為個(gè)人抽象出來(lái)的簡(jiǎn)化版,實(shí)際情況比這要復(fù)雜的多)。

   a. 當(dāng) webappA 運(yùn)行過(guò)程中出現(xiàn)異常無(wú)法訪問(wèn),webappB/ webappC .... 還能正常獲取  DB_A 數(shù)據(jù)嗎?

   b. 各種各樣的提供給 webappB/webappC ... 獲取 DB_A 數(shù)據(jù)的服務(wù)都集中在 webappA 中,webappA 的體積會(huì)無(wú)限水平擴(kuò)張,誰(shuí)都不喜歡贅肉對(duì)吧?

   c. webappA  項(xiàng)目在運(yùn)行過(guò)程中除了要正常提供自己的服務(wù)給用戶(hù)以外,還要兼顧其他項(xiàng)目獲取數(shù)據(jù)的請(qǐng)求,勢(shì)必會(huì)造成性能瓶頸。

   其中的有些問(wèn)題已經(jīng)在項(xiàng)目上線推進(jìn)中出現(xiàn)過(guò),隔三差五停機(jī)維護(hù)變成響亮的巴掌扇到項(xiàng)目組的臉上確實(shí)也不好受。

   題外話:按照目前互聯(lián)網(wǎng)的發(fā)展速度和各公司業(yè)務(wù)擴(kuò)展,能準(zhǔn)確預(yù)測(cè)項(xiàng)目?jī)赡暌詢(xún)?nèi)發(fā)展方向/并提前做好擴(kuò)展的架構(gòu)師,能力已經(jīng)非常不錯(cuò)。

   項(xiàng)目組有人提出繞開(kāi)項(xiàng)目 webappA ,其余的 webappB/webappC ...直接連接 DB_A 進(jìn)行交互,但很快被否決了(每個(gè)項(xiàng)目數(shù)據(jù)庫(kù)訪問(wèn)層你都要重新定義和編寫(xiě))。

   能否將其中的數(shù)據(jù)庫(kù)訪問(wèn)層獨(dú)立出來(lái),做為服務(wù)容許授權(quán)的項(xiàng)目進(jìn)行訪問(wèn)?如下:

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   核心想法是為無(wú)限增多的N個(gè) wabapp 提供特定數(shù)據(jù)庫(kù)的訪問(wèn)。這樣既避免了項(xiàng)目之間的耦合,也提高的數(shù)據(jù)訪問(wèn)層的重用率。

   想法已經(jīng)有了,那就開(kāi)干吧,BB 解決不了問(wèn)題。大概花了兩天時(shí)間進(jìn)行搭建,填了無(wú)數(shù)坑,終于出落的和我預(yù)想中的一樣貼切。

   原項(xiàng)目因商用無(wú)法開(kāi)源,demo 經(jīng)我重新組織已開(kāi)源到:http://git.oschina.net/LanboEx/dao-service-demo

   需要這方面實(shí)踐的同學(xué),clone 到本地跑起來(lái)一切也就明朗了。

回到頂部

1. 服務(wù)接口層

   電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   需要 DB_A 數(shù)據(jù)項(xiàng)目依賴(lài) dap-service-api 訪問(wèn) dao-service-impl 服務(wù)即可。

   dao-service-api 為提供給外層的接口,最終的呈現(xiàn)方式為 jar, maven 項(xiàng)目直接依賴(lài)即可。

   電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   如果存在老舊非 maven 項(xiàng)目,使用 maven-jar-plugin/maven-assembly-plugin 將所依賴(lài)的 jar 都裝配進(jìn)去添加到項(xiàng)目 lib 里面。

回到頂部

2. 服務(wù)實(shí)現(xiàn)層

   dao-service-impl 由 cxf + spring + druid + jpa(hibernate impl) 開(kāi)源類(lèi)庫(kù)搭建而成的純后端組件服務(wù)。

   電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   做為服務(wù)接口的實(shí)現(xiàn)層,最終呈現(xiàn)方式為 war,可進(jìn)行集群或分布式部署,給其他項(xiàng)目提供服務(wù)。

   目錄結(jié)構(gòu)一目了然,上手開(kāi)發(fā)速度很快,其中自己實(shí)現(xiàn)了簡(jiǎn)易的代碼生成(GenCodeServlet),dao 層 + webService 層 接口和實(shí)現(xiàn)都可以自動(dòng)生成。

   webSevice 實(shí)現(xiàn)層注入 dao 層接口,針對(duì)單表封裝增刪改查5個(gè)方法,大體上不用寫(xiě)多余的方法,避免編寫(xiě)百分之 90 的 SQL 。

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC)public interface UserWs {    /**
     * 通過(guò) ID 過(guò)去單個(gè) User 實(shí)體對(duì)象
     * cxf 傳輸返回對(duì)象不可為null,Dao 層獲取為null時(shí)
     * 實(shí)例化返回空對(duì)象,判空時(shí)使用對(duì)象主鍵進(jìn)行判斷即可
     *
     * @param id 主鍵ID     */
    UserPO getUser(String id);    /**
     * 通過(guò)類(lèi)似的 PO 獲取多個(gè) User 實(shí)體對(duì)象
     *
     * @param userPO 對(duì)照的實(shí)體對(duì)象     */
    List<UserPO> listUser(UserPO userPO);    /**
     * 通過(guò)類(lèi)似的 PO 獲取多個(gè) User 實(shí)體對(duì)象
     *
     * @param userPO  對(duì)照的實(shí)體對(duì)象
     * @param orderby 排序字段
     * @param asc     是否升序     */
    List<UserPO> listUserOrdrBy(UserPO userPO, String orderby, Boolean asc);    /**
     * 新增 User 實(shí)體對(duì)象
     *
     * @param userPO 要新增的對(duì)象     */
    UserPO addUser(UserPO userPO);    /**
     * 更新 User 實(shí)體對(duì)象
     *
     * @param userPO 要更新的對(duì)象     */
    UserPO updateUser(UserPO userPO);
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   開(kāi)發(fā)方式簡(jiǎn)單粗暴,使用工具反向生成 hibernate 數(shù)據(jù)庫(kù) po ,訪問(wèn) GenCodeServlet 生成 dao/ws 層接口和實(shí)現(xiàn)。

   添加配置文件選項(xiàng),發(fā)布 cxf webService 服務(wù)即可,估計(jì)5分鐘的時(shí)間都不要。

回到頂部

3. 服務(wù)調(diào)用方

   發(fā)布的單表服務(wù)在調(diào)用方里面理解為數(shù)據(jù)庫(kù)訪問(wèn)層,在你項(xiàng)目規(guī)定的地方注入,進(jìn)行耦合處理業(yè)務(wù)邏輯。

   這個(gè)模塊存在的意義,相當(dāng)于一個(gè)怎樣集成 cxf 發(fā)布的服務(wù)的 demo。

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   a.調(diào)用方項(xiàng)目中已集成了 spring (依賴(lài) dao-service-api)

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

    <jaxws:client id="UserWs" serviceClass="com.rambo.dsd.sys.ws.inter.UserWs"  address="${cxf.server.url}/UserWs">
        <jaxws:outInterceptors>
            <ref bean="wss4JOutInterceptor"/>
        </jaxws:outInterceptors>
    </jaxws:client>

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   具體的使用方式(在 spring 注入的前提下)

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

        Map<String, Object> map = new HashMap<>();
        UserWs userWs = (UserWs) SpringContextUtil.getBean("UserWs");
        UserPO user = userWs.getUser("031e7a36972e11e6acede16e8241c0fe");
        map.put("1.獲取單個(gè)用戶(hù):", user);

        user.setPhone("18975468245");
        UserPO userPO1 = userWs.updateUser(user);
        map.put("2.更新單個(gè)用戶(hù):", userPO1);

        UserPO userPO2 = new UserPO();
        userPO2.setName("rambo");
        userPO2.setPasswd(SecurityUtil.encryptMD5("123456"));
        userPO2.setSex("男");
        userPO2.setYxbz("Y");
        UserPO userPO3 = userWs.addUser(userPO2);
        map.put("3.新增單個(gè)用戶(hù):", userPO3);

        UserPO userPO4 = new UserPO();
        userPO4.setSex("男");
        List<UserPO> userPOList = userWs.listUser(userPO4);
        map.put("4.獲取所有的男用戶(hù):", userPOList);

        UserPO userPO5 = new UserPO();
        userPO5.setSex("男");
        List<UserPO> userPOList1 = userWs.listUserOrdrBy(userPO5, "sorts", true);
        map.put("5.獲取所有的男用戶(hù)并按照 sorts 字段排序:", userPOList1);        return map;

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   b.調(diào)用方項(xiàng)目中未集成 spring (依賴(lài) dao-service-api)

   使用工具或者命令生成 cxf 服務(wù)客戶(hù)端,引入工廠模式在使用的地方獲取服務(wù)實(shí)例,進(jìn)行耦合即可。

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

            UserWsImplService userWsImplService = new UserWsImplService(new URL(cxfServerUrl + "/UserWs?wsdl"));
            UserWs userWs = userWsImplService.getUserWsImplPort();
            addWSS4JOutInterceptor(userWs);

            UserPO user = userWs.getUser("031e7a36972e11e6acede16e8241c0fe");
            map.put("1.獲取單個(gè)用戶(hù):", user);

            user.setPhone("18975468245");
            UserPO userPO1 = userWs.updateUser(user);
            map.put("2.更新單個(gè)用戶(hù):", userPO1);

            UserPO userPO2 = new UserPO();
            userPO2.setUuid(StringUtil.getUUID());
            userPO2.setName("rambo");
            userPO2.setPasswd(SecurityUtil.encryptMD5("123456"));
            userPO2.setSex("男");
            userPO2.setYxbz("Y");
            UserPO userPO3 = userWs.addUser(userPO2);
            map.put("3.新增單個(gè)用戶(hù):", userPO3);

            UserPO userPO4 = new UserPO();
            userPO4.setSex("男");
            UserPOArray userPOArray1 = userWs.listUser(userPO4);
            map.put("4.獲取所有的男用戶(hù):", userPOArray1);

            UserPO userPO5 = new UserPO();
            userPO5.setSex("男");
            UserPOArray userPOArray2 = userWs.listUserOrdrBy(userPO5, "sorts", true);
            map.put("5.獲取所有的男用戶(hù)并按照 sorts 字段排序:", userPOArray2.getItem());

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

回到頂部

4. cxf 安全認(rèn)證機(jī)制

   cxf 采用 soap 通信協(xié)議,畢竟是對(duì)外發(fā)布出去的服務(wù),安全性還是很重要。

   安全認(rèn)證引入 cxf ws-security wss4j  攔截器實(shí)現(xiàn),soap 報(bào)文頭添加認(rèn)證信息。

   a.服務(wù)端配置

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   <!--服務(wù)端安全認(rèn)證回調(diào)函數(shù)-->
    <bean id="serverAuthCallback" class="com.rambo.dsd.base.handler.CXFServerAuthHandler"/>

    <!--安全日志認(rèn)證攔截器-->
    <bean id="wss4JInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="UsernameToken"/>
                <entry key="passwordType" value="PasswordDigest"/>
                <entry key="passwordCallbackRef" value-ref="serverAuthCallback"/>
            </map>
        </constructor-arg>
    </bean>

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

  服務(wù)端實(shí)現(xiàn) javax.security.auth.callback.CallbackHandler 的安全回調(diào)函數(shù):

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

public class CXFServerAuthHandler implements CallbackHandler {
    protected final static Logger log = LoggerFactory.getLogger(CXFServerAuthHandler.class);
    private static final Map<String, String> userMap = new HashMap<String, String>();

    static {
        userMap.put("webappA", "webappA2017");
        userMap.put("webappB", "webappB2017");
    }

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        for (Callback callback : callbacks) {
            WSPasswordCallback pc = (WSPasswordCallback) callback;

            String clientUsername = pc.getIdentifier();
            String serverPassword = userMap.get(clientUsername);
            log.info(" client:{} is starting webservice...", clientUsername);
            int usage = pc.getUsage();
            if (usage == WSPasswordCallback.USERNAME_TOKEN) {
                pc.setPassword(serverPassword);
            } else if (usage == WSPasswordCallback.SIGNATURE) {
                pc.setPassword(serverPassword);
            }
        }
    }
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

    b.集成 Spring 的客戶(hù)端配置

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

    <!--客戶(hù)端安全認(rèn)證回調(diào)函數(shù)-->
    <bean id="wsClientAuthHandler" class="com.rambo.dsc.handler.WsClientAuthHandler"/>

    <!--安全認(rèn)證對(duì)外攔截器-->
    <bean id="wss4JOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="UsernameToken"/>
                <entry key="user" value="webappA"/>
                <entry key="passwordType" value="PasswordDigest"/>
                <entry key="passwordCallbackRef" value-ref="wsClientAuthHandler"/>
            </map>
        </constructor-arg>
    </bean>

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   注入的 webService 服務(wù)配置攔截器:

        <jaxws:outInterceptors>
            <ref bean="wss4JOutInterceptor"/>
        </jaxws:outInterceptors>

   客戶(hù)端實(shí)現(xiàn) javax.security.auth.callback.CallbackHandler 的安全回調(diào)函數(shù):

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

public class WsClientAuthHandler implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        for (Callback callback : callbacks) {
            WSPasswordCallback pc = (WSPasswordCallback) callback;
            pc.setPassword("webappA2017");
        }
    }
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   c.未集成 Spring 的客戶(hù)端進(jìn)行編碼

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

  private void addWSS4JOutInterceptor(Object wsClass) {
        Endpoint cxfEndpoint = ClientProxy.getClient(wsClass).getEndpoint();
        Map outProps = new HashMap();
        outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
        outProps.put(WSHandlerConstants.USER,"webappA");
        outProps.put(WSHandlerConstants.MUST_UNDERSTAND, "0");
        outProps.put(WSHandlerConstants.PASSWORD_TYPE, "PasswordDigest");
        outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, WsClientAuthHandler.class.getName());
        cxfEndpoint.getOutInterceptors().add(new WSS4JOutInterceptor(outProps));
    }

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

   項(xiàng)目中服務(wù)端安全認(rèn)證使用的是 UsernameToken,cxf 支持認(rèn)證方式/密碼類(lèi)型還有很多,當(dāng)然你也可以自定義安全認(rèn)證方式。

 4.結(jié)束語(yǔ)

   互聯(lián)網(wǎng)公司服務(wù)架構(gòu)是血液,是習(xí)慣,每家公司都有自己的套路和架構(gòu),細(xì)節(jié)有不同,但是核心理念是通的。

   這次實(shí)踐有點(diǎn)微服務(wù)的感覺(jué),但還遠(yuǎn)遠(yuǎn)不夠,如服務(wù)的注冊(cè)/路由/容錯(cuò)/緩存.....很多很多,項(xiàng)目已開(kāi)源到上面,有興趣一起完善它吧。

   

作者:Orson 
出處:http://www.cnblogs.com/java-class/ 
如果,您認(rèn)為閱讀這篇博客讓您有些收獲,不妨點(diǎn)擊一下右下角的【推薦】 
如果,您希望更容易地發(fā)現(xiàn)我的新博客,不妨點(diǎn)擊一下左下角的【關(guān)注我】 
如果,您對(duì)我的博客內(nèi)容感興趣,請(qǐng)繼續(xù)關(guān)注我的后續(xù)博客,我是【Orson】 

本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段 聲明,且在文章頁(yè)面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。 

http://www.cnblogs.com/java-class/p/7064163.html