分享
 
 
 

如何编写Enterprise bean的客户端

王朝other·作者佚名  2008-05-31
窄屏简体版  字體: |||超大  

一、Enterprise JavaBean的客户视图:

Enterprise JavaBean客户是一个独立的应用程序,或者是一个Servlet,或者一个Applet,还或者另一个Enterprise JavaBean。无论如何,客户要使用一个Enterprise JavaBean必须要做如下事情:

首先要JavaBean的home接口:EJB 规范里面要求客户应该使用Java的名字和目录接口JNDI(Java Naming and Direcotry Interface)API来定位Bean的home接口。

然后获得Enterprise JavaBean的remote接口的引用:可以使用Enterprise bean的home接口中定义的方法。可以产生一个会话bean,也可以产生或寻找(find)一个实体bean。

调用Enterprise JavaBean中定义的方法:客户并不直接调用Enterprise JavaBean中定义的方法。而是通过调用在Enterprise JavaBean的remote接口中暴露给用户的方法来使用JavaBean类中的方法。在remote接口中定义的方法就是在JavaBean中希望暴露给用户,让用户使用的方法。

初始化客户:

在IAS所带的例子里面,作为客户的SortClient应用程序引入了必要的JNDI类、还引入了SortBean的home接口和remote接口。客户使用JNDI的API来定位Enterprise JavaBean的Home接口。

定位Home接口:

客户用JNDI定位home接口。首先需要获得一个JNDI的初始化的上下文context。下面的代码实例化了一个新的javax.naming.Context类。在我们这个例子中叫做initialContext。然后,客户就可以使用上下文的lookup()方法从一个名字对应到home接口。注重,初始的命名上下文工厂的初始化是由EJB容器/服务指定的。

上下文的lookup()方法返回了一个java.lang.Object的对象。代码必须把返回的对象转成所希望的类型。下面的代码是SortClient例子中的一段代码。Main()从使用JNDI服务开始,并用上下文的lookup()方法来定位home接口。将remote接口的名字(在这个例子中是sort)传递给context.loopup()方法。注重,程序最后将context.lookup()方法的结果传递给了SortHome,home接口的类型。

用JNDI定位home接口:

// SortClient java

import javax.naming.InitialContext;

import SortHome; // 引入bean的home接口

import Sort; // 引入bean的remote接口

public class SortClient {

……

public static void main(String[] args) throws Exception

javax.naming.Context context;

{ // 用命名服务获得JNDI上下文

context = new.javax.naming.InitialContext();

}

Object objref = context.lookup(sort);

SortHome home = (SortHome)javax.rmi.PortableRemoteObject.narrow(objref,

SortHome class);

Sort sort = home.create();

//做排序工作

sort remove();

}

maint()首先抛出一个普通的异常Excepion。当代码执行到这儿的时候,就算可能会导致程序的终止,客户也需要捕捉任何发生的异常。

获得remote接口:

现在我们获得了Enterprise JavaBean的home接口,然后,使用home接口的create()方法或者finder()方法就可以获得其remote接口了。实际使用哪个方法取决于Enterprise JavaBean的类型和Enterprise JavaBean的提供者在home接口定义的方法。

例如:上面的代码显示了客户SortClient如何获得Sort的remote接口的引用。一旦SortClient获得了home接口的引用并将之转化为适当的类型(这里是SortHome),客户就可以产生bean的实例,并调用它的方法。本例子中调用了home接口的create()方法,方法返回了remote接口的引用。(因为这个例子里面SortBean是一个无状态的会话bean,其home接口只有一个create()方法,这个方法没有任何参数)。然后,SortClient就能够调用定义在remote接口中的方法:sort()和merge()来进行排序工作。当排序工作完成的时候,客户调用remote接口的remove方法来删除Enterprise bean的实例。

会话Bean:

客户通过调用home接口中的Create()方法获得Enterprise JavaBean的remote接口的引用。

每一个会话Bean都必须至少有一个Create()方法。无状态的会话bean只能有一个Create()方法,并且这个Create()方法不能有参数。有状态的Session bean可以有一个不带参数的Create()方法,也可以有另外的带有不同参数的create()方法。Create()方法中的参数是用来初始化的会话bean的。

缺省的create()方法没有参数。例如,Sort例子使用了一个无状态的会话bean。根据定义,它有一个没有参数的create()方法。

Sort sort=home.create();

我们比较一下另一个例子:Cart例子,使用的是有状态的会话bean。在其home接口中实现了多个的create()方法。其中一个create()方法带了三个参数,三个参数用来标识cart的购买者,返回了Cart的home接口的引用。Cart客户先将三个参数的值给cartHoldName,creditCartNumber和eXPirationDate这三个变量,然后调用create()方法。其代码如下:

