EJB开发实例CoursesOnline 1st Edition

来源:互联网 发布:mysql show 编辑:程序博客网 时间:2024/04/23 17:48
标题:EJB开发实例CoursesOnline 1st Edition
[评论]

作者:陈小冲 dev2dev idchenxc

一、CoursesOnline简介

       CoursesOnline是一个实验性质的系统,CoursesOnline是“在线课程”的意思,在这个CoursesOnline系统里,学生选择课程,老师可以开设课程,系统管理员则对学生、老师以及课程进行管理。

CoursesOnline使用J2EE来实现,目的是为其它EJB系统的开发提供一些参考。    

二、开发环境

       CoursesOnline使用Jbuilder9 + Oracle9i + WebLogic7的开发环境。

       因为J2EE是一种行业标准,所以采用哪种开发环境的搭配并不是最重要的。目前其它常见开发环境的搭配还有Eclipse + MySQL + Jboss(都是OpenSource),VJA + DB2 + WebSphere,等等。

       Jbuilder9 + Oracle9i + WebLogic 7开发环境的配置请参考附录A

三、CoursesOnline需求分析                         

3.1 CoursesOnline用例

上图是CourcesOnlineUse Case Diagram。显而易见,系统中有学生、老师和系统管理员三种Actor。学生需要注册成系统用户后才能浏览课程和选课。

3.2 数据库建模(ER图)及数据字典

       上图是CoursesOnline的数据库ER图,建模工具是ERWin。顺便提一下,ERWin的正向工程支持ER图直接生成数据库表结构,逆向工程支持数据库表结构生成ER图。

       CoursesOnline使用到的表有5个,数据字典如下:

3.2.1 Actor登录信息表(Actor

序号

字段名

数据类型

约束

备注

1

ActorID

SmallInt

PK

Actor标识符

2

UserName

Varchar(20)

 

Actor登录帐号

3

Password

Char(8)

 

Actor登录口令

4

ActorType

SmallInt

 

Actor类型,0:系统管理员;1:老师;2:学生

3.2.2 Actor基本信息表(ActorInfo

序号

字段名

数据类型

约束

备注

1

ActorID

SmallInt

FK(Actor)

Actor标识符

2

ActorName

Varchar(20)

 

Actor姓名

3

Phone

Varchar(16)

 

电话

4

Email

Varchar(50)

 

Email

3.2.3 课程信息表(Courses

序号

字段名

数据类型

约束

备注

1

CoursesID

SmallInt

PK

课程标识符

2

CoursesName

Varchar(20)

 

课程名称

3

StartDate

Date

 

课程开始时间

4

EndDate

Date

 

课程结束

5

ActorID

SmallInt

FK(Actor)

 

6

RoomID

SmallInt

FK(Room)

教室标识符

3.2.4 学生选课表(Appointment

序号

字段名

数据类型

约束

备注

1

ActorID

SmallInt

FK(Actor)

Actor标识符(学生,ActorType=2

2

CoursesID

SmallInt

FK(Courses)

课程标识符

3.2.5 教室信息表(Room

序号

字段名

数据类型

约束

备注

1

RoomID

SmallInt

PK

教室标识符

2

RoomName

Varchar(30)

 

教室名称

四、CoursesOnline系统设计

4.1 Design Pattern的选择和思考

       在系统设计上,Design Pattern的选择是很重要的。因为正确的Design Pattern不仅在开发阶段可以让开发人员思路清晰得心应手,而且在维护阶段也不至于让维护人员抓狂。除此之外,对于系统的健壮和运行效率而言也起着举足轻重的作用。如果采用了错误的Design Pattern,那么对于系统来说就像是在错误的时间错误的地点与错误的敌人打了一场错误的战争。

       下面以CoursesOnline系统学生注册时采用的两种不同的处理方法为例来简单说明选择Design Pattern的重要性。

学生在Client提交注册信息,包括登录帐号,登录口令,姓名,电话和Email五项内容。从数据库ER图中我们可以看到学生信息被分散在两张表里,也就是说服务器端有两个Entity Bean来存取学生的注册信息,一个为Actor,另一个为ActorInfo

方法一:客户端直接与Entity Bean沟通以完成工作

       方法二:客户端与Session Bean交互,由Session BeanEntity Bean沟通以完成工作

       方法一的设计虽然也能完成对学生注册的处理,但是客户端必须写所有的业务逻辑代码,而且由于客户端直接访问Entity Bean,不但造成了多次的网络roundtrip,使执行效率大幅下降,也使客户端与Entity Bean形成强耦合,日后不管修改客户端还是Entity Bean,都会牵一发而动全身,对系统的改造简直就是一场灾难。

       方法二不但大幅降低了网络的roundtrip,而且Session Façade分隔了客户端和Entity BeanEntity Bean对于客户端来说是透明的,客户端需要关心的只是Session Façade提供的接口。这样一来系统的可扩展性就得到了质的提升。

4.2 CoursesOnline系统示意图

五、创建数据库

       先有鸡再有蛋?还是先有蛋再有鸡?

       先有数据库表再有Entity Bean?还是先有Entity Bean再有数据库表?

       这两个问题有异曲同工之妙。鸡与蛋的问题已经讨论几千年了,哲学的Big Fans可能会争的脸红耳赤唾沫横飞;偶们只是普通的程序员,谁先谁后的问题还留给理论学家吧。

       可能会有人先设计Entity Bean再建数据库表,也有可能反其道而行。在CoursesOnline这个实验性质系统的开发过程中,是先建数据库表然后才有Entity Bean

5.1 新建一个数据库

       Oracle可以在命令行模式下敲入dbca,或者直接在开始菜单里找到并运行Database Configuration Assistant,然后根据向导的提示新建一个名为CoursesDB的数据库。

       Oracle 9i新建数据库的详细过程请参考Oracle的相关文档。

5.2 为数据库创建一个用户

       在命令行下敲入oemapp console,或者直接在开始菜单里找到EnterPrise Manager Console打开Oracle管理控制中心,以SYSDBA的身份进入CoursesDB数据库后,在安全性->用户中新增一个用户,如chenxc,口令chenxc,并赋予dba的角色。这些设置在接下来的CourseseOnline系统具体开发中会用到。

       Oracle 9i新建用户的具体操作请参考Oracle的相关文档。

5.3 建表及表的初始化

5.3.1 建表

       建表的方法有N种:1)用sqlplus连上CoursesDB数据库后,用sql语句把数据字典中列出来的表结构敲进去;2)把数据字典中表结构写成sql脚本文件,然后用sqlplus连上CoursesDB,执行SQL>@@ c:CoursesDB.sql3)使用一种支持正向工程的数据库建模工具直接把ER图转换成数据库表结构,如ERWin4)使用可视化工具建表,如PLSQL Developer

       其它的我知道的还有我还不知道的数据库的建表方法,在这里就不一一列举了,有兴趣的请自行研究,然后把经验告诉大家。

       附录C提供了CoursesOnline建表及表的初始化的sql脚本。

5.3.2 表的初始化

       CoursesOnline用例图中可以看出,系统没有提供系统管理员、老师以及教室的管理的接口,系统管理员、老师和教室的信息在数据库建完后就应该初始化了。也就是说,这些信息是已经存在的,除非直接操作数据库,否则无法改变系统管理员、老师和教室的信息。

       建完表后,我们为CoursesOnline系统初始化了一位系统管理员,名为sysadmin;三位老师,分别为任我行、东方不败和岳不群;除此之外还初始化了三间教室,分别为黑木崖教室1、黑木崖教室2和华山教室3

附录C提供了CoursesOnline建表及表的初始化的sql脚本。

六、CoursesOnline的具体实现

6.1 Jbuilder 9中新建一个工程

启动Jbuilder 9File->New Project,项目名为CoursesOnline,选择路径后,点击finish

因为CoursesOnline项目使用Oracle 9i数据库,所以还需要把Oracle的驱动加载进来,Project -> Project Properties -> Path -> Required libraries,点击add,在Select One Or More Libraries窗口中选择OracleJDBCLib

       OracleJDBCLib的配置请参考附录A.5.3 配置数据库驱动

6.2 创建Entity BeanCMP

6.2.1 新建一个EJB Module

File -> New -> Enterprise ->EJB Module

       点击ok,并在接下来的窗口中为EJB Module命名为Courses,结果如下图

6.2.2 Import Schema From Database

       在上图中的DataSources上点击右键,或者在Courses设计面板上点击右键,然后点击Import Schema From Database,在弹出的窗口中输入DriverURL等参数,如下图所示

       点击ok,这时数据库中的5个表在DataSources面板中显示出来,如下图所示

6.2.3 创建Entity Bean

       在上图中DataSources面板中右键Actor,在弹出菜单中选择Create CMP 2.0 Entity Bean,结果如下图

       左键点击Entity Bean Actor可以在弹出的窗口中编辑它的属性,在这里我们暂时使用默认值。接下来用同样的方法为其它四个表创建CMP 2.0 Entity Bean,结果如下图

       从左边的Project下拉菜单中可以看到,每个Entity Bean都有三个(或四个)Java程序与之相对应,例如Actor.javaActorBean.javaActorHome.java。为了便于管理,以及避免和后面的程序胡搅蛮缠搞得眼睛高度紧张,我把Entity Bean对应的java程序都放在com.chenxc.coursesOnline.ejb20下。点击Entity Bean的名称,在Bean Properties窗口中点击Classes And Packages…按钮,然后为每个类指定路径。

6.2.4 阶段总结

       这一节中创建了五个CMP 2.0 Entity Bean,是CMP而不是BMP,是2.0而不是1.x,是local接口而不是remote接口,而且还没有动手写过任何代码。本文并不打算深入探讨EJB的内部机制,而仅仅是举例子来说明使用EJB实现分层思想及分布式计算的方法。如果要研究EJB的细节,请参考附录B 参考资料列举的书籍或文章。

       当前阶段的五个Entity Bean只能算是半成品,要完成业务逻辑还需要进一步加工,这些在接下来开发Session Bean的过程中会提到。欲知后事如何,请听下回分解。

6.3 创建Session BeanStateless

6.3.1 SessionFacade ? 连接StrutsEntity Bean的桥梁

       新建一个名为SessionFacadeSession BeanSessionFacade负责与Entity Bean沟通,并为Struts提供接口。

这个Session Bean扮演一个中间人的角色,就像是北京西客站的售票厅,旅客可以买T97的火车票去往广州,也可以买T31的火车票去往上海。在CoursesOnline系统中,Struts跟购买火车票的旅客一样,通过SessionFacade提供的接口完成系统登录、学生注册、学生选课等操作。

下面以Actor登录为例来说明SessionFacade是怎样扮演中间人的角色,如下图:

       SessionFacade中新增一个名为actorLogin的方法,返回值boolean,输入参数用户名,口令和用户类型(学生、老师或系统管理员),接口remote

       方法actorLogin提供给远程的Struts调用。

登录处理流程如下:

       1、用户在页面上输入用户名、口令和用户类型并提交;

       2JavaBean存储用户在页面提交上来的用户名、口令和用户类型;

       3Struts读取JavaBean的用户名、口令和用户类型;

       4Struts通过远程接口调用SessionFacadeactorLogin(String username,String Password,int actortype)方法;

       5SessionFacade通过本地接口调用Entity BeanActor)的findByName(String username,int actortype)方法;

       6SessionFacade获取Entity BeanActor)的findByName(String username,int actortype)方法的返回值;

       7SessionFacadeactorLogin(String username,String Password,int actortype)方法中比较Entity Bean(Actor)findByName(String username,int actortype)得到的password的值;

       8、如果两个password的值相等,则actorLogin(String username,String Password,int actortype)返回true,否则返回false

       9Struts根据SessionFacadeactorLogin(String username,String Password,int actortype)的返回值(true or false)判断登录是否成功,并导航到对应的页面。

