遗忘海岸

江湖程序员 -Feiph(LM战士)

导航

跟微软保持适当距离--Hessian + .net 实现RPC体系的企业应用

同在一个产业链园区的XX厂因为5台Window2003服务器收到了律师函并且被迫下了12万$的采购单,虽然100万对XXX厂来数不是大数目,但是总有种被打劫的感觉。

在企业ERP应用中服务层一般都是做成远程调用的,具体Windows平台的技术有WebService,WCF,Remoting等,这里展示的是服务端采用linux 平台下采用Hessian组件实现RPC.

服务端:
Web服务器:JBoss,tomcat (weblogic挺美但是不免费啊)
数据库:mysql(一般erp都用oracle做数据库,当然那个啥费用也是不含糊地)
客户端:
逆天的XP(sp3) 加.net4.0 (NND,这个组合量你也收不了多少钱把!)
VS2010? 我们用180试用版或者那啥notepad

 

Hessian+spring配置
1.web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>HessianTest</display-name>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:conf/ht-core.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>hessionRpc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath*:conf/ht-rpc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>hessionRpc</servlet-name>
    <url-pattern>/rpc/*</url-pattern>
  </servlet-mapping>

  <servlet>
    <description></description>
    <display-name>TestSpring</display-name>
    <servlet-name>TestSpring</servlet-name>
    <servlet-class>f.studio.web.servlet.TestSpring</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>TestSpring</servlet-name>
    <url-pattern>/TestSpring</url-pattern>
  </servlet-mapping>
  <filter>
    <display-name>HessianCtxFilter</display-name>
    <filter-name>HessianCtxFilter</filter-name>
    <filter-class>f.studio.web.filter.HessianCtxFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>HessianCtxFilter</filter-name>
    <url-pattern>/rpc/*</url-pattern>
  </filter-mapping>
  
   <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
</web-app>
View Code

2.Service,DAO等spring配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    
        <bean name="studentServiceImpl"
            class="f.studio.service.impl.StudentServiceImpl" scope="prototype" />
</beans>
View Code

3.Hessian导出层spring配置,因为一个项目里可能使用strut2,Servlet,cxf等服务提供层,但是他们需要共用Service,DAO等
参考:http://jinnianshilongnian.iteye.com/blog/1602617

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

    <bean name="/studentServiceRpc"
        class="org.springframework.remoting.caucho.HessianServiceExporter">
        <property name="service" ref="studentServiceImpl" />
        <property name="serviceInterface" value="f.studio.service.StudentService" />
    </bean>
    
</beans>
View Code

 

用户登陆状态问题
Hessian的C#实现可以自己保存cookie,并且是全局的(应用程序范围)
服务端尝试使用Hessian提供的ServiceContext获取对Session的引用但是结果总为null,所以写个filter 来自己维护用户登陆Session供Service实现类使用

package f.studio.web.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import f.studio.util.HessianCtxContainer;

/**
 * Servlet Filter implementation class HessianCtxFilter
 */
public class HessianCtxFilter implements Filter {

    public static final String LOGIN_SESSION_KEY="LOGIN_USER_KEY"; 
    /**
     * Default constructor. 
     */
    public HessianCtxFilter() {
        // TODO Auto-generated constructor stub
    }

    /**
     * @see Filter#destroy()
     */
    public void destroy() {
        // TODO Auto-generated method stub
    }

    /**
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpSession session = ((HttpServletRequest) request).getSession(true);
        System.out.println("sessionId:" + session.getId());
        try {
            
            Object user = session.getAttribute(LOGIN_SESSION_KEY);
            HessianCtxContainer.setAttribute(LOGIN_SESSION_KEY, user);
            System.out.println("sessionUser:" + user);
            chain.doFilter(request, response);
            
        } finally {
            //移除掉ThreadLocal中Map中的对象防止益出
            //session具备自动移动除功能
            //不要在环境下(cxf,strut2等)使用HessionCtxContainer,避免线程重用时造成混乱
            Object user = HessianCtxContainer.remove(LOGIN_SESSION_KEY);
            session.setAttribute(LOGIN_SESSION_KEY, user);
        }
        
    }

    /**
     * @see Filter#init(FilterConfig)
     */
    public void init(FilterConfig fConfig) throws ServletException {
        // TODO Auto-generated method stub
    }

}
View Code

服务实现类:

package f.studio.service.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.omg.PortableInterceptor.USER_EXCEPTION;

import com.caucho.hessian.io.Hessian2Output;
import com.caucho.services.server.ServiceContext;

import f.studio.domain.Klass;
import f.studio.domain.QueryStudentInfo;
import f.studio.domain.StudentInfo;
import f.studio.service.StudentService;
import f.studio.util.HessianCtxContainer;
import f.studio.web.filter.HessianCtxFilter;

public class StudentServiceImpl implements StudentService {