如何调用create()方法的代码:

Cart cart;

{

String cartHolderName=”Jack B.Quick”

String creditCartNumber = “345-346-32525252”

Date expirationDate = new GregorianCalendar(2001, Calendar JULY, 1) getTime();

Cart = home.create(cartHolderName,creditCartNumber,expirationDate);

}

另外,要注重的是,会话Bean没有finder方法。

实体 bean:

通过finder方法,或者通过create方法,客户得到实体对象的引用。我们在前面提到,实体对象代表了存储在数据库中的某些数据。既然Entity bean代表了一些持久的数据,那么通常实体bean存在相对长的一段时间,肯定比客户调用它的时间要长,这样,客户通常需要寻找一个代表了数据库中它感爱好的那一段数据的实体bean,而不是创建(create)一个实体bean。Create操作将会产生一个新的数据,并将新的数据存在底层的数据库中。

客户通过find操作来定位一个已经存在的实体bean。可以是相关数据库里面指定的行。也就是说,finder操作定位一个原来已经插入到数据库中的数据实体。这个数据也许已经被实体bean加入到数据库中,也许已经被加入到EJB上下文的外部(例如是数据库治理系统DBMS)。或者是在原有系统中,这数据的在安装EJB之前就已经存在。

客户使用create方法产生一个新的数据实体,并将数据实体存放在底层的数据库中。实体bean的create方法将实体数据插入到数据库中,用create方法的参数初始化实体的变量。实体bean的create方法总是返回一个remote接口。但是,对应的ejbcreate()方法是返回实体Bean实例的主键。

每一个实体bean都必须有一个主键值来唯一标识它。一个实体bean也可以有次键值,用来定位特定的实体对象。

Find方法和主键类:

一个实体bean的缺省的find方法是findByPrimaryKey()方法。它通过主键来定位实体bean。其语法如下:

<remote interface> findByPrimaryKey(<key type> primary key)

每一个实体bean必须实现findByPrimaryKey方法。PrimaryKey参数是一个独立的定义在配置描述器中的主键类。键的类型是主键,而且必须是RMI-IIOP合法的值。键类可以是任何Java类或者你自己写的类。

例如,你可能有一个名字为Acount的实体bean。已经将它的主键类定义为AccountPK。AccountPK是一个字符串类型。它拥有Account bean的标识。我们将account bean的标识设为AccountPK,然后调用findByPrimaryKey方法,就可以得到account bean实例的引用。如下代码所示:

Finding a Entity bean instance using the primary key

AccountPK accountPK =new AccountPK(“1234-56-789”);

Account source = accountHome.findByPrimaryKey(accountPK);

Bean的提供者可以定义另外的finder方法给客户使用。

Create()方法和remove()方法:

客户也能够用home接口中定义的create()方法来创建一个实体bean。当客户调用实体bean的create()方法的时候,一个新的实体bean的实例就存到数据仓库中。新实例总是有一个标识它的主键值。它的状态可以用create方法传递的参数值来初始化。

注重:实体bean存在的时间与它在数据库中代表的数据是一样的。其生命并不由与客户的会话来决定。通过调用实体bean的remove方法,可以将实体bean删除。Remove方法不仅将实体bean删除,也将它所代表的数据从数据库中删除。另外也可以直接删除实体bean。例如,可以通过用原来的应用程序或用DBMS删除数据库中的一条记录的方法来删除。

调用方法:

一旦客户获得了bean的remote接口的引用。就可以调用在remote接口中为enterprise bean定义的方法。属于此bean的应用逻辑的方法才是客户感爱好的。也有一些方法是用来获得enterprise bean的信息或者接口信息的。例如:获得bean对象的handle,检查一个bean是否与另一个bean相同,删除bean的方法。

下面的代码解释了客户如何调用enterprise bean中的方法。这个例子调用的是Cart 这个会话bean。代码从产生一个新的Cart bean实例并重新找到一个cart bean实例的remote接口的引用开始。从这里开始,客户就预备好可以调用bean的方法。

首先,客户创建了一个新的book对象,设定了其title 和price参数。然后,调用了enterprise bean的应用方法addItem()来将一个book对象加到shopping cart中去。AddItem()方法是在CartBean 这个会话bean中定义的,通过Cart的remote接口把它变成公共的方法。客户增加了一项其它的东西,然后用summairze()方法列出shopping cart里面的所有的Items。最后,就调用remove方法来删除bean的实例。注重,客户用调用其他的方法也是通过同样的途径,即使是自己定义的方法summarize()。