6.3.2 登录流程的代码

/*----------------------------------------------------*/

//1、用户登录界面 - login.jsp

<!- 部分HTML代码此处省略 -- >

<form name="teaForm" method="post" action="<%=contextPath%>/login.do">

<table border="0" cellspacing="0" cellpadding="0">

<tr>

<td width="60"><font size="2">用户类型</font></td>

<td>

<input type="radio" value="0" name="actortype" checked><font size="2">系统管理员 </font>

<input type="radio" value="1" name="actortype"><font size="2">学生 </font>

<input type="radio" value="2" name="actortype"><font size="2">老师 </font>

</td>

</tr>

<tr>

<td width="60"><font size="2">用户名</font></td>

<td><input name="username" value="" size="12"> *</td>

</tr>

<tr>

<td width="60"><font size="2">口令</font></td>

<td><input type="password" name="password" value="" size="8" maxlength="8"> *</td>

</tr>

</table>

<br>

<table border="0" cellspacing="0" cellpadding="0">

<tr>

<td width="50">&nbsp;</td>

<td><input type="submit" value="  "></td>

</tr>

</table>

</form>

/*----------------------------------------------*/

//2Struts程序

//2.1 LoginForm.java(存储用户在页面上提交的用户名、口令和用户类型)

package com.chenxc.coursesonline.struts;

