跟微软保持适当距离--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>
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>
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>
用户登陆状态问题
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 } }
服务实现类:
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("未登录或登录超时!"); } } }
.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); } } }