调用Bean的方法

………

Cart cart;

{

//获得bean的remote接口的实例。

Cart = home.create(cartHodlerName,creditCardNumber,expirationDate);

}

//创建一个新的book对象。

Book knuthBook = new Book(“The Art of Computer Programming”,49.95f);

//将新书加到Cart中。

Cart.addItem(knutBook);

………

//列举当前Cart中的书

summarize(cart);

cart.removeItem(knutBook);

……

删除bean的实例:

remove方法对会话bean的操作和对实体bean的操作是不同的。因为对于客户来说,会话对象的存在是不持久的。会话对象的客户在完成了会话之后需要调用remove方法。对客户来说,有两个remove方法可以调用:可以用javax.ejb.EJBObject.remove()方法来删除会话bean;也可以用javax.ejb.EJBHome.remove(Handle handle)方法来删除会话bean的handle。

一个好的编程风格是不需要由客户删除一个会话bean对象。假如客户没有删除一个有状态的会话bean,容器在过了一段时间(指定的超时值timeout value)之后会自动删除这个对象。Timeout value是一个配置属性。但是,客户能够为以后的引用保持这个对象的handle。

Entity bean的客户不需要考虑这个问题,因为实体bean只是在客户的事务期间。由容器负责它的生命周期,包括激活和钝化。只是当客户需要将实体对象从底层数据库中删除的时候,才调用remove方法。

使用bean的句柄:

句柄是用来引用enterprise bean的另一种方法。句柄相当于bean的一个较长的指针。你可以从remote接口获得句柄。一旦你拥有了句柄,就可以将它写入到文件或其它的持久存贮器里面。便于以后可以重新得到句柄,用它来重新建立enterprise bean的引用。

但是,你只能用remote接口的句柄来重新建立bean的引用。你不能用句柄来创建bean本身。假如另一个进程删除了bean,或者系统崩溃或关闭,删除了bean的实例,则当应用程序试图用句柄重新建立对bean的引用时会抛出异常。

当你不确定bean的实例是否依然存在的时候,你可以不用remote接口的句柄。而是保存bean的home接口的句柄,在以后要用的时候再通过调用create方法或finder方法重新创建bean对象。

在客户创建了bean的实例以后,就能够用getHandle()方法来获得实例的句柄。一旦拥有的句柄,就能够将它写到文件里面去。在以后的时间,客户可以读这个文件,将读出来的对象转化为句柄类型。然后,就可以在句柄上调用的getEJBObject方法来获得bean的引用。最后再将getEJBObject方法返回的值转化为合适的类型。

使用句柄来引用一个Bean

Import java.io;

Import javax.ejb.Handle;

……

Cart cart;

……

cart = home.create(cartHolderName,creditcartNumber,expirationDate);

//在cart对象上调用getHander方法来获得它的句柄。

CartHander=cart.getHandler();

//将hander写到文件中去。

FileOutputStream f = new FileOutputStream(“carthandle.ser”);

ObjectOutputStream o = new ObjectOutputStream(f);

o.writeObject(myHandle);

o.flush();

o.close();

……

//在以后的某个时间,可以从文件中读出handle

FileInputStream fi = new FileInputStream(“carthandle.ser”);

ObjectInputSteam oi = new ObjectInputStream(fi);

//从文件中读出对象并将它转化为Hander类型。

CartHanle = (Handle)oi.readObject();

Oi.close;

……

//使用handle来引用bean的实例

try{

Object ref = context.lookup(“cart”);

Cart cart1 = (Cart)javax.rmi.PortableRemoteObject.narrow(ref,Cart.class);

……

}catch(RemoteException e){

……

}

……

当用完会话bean的句柄以后,客户将使用javax.ejb.EJBHome.remove(Handle handle)方法来删除句柄。

二、治理事务:

客户程序能够治理它自己的事务,而不是让enterprise bean或者是容器来治理。客户治理自己事务的时,就好象会话bean治理它自己的事务一样。

当客户治理自己的事务的时候,需要自己来描述事务的分界线。这也就是说,它必须明确的开始一个事务和终止(提交或回滚)一个事务。

客户使用javax.transaction.UserTransaction接口来治理它自己的事务。它必须首先用JNDI来获得UserTransaction接口的引用。一旦有了UserTranscation的上下文,就可以用UserTransaction.begin()方法来开始一个事务(后面用UserTranscation.commit()方法提交或Usertransction.rollback()方法回滚这个事务)。这之间客户做有关的查询和更新操作。

如下代码演示了客户如何实现治理它自己的事务。客户治理的属于协议的那部分用粗体显示:

客户治理事务:

import javax.naming.initialContext;