/**

 * Title:         LoginForm

 * Description:   存储登录信息

 * Time:         2004-3-20

 * Company:      

 * Author:        chenxc

 * version 1.0

 */

import java.util.*;

public class LoginForm extends org.apache.struts.action.ActionForm{

  private String username;  //用户名

  private String password;  //口令

  private int actortype;  //用户类型

//用户名

  public void setUsername(String username) {

    this.username = username;

  }

  public String getUsername() {

    return username;

  }

//口令

  public void setPassword(String password) {

    this.password = password;

  }

  public String getPassword() {

    return password;

  }

//用户类型

  public void setActortype(int actortype)

  {

    this.actortype = actortype;

  }

  public int getActortype()

  {

    return actortype;

  }

}

//2.2 LoginAction.java(控制登录流程的走向)

package com.chenxc.coursesonline.struts;

/**

 * Title:         LoginAction

 * Description:   登录验证

 * Time:         2004-3-20

 * Company:      

 * Author:        chenxc

 * version 1.0

 */

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

import java.util.*;

import org.apache.struts.action.*;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.ServletException;

import com.chenxc.coursesonline.struts.*;

public class LoginAction extends org.apache.struts.action.Action{

  public ActionForward execute(ActionMapping mapping,ActionForm actionForm,HttpServletRequest request,HttpServletResponse response) throws Exception

  {

    ActionErrors errors = new ActionErrors();

    HttpSession session = request.getSession();

    ActionForward forward = null;

    LoginForm loginForm = (LoginForm)actionForm;

    FacadeBean facadeBean = new FacadeBean();

    if(facadeBean.actorlogin(loginForm.getUsername(),loginForm.getPassword(),loginForm.getActortype()))

    {

      forward = mapping.findForward("success");

      return forward;

    }

    return (new ActionForward(mapping.getInput()));

  }

}

//2.3 FacadeBean.java(实现登录验证)

package com.chenxc.coursesonline.struts;

/**

 * Title:         FacadeBean

 * Description:   负责与Session Bean沟通

 * Time:         2004-3-20

 * Company:      

 * Author:        chenxc

 * version 1.0

 */

import com.chenxc.coursesonline.ejb20.*;

import javax.naming.*;

import java.util.Properties;

import javax.rmi.PortableRemoteObject;

import javax.ejb.CreateException;

import java.rmi.RemoteException;

public class FacadeBean

    extends Object {

  private static final String ERROR_NULL_REMOTE = "Remote interface reference is null.  It must be created by calling one of the Home interface methods first.";

  private static final int MAX_OUTPUT_LINE_LENGTH = 100;

  private CoursesFacadeHome coursesFacadeHome = null;

  private CoursesFacade coursesFacade = null;

//Construct the FacadeBean

  public FacadeBean() {

    initialize();

  }

  public void initialize() {

    try {

      //get naming context

      Context context = getInitialContext();

      //look up jndi name

      Object ref = context.lookup("CoursesFacade");

      //look up jndi name and cast to Home interface

      coursesFacadeHome = (CoursesFacadeHome) PortableRemoteObject.narrow(ref,

          CoursesFacadeHome.class);

    }

    catch (Exception e) {

      e.printStackTrace();

    }

  }

  private Context getInitialContext() throws Exception {

    String url = "t3://192.168.100.134:7001";

    String user = null;

    String password = null;

    Properties properties = null;

    try {

      properties = new Properties();

      properties.put(Context.INITIAL_CONTEXT_FACTORY,

                     "weblogic.jndi.WLInitialContextFactory");

      properties.put(Context.PROVIDER_URL, url);

      if (user != null) {

        properties.put(Context.SECURITY_PRINCIPAL, user);

        properties.put(Context.SECURITY_CREDENTIALS,

                       password == null ? "" : password);

      }

      return new InitialContext(properties);

    }

    catch (Exception e) {

      throw e;

    }

  }

//actorlogin

  public boolean actorlogin(String username, String password, int actortype) {

    try {

      coursesFacade = coursesFacadeHome.create();

      if (coursesFacade.actorLogin(username, password, actortype)) {

        return true;

      }

      else

      {

        return false;

      }

    }

    catch (CreateException ex) {

      ex.printStackTrace();

    }

    catch (RemoteException ex) {

      ex.printStackTrace();

    }

    return false;

  }

}

