2021SC@SDUSC
目录
之前的文章已经说明,针对openmeetings-db模块,我们首先着重分析的源码是src/main/java目录下的文件,并且针对pom.xml进行了详细解析,因此本文开始就对该目录下的java代码进行探究。
文件结构
打开src/main/java/org/apache/openmeetings目录,展现的内容如下:
有IApplication.java和IWebSession.java两个java文件,还有db文件夹。根据字面意义,这两个java文件描述的功能应该分别是应用和网络会话,db文件夹包含了openmeetings-db模块的大部分核心代码。
继续打开db目录,如下:
可以看到db目录下包含了5个文件夹。同样,根据字面意思,dao指的是数据访问对象(Data Access Object),dto指的是数据传输对象(Data Transfer Object),entity指的是实体,manager指的是管理者,util指的是工具。
打开dao目录,如下:
包含了两个java文件和若干文件夹。再依次打开dto、entity、manager、util目录,如下:
dto:
entity:
manager:
util:
可以看出,dao、dto、entity具有相似的目录结构,manager和util则具有较为区别的结构。
源码分析尝试
说明了源码的具体结构之后,就可以正式开始对源码的分析了。
IWebSession.java
本着由易到难的原则,首先来分析src/main/java/org/apache/openmeetings目录下的IWebSession.java文件。
打开IDEA,其源码如下:
package org.apache.openmeetings;
import java.util.Locale;
public interface IWebSession {
long getOmLanguage();
void setLanguage(long languageId);
Locale getLocale();
}
整个java文件的代码量比较小, 只声明了IWebSession接口。在接口中,提供了三个方法,等待实现这个接口的类去实现,分别是long getOmLanguage()、void setLanguage(long languageId)、Locale getLocale()。
三两个方法都比较容易理解,前两个分别是获取某个语言信息和根据语言的id设置语言,第三个方法则是获取类型为Locale的数据。这个类型在前面已经import了,因此进入查看Locale类的源码:
Locale类的源码量过大,因此只展示这一小部分。Locale类内部有静态Locale类型数据,如ENGLISH、FRENCH、GERMAN等,初步判断,Locale类与语言或者语言代表的地域有关。
进一步查阅资料得知,Locale类的作用是转换和划分地区的国际化类。Locale表示地区,每一个Locale对象都代表了一个特定的地理、政治和文化地区,在操作Date、Calendar等表示日期/时间的对象时经常会用到,因为在不同的区域里时间的表示方式不同。 (此处参考自文章https://m.jb51.net/article/85858.htm)对于Locale对象,提供了多种方法。例如,String getCountry()可以获得国家或地区的代码,String getLanguage()可以获得国家的语言等等。
弄清楚Locale的含义后,就可以解释在IWebSession.java中使用到它的理由。即,它可以确定用户所在的国家或地区及其信息。
IApplication.java
使用IDEA打开同样位于src/main/java/org/apache/openmeetings目录下的IApplication.java文件,其源码如下:
package org.apache.openmeetings;
import java.util.Locale;
import java.util.function.Supplier;
import javax.servlet.ServletContext;
import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
import org.apache.openmeetings.db.entity.room.Invitation;
import org.apache.openmeetings.util.ws.IClusterWsMessage;
import org.apache.wicket.request.IExceptionMapper;
import org.apache.wicket.request.IRequestMapper;
import org.apache.wicket.request.mapper.parameter.PageParameters;
public interface IApplication {
<T> T getOmBean(Class<T> clazz);
<T> T _getOmBean(Class<T> clazz);
ServletContext getServletContext();
IRequestMapper getRootRequestMapper();
Supplier<IExceptionMapper> getExceptionMapperProvider();
String getOmString(String key);
String getOmString(String key, long languageId);
String getOmString(String key, final Locale loc, String... params);
String getOmContactsLink();
String getOmInvitationLink(Invitation i);
String urlForActivatePage(PageParameters pp);
void setXFrameOptions(String xFrameOptions);
void setContentSecurityPolicy(String contentSecurityPolicy);
String getServerId();
//JPA
void updateJpaAddresses(ConfigurationDao dao);
//WS
void publishWsTopic(IClusterWsMessage msg);
}
与IWebSession一样,它声明了一个接口。但显然,它import了db/dao目录和db/entity目录下的类。如果不阐明它们的作用,也无法说明IApplication接口的作用。因此此处不得不暂停,先完成分析dao目录和entity目录下的源码,最后回过头来看IApplication.java。但经过观察又发现,db/util目录下的类作为工具常被引用,因此似乎先分析db/util是个更好的选择。
db/util目录
前文提到,util应该是作为工具存在的,其结构如下:
在db/util目录下,存在许多“Helper”、“Util”等java文件。打开ws文件夹,可以看到:
ws文件夹下存在两个java文件,分别是RoomMessage.java和TextRoomMessage.java,现在从它们两个入手开始展开分析。
RoomMessage.java
顾名思义,RoomMessage.java的字面意义是房间信息。我想,在类里面保存的应该正式房间的主要信息。其源码如下:
package org.apache.openmeetings.db.util.ws;
import static org.apache.openmeetings.db.dao.room.SipDao.SIP_FIRST_NAME;
import static org.apache.openmeetings.util.OmFileHelper.SIP_USER_ID;
import java.util.Date;
import java.util.UUID;
import org.apache.openmeetings.db.entity.basic.IClient;
import org.apache.openmeetings.db.entity.user.User;
import org.apache.wicket.protocol.ws.api.message.IWebSocketPushMessage;
public class RoomMessage implements IWebSocketPushMessage {
private static final long serialVersionUID = 1L;
public enum Type {
roomEnter
, roomExit
, roomClosed
, pollCreated
, pollUpdated
, recordingStarted
, recordingStoped
, sharingStarted
, sharingStoped
, rightUpdated
, activityRemove
, requestRightModerator
, requestRightPresenter
, requestRightWb
, requestRightShare
, requestRightRemote
, requestRightA
, requestRightAv
, requestRightExclusive
, haveQuestion
, kick
, newStream
, closeStream
, mute
, exclusive
, quickPollUpdated
}
private final Date timestamp;
private final String uid;
private final Long roomId;
private final Long userId;
private final String name;
private final Type type;
public RoomMessage(Long roomId, IClient c, Type type) {
this(roomId, c.getUserId(), c.getFirstname(), c.getLastname(), type);
}
public RoomMessage(Long roomId, User u, Type type) {
this(roomId, u.getId(), u.getFirstname(), u.getLastname(), type);
}
private RoomMessage(Long roomId, Long userId, String firstName, String lastName, Type type) {
this.timestamp = new Date();
this.roomId = roomId;
if (SIP_USER_ID.equals(userId)) {
this.name = SIP_FIRST_NAME;
} else {
name = String.format("%s %s", firstName, lastName);
}
this.userId = userId;
this.type = type;
this.uid = UUID.randomUUID().toString();
}
public Date getTimestamp() {
return timestamp;
}
public Long getRoomId() {
return roomId;
}
public Long getUserId() {
return userId;
}
public String getName() {
return name;
}
public Type getType() {
return type;
}
public String getUid() {
return uid;
}
}
前面import的类中也包含了dao目录和entity目录下的类,先不去处理它们,观察RoomMessage类的结构。
首先,RoomMessage类实现了IWebSocketPushMessage接口,所以先查看这个接口的信息:
package org.apache.wicket.protocol.ws.api.message;
public interface IWebSocketPushMessage extends IWebSocketMessage {
}
IWebSocketPushMessage接口继承自IWebSocketMessage接口,再如此查看它的信息,发现IWebSocketMessage接口继承自IClusterable接口,IClusterable接口继承自Serializable接口。在继承的过程中,每个接口没有提供方法,可以粗略认为IWebSocketPushMessage继承自Serializable接口。也就是说,RoomMessage实现了Serializable接口。
Serializable接口的源码如下:
package java.io;
public interface Serializable {
}
查阅资料(参考自Java Serializable 接口 - 费强胜 - 博客园) 得知,Serializable是java.io包中定义的、用于实现java类的序列化操作而提供的语义级别的接口。Serializable序列化接口没有任何方法或者字段,只是用于标识可序列化的语义。实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象。例如,我们可以将序列化对象写入文件后,再次从文件中读取它并反序列化成对象。简单说就是为了保存在内存中的各种对象的状态,并且可以把保存的对象状态再读出来。
既然RoomMessage类实现了Serializable接口,那么它的状态自然可以被保存到内存某处,然后在适当时机重新获取。
接下来看到,RoomMessage类包含的第一个字段是private static final long serialVersionUID = 1L。private意味着它是私有数据,static意味着它是静态数据,final意味着它是不可再次被修改的,类型为long。serialVersionUID的意思应该是序列版本号,它应该是作为一个固定的版本号存在,具体应用还需要在实例中体现。
然后,是一个枚举enum类型,其组成如下:
public enum Type {
roomEnter
, roomExit
, roomClosed
, pollCreated
, pollUpdated
, recordingStarted
, recordingStoped
, sharingStarted
, sharingStoped
, rightUpdated
, activityRemove
, requestRightModerator
, requestRightPresenter
, requestRightWb
, requestRightShare
, requestRightRemote
, requestRightA
, requestRightAv
, requestRightExclusive
, haveQuestion
, kick
, newStream
, closeStream
, mute
, exclusive
, quickPollUpdated
}
根据枚举的定义,它表示的是一组常量。个人理解,它起到了类似class的作用,可以声明一个类型(在这里就声明了Type类型),声明的类型可以去定义对象执行某些操作。因此,在这里的Type,表示的是房间的某种状态或类型,这可以根据它包含的常量名称(如roomEnter等)确认。
再往下,就是一些私有的final变量,但尚未赋值,包含timestamp(时间戳)、uid(识别码)、roomId、userId、name,以及Type类型的type:
private final Date timestamp;
private final String uid;
private final Long roomId;
private final Long userId;
private final String name;
private final Type type;
这些字段应该是在房间创建时根据构造器确定的,且确定后便不能更改,因此这里还没有赋值。
然后便出现了RoomMessage类的几种构造器了:
public RoomMessage(Long roomId, IClient c, Type type) {
this(roomId, c.getUserId(), c.getFirstname(), c.getLastname(), type);
}
public RoomMessage(Long roomId, User u, Type type) {
this(roomId, u.getId(), u.getFirstname(), u.getLastname(), type);
}
private RoomMessage(Long roomId, Long userId, String firstName, String lastName, Type type) {
this.timestamp = new Date();
this.roomId = roomId;
if (SIP_USER_ID.equals(userId)) {
this.name = SIP_FIRST_NAME;
} else {
name = String.format("%s %s", firstName, lastName);
}
this.userId = userId;
this.type = type;
this.uid = UUID.randomUUID().toString();
}
共有三个构造器,前两个是public的,供其他代码调用,而第三个是private的,在前两个构造器中调用。
首先看第三个构造器。传入的参数比较多,包括了roomId、userId、firstName、lastName、type,分别给对应字段赋值,timestamp赋值为实时生成的Date对象,uid由UUID类调用randomUUID方法随机生成。其中,在给name赋值时,对SIP_USER_ID与传入的userId进行了比对,但此时还不清楚SIP_USER_ID的具体作用,所以暂时留个坑以后来填。
这里需要插一些关于UUID的内容,参考自百度百科。
UUID是通用唯一识别码,其目的是让分布式系统中的所有元素,都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。如此一来,每个人都可以创建不与他人冲突的UUID。UUID类中的randomUUID方法,可以随机获取一个UUID,重复的概率极小。
所以,在RoomMessage中加入uid字段的目的是,使每个房间都由唯一的UUID来标识。
前两个构造器的参数有些差别,除了传入roomId和type外,第一个构造器传入的是IClient对象,第二个传入的是User对象,并通过它们获取用户的name。同样,它们是来自entity目录下的类,也暂时留个坑。
余下的部分就是普通的get方法了,没有什么好说的。至此,RoomMessage类的源码分析完毕。
TextRoomMessage.java
由于代码量不多,先贴上源码:
package org.apache.openmeetings.db.util.ws;
import org.apache.openmeetings.db.entity.basic.IClient;
import org.apache.openmeetings.db.entity.user.User;
public class TextRoomMessage extends RoomMessage {
private static final long serialVersionUID = 1L;
private final String text;
public TextRoomMessage(Long roomId, IClient client, Type type, String text) {
super(roomId, client, type);
this.text = text;
}
public TextRoomMessage(Long roomId, User u, Type type, String text) {
super(roomId, u, type);
this.text = text;
}
public String getText() {
return text;
}
}
可以清晰地看出,TextRoomMessage类是RoomMessage类的子类!!!这意味着,它只是对RoomMessage进行了一定程度上的扩展。果然,它只是增加了一个text字段,其余与RoomMessage相同。对于增加这个text的作用,初步猜测是对房间进行某些文字描述。
至此,TextRoomMessage.java分析完毕。
总结
本文对openmeetings-db模块中src/main/java/org/apache/openmeetings目录下的java文件做了结构梳理,并以IWebSession.java为开端,展开对源码的分析,已经完成了IWebSession.java以及util/ws目录下文件的分析。接下来,将尽量迅速地完成util目录的分析,然后选择其他目录继续依次探究源码。