import javax.transcation.UserTransaction;

……

public class clientTransaction{

public static void main(String[] argv){

UserTranscation ut=null;

InitialContext initContext = new InitialContext();

……

ut = (UserTransaction)initContext.lookup(“java:comp/UserTranscation”);

//开始一个事务

ut.begin();

//做事务工作。

……

ut.commit(); //or ut.rollback();

}

}

三、获得enterprise bean的信息:

enterprise bean的信息是一个指向metadata引用。客户使用enterprise bean的home接口的getMetaData()方法能够获得bean的metadata信息。

GetMetaData()方法经常被开发环境和build工具所使用,因为它们需要知道enterprise bean的相关信息,例如哪些链接到一起的bean已经被安装。脚本客户也需要获得bean的metadata信息。

一旦客户重新得到home接口的引用。就能够在home接口上调用getEJBMetaData()方法。然后,客户就可以通过调用EJBMetaData接口上的方法来取得如下信息。

用EJBMetaData.getEJBHome()方法获得bean的EJBHome接口。

用EJBMetaData.getHomeInterfaecClass()方法来获得home接口类的对象。包括其接口,类,域和方法。

用EJBMetaData.getRemoteInterfaceClass()方法来获得remote接口类的对象。包括所有的类信息。

用EJBMetaData.getPrimaryKeyClass()方法来获得bean的主键类对象。

不管是会话bean还是实体bean。可用EJBMetaData.isSession()方法来判定。

用EJBMetaData.isStatelessSession()来判定会话bean是否有状态还是无状态的。

四、JNDI的支持:

EJB规范定义了定位home接口的API。JNDI是在其它的服务(CORBA的命名服务,LDAP/X.500,flat files,目录服务)上面实现的。下图解释了不同的实现选择。典型的,EJB服务提供者选择一个特定的JNDI实现。

这个技术实现了JNDI底层的与客户无关性。客户只需要使用JNDI的API就可以了。

五、EJB到CORBA的映射:

EJB和CORBA之间有许多的联系。有三点很重要:

用ORB实现EJB容器/服务。

将原有系统放到EJB中间层的综合能力。

从非java组件的客户,访问EJB的能力。

EJB规范目前只涉及到这三个方面。

CORBA是实现EJB下层组织的很适合而且很自然的平台。CORBA提供了所有的EJB规范与CORBA核心规范或者与CORBA核心服务之间的相关的东西。

持分布式:CORBA核心和CORBA命名服务

支持事务:CORBA对象的事务服务

支持安全:CORBA的安全规范,包括IIOP-over-SSL

另外:CORBA答应非JAVA组件与应用程序的结合。这些组件可以是原来的系统或程序,将它们插入到不同的客户中去。后端系统能够很轻易的用OTS和任何能够与IDL映射的程序设计语言结合在一起。这需要容器提供OTS和IIOP的APIs。

EJB规范答应非JAVA客户访问enterprise bean,也提供了EJB到CORBA的映射。EJB/CORBA映射的目的是:

支持用任何CORBA支持的程序设计语言编写的客户端和基于CORBA的EJB服务上的enterprise bean的结合。

使客户程序在相同的事务处理中与CORBA对象或者enterprise bean相结合。

支持在涉及到不同代理商提供的多个客户端在基于CORBA的EJB服务器上运行时的分布式事务。

映射是基于java-to-IDL映射的。其规范包括了如下几个方面:分布式关系的映射,命名约定的映射,事务处理的映射,安全的映射。下面的部分,我们将解释每一个映射。因为映射用了新的IDL特性,这些特性在OMG的Object-by-Value规范中介绍。与其它语言的结合需要与CORBA2.3ORB规范兼容。

分布式的映射:

enterprise bean有两个可以远程访问的接口:remote接口和home接口。IDL规范中有这样的JAVA/IDL到这些接口的映射。EJB规范中定义的基类也是以同样的方式映射到IDL中的。

例如:让我们看看下面的IDL接口,这是ATM那个例子。是一个enterprise 会话bean。有一个方法在accounts之间转帐。也可以抛出资金不足的异常。通过JAVA/IDL映射其home接口和remote接口。我们得到如下的IDL接口:

Module transaction{

Module ejb{

Valuetype InsufficientFundsException: ::java::lang::Exception{};

Exception InsufficientFundsEx{

::transcation::ejb::InsufficientFundsException value;

};

interface Atm: ::javax::ejb::EJBObject{

void transfer(in string arg[], in string arg1, in float arg2)

raise(::transaction::ejb::InsufficientFundsEx);

};

interface AtmHome: ::javax.:ejb::EJBHome{

::transaction::ejb::Atm create{}

raise(::javax::ejb::CreateEx);

};

};

};