/*---------------------------------------*/

//3Session Bean程序

//3.1 SessionFacadeHome.java

package com.chenxc.coursesonline.ejb20;

import javax.ejb.*;

import java.util.*;

import java.rmi.*;

public interface SessionFacadeHome extends javax.ejb.EJBHome {

  public SessionFacade create() throws CreateException, RemoteException;

}

//3.2 SessionFacade.java

package com.chenxc.coursesonline.ejb20;

import javax.ejb.*;

import java.util.*;

import java.rmi.*;

public interface SessionFacade extends javax.ejb.EJBObject {

  public boolean actorLogin(String actor, String password, int actortype) throws RemoteException;

}

//3.2 SessionFacadeBean.java

package com.chenxc.coursesonline.ejb20;

import javax.ejb.*;

import javax.naming.*;

import java.rmi.RemoteException;

public class SessionFacadeBean implements SessionBean {

  SessionContext sessionContext;

  ActorHome actorHome;

  Actor actor;

  public void ejbCreate() throws CreateException {

    try

    {

      Context ctx = new InitialContext();

      actorHome = (ActorHome)ctx.lookup("Actor");

    }

    catch (Exception ex) {

      throw new EJBException(ex);

    }

  }

  public void ejbRemove() {

    /**@todo Complete this method*/

  }

  public void ejbActivate() {

    /**@todo Complete this method*/

  }

  public void ejbPassivate() {

    /**@todo Complete this method*/

  }

  public void setSessionContext(SessionContext sessionContext) {

    this.sessionContext = sessionContext;

  }

  public boolean actorLogin(String username, String password, int actortype) {

    try

    {

      actor = actorHome.findByName(username,actortype);

      if(actor.getPassword().equals(password))

      {

        System.out.println(username + " 登录成功!");

        return true;

      }

    }catch(ObjectNotFoundException ex) {

    }

    catch(Exception ex) {

      ex.printStackTrace();

    }

    return false;

  }

}

/*-------------------------------*/

//4、为Entity Bean(Actor)新增一个findByNameFinder,如下图

       FinderfindByName,返回值Actor,输入参数用户名,用户类型,接口local homeEJB-QL: SELECT OBJECT(a) FROM Actor AS a WHERE a.username = ?1 AND a.actortype = ?2

6.3.3 阶段总结

       Client -> Struts -> Session Bean -> Entity Bean,实现一个登录流程用了十几个java程序,真是累人的说。一个JSP程序就可以实现的功能却要大阵战对待,杀鸡用牛刀乎?

       答案是否定的,因为EJB存在的意义在于分布式计算和分层的思想。

6.4未完成的

       写到这里,CoursesOnline系统只实现了系统登录验证的功能,主要的业务逻辑基本上没实现,但MVC框架,Façade Design Pattern都或多或少有所描述,继续完成CoursesOnline系统应该不会太困难。

       另外由于使用CMP查询记录集时处理起来比较麻烦,所以在浏览课程或浏览学生等记录集查询时可以考虑用BMP来实现,或者干脆在Session Bean实现。

附录A 开发环境的配置

A.1 安装Jbuilder 9

请参考Jbuilder 9的安装手册

A.2 安装Oracle 9i

请参考Oracle 9i的安装手册

A.3 安装WebLogic 7

请参考WebLogic 7的安装手册

A.4 创建WebLogic Domain

请参考WebLogic创建Domain的相关资料

A.5 配置Jbuilder 9

A.5.1选择Tools->Configure Servers配置Server信息

Home Directory:选择Weblogic安装目录下的Server目录,如:C:eaweblogic700server

Main Class,VM Parameters:系统会自动获得,不用修改

Working Directory:创建的新Domain目录 如:C:eauser_projectsmydomain