    public List<StudentInfo> query(QueryStudentInfo q) {
        
        CheckLogin();
        
        ServletRequest request=  ServiceContext.getContextRequest();
        System.out.println(ServiceContext.getServiceName());
        //HttpSession session= request.getSession(true);
        //session.setAttribute("User", new Date());
        
        System.out.println("ServiceImpHashCode:" + this.hashCode());
        System.out.println(q);
        //if(1==1)throw new RuntimeException("运行错误信息啊");
        List<StudentInfo> list=new ArrayList<StudentInfo>();
        Klass klass=new Klass();
        klass.setId(9999);
        klass.setName("张老师");
        klass.setAddTime(new Date());
        
        for(int i=0;i<10;i++){
            StudentInfo s=new StudentInfo();
            
            //===父类
            s.setRecId(88888);
            s.setCreateDate(new Date());
            //==
            s.setId(i);
            s.setName("张思念" + i);
            s.setSex(i % 5 ==0);
            //===添加两个元素==
            s.getKs().add(klass);
            s.getKs().add(klass);
        
            list.add(s);
        }
        return list;
    }

    public String hello(String name) {
            return "Hi " +name;
    }

    public void Login(String username, String password) {
        if("Admin".equals(username) && "123".equals(password)){
            HessianCtxContainer.setAttribute(HessianCtxFilter.LOGIN_SESSION_KEY , username);
            return;
        }
        throw new RuntimeException("错误的用户名或密码!");
    }

    public static void CheckLogin(){
        
        if(HessianCtxContainer.getAttribute(HessianCtxFilter.LOGIN_SESSION_KEY)==null){
            throw new RuntimeException("未登录或登录超时!");
        }
    }
}
View Code

 

.net客户端,需要添加对hessianCsharp.dll的引用
调用一次Login后,再执行其他调用

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using hessiancsharp.client;

namespace HessianTest
{
    using f.studio.domain;
    using System.Threading;
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }



        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                CHessianProxyFactory factory = new CHessianProxyFactory("userName", "password");
                string url = "http://localhost/HessianTest/rpc/studentServiceRpc";//修改为你的server端地址
                StudentService test = (StudentService)factory.Create(typeof(StudentService), url);
                string result = test.hello("大白鲨");
                var q = new QueryStudentInfo() { BTime = DateTime.Now, Name = "哈哈", Id = 1888, Sex = false };
                q.Data = new byte[] { 5, 4, 3, 2, 1 };
                q.CreateDate = DateTime.Now;
                q.RecId = 99999;

                var list = test.query(q);
                foreach (var it in list)
                {
                    Console.WriteLine(it);
                }
                Console.WriteLine(result);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        

        }


        private void button5_Click(object sender, EventArgs e)
        {
            try
            {
                CHessianProxyFactory factory = new CHessianProxyFactory("userName", "password");
                string url = "http://localhost/HessianTest/rpc/studentServiceRpc";//修改为你的server端地址
                StudentService test = (StudentService)factory.Create(typeof(StudentService), url);
                test.Login("Admin", "123");
                
                Console.WriteLine("登陆成功");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }

    public interface StudentService
    {
         string hello(string name);
         List<StudentInfo> query(QueryStudentInfo q);
         void Login(String usename, String password);
    }




}

namespace f.studio.domain
{
    public class BaseInfo
    {
        private DateTime? createDate;

        public DateTime? CreateDate
        {
            get { return createDate; }
            set { createDate = value; }
        }
        private long? recId;

        public long? RecId
        {
            get { return recId; }
            set { recId = value; }
        }
    }
    /// <summary>
    /// 上传时用需要保持命名空间与服务器一致
    /// </summary>
    public class QueryStudentInfo :BaseInfo
    {
        private int id;
        private String name;
        private DateTime? btime;
        private Byte[] data;
        private bool sex;

        public int Id
        {
            get { return id; }
            set { id = value; }

        }
        public DateTime? BTime
        {
            get { return btime; }
            set { btime = value; }
        }
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        public Byte[] Data
        {
            get { return data; }
            set { data = value; }
        }
        public bool Sex
        {
            get { return sex; }
            set { sex = value; }
        }
    }


    /// <summary>
    /// 不能使用public int Id{get;set;}
    /// private 字段名称,大小写需要跟服务端定义一致
    /// [Serializable]标记貌似不是必须的
    /// </summary>
    public class Klass :BaseInfo
    {
        private int id;
        private String name;
        private DateTime? addTime;

        public DateTime? AddTime
        {
            get { return addTime; }
            set { addTime = value; }
        }
        public string Name
        {
            get { return name; }
            set { name = value; }
        }


        public int Id
        {
            get { return id; }
            set { id = value; }
        }

    }


    public class StudentInfo :BaseInfo
    {
        private string name;
        private bool? sex;
        private long id;
        private byte[] fileData;

        public byte[] FileData
        {
            get { return fileData; }
            set { fileData = value; }
        }
        private List<Klass> ks;

        public List<Klass> Ks
        {
            get { return ks; }
            set { ks = value; }
        }


        public string Name
        {
            get { return name; }
            set { name = value; }
        }


        public long Id
        {
            get { return id; }
            set { id = value; }
        }


        public bool? Sex
        {
            get { return sex; }
            set { sex = value; }
        }
        public override string ToString()
        {
            return string.Format("Id:{0},Name:{1},Sex:{2},RecId:{3},CreateDate:{4}", Id, Name, Sex,RecId,CreateDate);
        }

    }
}
View Code


完成服务端代码下载

posted on 2013-08-01 10:24  遗忘海岸  阅读(3475)  评论(20编辑  收藏  举报