命名映射:

基于CORBA的EJB运行时环境假如要想让所有的CORBA客户都能够访问enterprise bean,就必须使用CORBA命名服务来发布enterprise bean的home接口。运行时能够直接使用CORBA的命名服务,或者通过JNDI及其到CORBA命名服务的标准映射来间接的使用。

JNDI命名用一个字符串表示成如下形式:“Directory1/directory2/…/directotyN/objectName”。CORBA的命名服务将名字定义成一系列的名字组件:

Typedef string Istring;

StrUCt NameComponet{

Istring id;

Istring kind;

};

typedef sequence <NameCompoent> Name;

每一个由”/”分开的JNDI字符串名字都被映射成一个名字组件。最左边的组件就是CORBA命名服务名字的第一个入口。

JNDI字符串名字与一些命名上下文有关。我们把这样的上下文叫做JNDI根部上下文。JNDI根部上下文相当于CORBA的命名服务的初始化上下文。CORBA命名服务的名字与CORBA的初始化上下文有关。

通过在ORB的虚拟对象上面调用resolv_initial_reference(“NameService”),CORBA应用程序能够得到初始的CORBA命名服务的上下文。CORBA命名服务并没有指定如何组织命名上下文,这样,根上下文的概念就不起作用了。ORB的初始化通过resolve_initial_reference()来决定上下文。

例如:一个C++的客户端能够定位我们的ATM会话bean的home接口。这个ATM bean已经用JNDI字符串“transaction/corbaEjb/atm”注册。首先,我们得到初始的命名上下文。

oject_ptr obj = orb->resolve_initial_references(“NameService”);

NameContext initialNameContext = NameingContext.narrow(obj);

if (initialNameingContext == NULL) {

cerr <<”Could not initial naming context” <<endl;

exit(1);

}

然后,我们产生了一个CORBA命名服务的名字并且根据上面所说的对它进行初始化。

Name name = new Name(1);

name[0].id=”atm”;

name[0].kind=””;

现在我们解决了初始化命名服务的名字问题。假定我们成功的进行了初始化,我们也有了enterprise bean名字域上的上下文。我们将CORBA对象范围缩小到所需要的类型,并且,确信这样的缩小是成功的。

Object_ptr obj=initialNamingContext->resolve(name);

AtmSessionHome_ptr atmSessionHome = AtmSessionHome.narrow(obj);

if (atmSessionHome == NULL){

cerr<<”Could not narrow to ATMSessionHome” <<endl;

exit(1);

}

事务处理的映射:

基于CORBA的enterprise bean运行时环境需要使CORBA的客户端参加到涉及enterprise bean的事务处理中来,就必须使用CORBA的OTS(Object Transaction Service)来进行事务控制。

当enterprise bean配置好之后,能够根据不同的事务服务方针进行安装。方针是定义在配置描述器中的。

事务性的enterprise bean有如下的定义规则:CORBA客户通过IDL接口产生的stub来调用enterprise bean的remote接口和home接口。假如涉及到事务处理,它使用CORBA OTS提供的接口。

例如:一个C++的客户能够调用ATM会话bean:

try{

//oBTain transaction current

Object_ptr obj = orb->resolve_initial_reference(“Current”);

Current current = Current.narrow(obj);

if(current==NULL){

cerr<<”Could not resolve current ”<<endl;

exit(1);

}

//execute transaction

try{

current->begin();

atmSession->transfer(“checking”,”saving”,100.00);

current->commit(0);

}catch(…){

current->rollback();

}

}catch(…){

……

}

安全的映射:

EJB规范的安全方面主要考虑的是控制enterprise bean的访问权限。CORBA规定了一些方法来定义标识符,包括如下的方面:

Plain IIOP(简单的IIOP方式):CORBA的主要接口在1998年早期并没有被接受。主要的接口用来确定客户的标识。但是,CORBA安全服务的作者却实现了不同的途径:GIOP。GIOP规范包括了一个叫服务上下文的组件,其实是一个元素为值对的数组。其标识符是CORBA的long型的,值是十进制的序列。服务上下文的入口可以用来标识调用者。

Secure IIOP(可靠的IIOP方式):CORBA的安全规范为身份定义了一个不透明的数据类型。身份的实际类型是由所选择的安全机制所决定的。例如:GSS Kerberos,SPKM,或者CSI-ECMA。

IIOP over SSL:SSL(加密套接字协议层)用X.509来来证实服务器或者是客户的身份。当服务器需要客户的身份验证的时候,服务器使用这个证书作为客户的身份验证。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有