Class 中:由于没有Weblogic的SP包,删除weblogic_sp.jar包,仅保留weblogic.jar

JDK Install directory:选择安装Weblogic目录下的JDK目录 如:D:eajdk131_03

BEA Home directory: 选择安装Weblogic目录  如:C:ea

User Name and Password:创建Domain时Administor的用户名称和密码

Domain Name and Server Name:系统会自动获得

A.5.2配置缺省工程的Server信息(Project->Default Projects Properties)

这样一来新建的工程默认情况下就使用这种Server配置

A.5.3配置数据库驱动

点击Add按钮

在本机Oracle的目录下选择Oracle JDBC lib的路径,如D:OracleOra92jdbclibclasses12.jar

配置数据库驱动后需重启JB才能使设置生效

附录B 参考资料

1)《Mastering EJB 2

2)《Enterprise JavaBeans, 3rd Edition

3)《EJB Design Pattern

4http://java.sun.com

5http://dev2dev.bea.com.cn

附录C 建表及表初始化的sql脚本

C.1 建表

CREATE TABLE Appointment (ActorID SMALLINT NOT NULL,CoursesID SMALLINT NOT NULL);

ALTER TABLE Appointment ADD ( PRIMARY KEY (ActorID, CoursesID) ) ;

CREATE TABLE Courses (

       CoursesID            SMALLINT NOT NULL,

       CoursesName          VARCHAR(30) NULL,

       StartDate            DATE NULL,

       EndDate              DATE NULL,

       RoomID               SMALLINT NULL,

       ActorID              SMALLINT NULL

);

ALTER TABLE Courses ADD ( PRIMARY KEY (CoursesID) ) ;

CREATE TABLE ActorInfo (

       ActorID              SMALLINT NOT NULL,

       ActorName            VARCHAR(30) NULL,

       Phone                VARCHAR(16) NULL,

       Email                VARCHAR(50) NULL

);

ALTER TABLE ActorInfo ADD ( PRIMARY KEY (ActorID) ) ;

CREATE TABLE Actor (

       ActorID              SMALLINT NOT NULL,

       UserName             VARCHAR(20) NULL,

       Password             CHAR(8) NULL,

       ActorType            SMALLINT NULL

);

ALTER TABLE Actor ADD ( PRIMARY KEY (ActorID) ) ;

CREATE TABLE Room (RoomID SMALLINT NOT NULL,RoomName VARCHAR(20) NULL);

ALTER TABLE Room ADD ( PRIMARY KEY (RoomID) ) ;

ALTER TABLE Appointment ADD ( FOREIGN KEY (CoursesID) REFERENCES Courses ) ;

ALTER TABLE Appointment ADD ( FOREIGN KEY (ActorID) REFERENCES Actor ) ;

ALTER TABLE Courses ADD ( FOREIGN KEY (ActorID) REFERENCES Actor ) ;

ALTER TABLE Courses ADD ( FOREIGN KEY (RoomID) REFERENCES Room ) ;

ALTER TABLE ActorInfo ADD ( FOREIGN KEY (ActorID) REFERENCES Actor ) ;

C.2表的初始化

--1)初始化系统管理员

insert into actor values(1,sysadmin,sysadmin,0);

insert into actorinfo values(1,’sysadmin’,66668888,chenxc@263.net);

--2)初始化老师

--增加任我行老师

insert into actor values(2,’rwx’,’rwx’,1);

insert into actorinfo values(2,’任我行’,’77778888’,’rwx@courses.com.cn’);

--增加东方不败老师

insert into actor values(3,’dfbb’,’dfbb’,1);

insert into actorinfo values(3,’东方不败’,’88888888’,’dfbb@courses.com.cn’);

--增加岳不群老师

insert into actor values(4,’ybq’,’ybq’,1);

insert into actorinfo values(4,’岳不群’,’99998888’,’ybq@courses.com.cn’);

--3)初始化教室

insert into room values(1,’黑木崖教室1’);

insert into room values(2,’黑木崖教室2’);

insert into room values(3,’华山教室3’);

 

附录D 关于作者

姓名:陈小冲

昵称(笔名):疾风之鹰

简介:20016月毕业于北京化工大学计算机专业,毕业后一直在创原天地从事J2EE开发工作。

原创粉丝点击