小说阅读器

小说阅读器

1.需求分析

  1. 注册
  2. 登陆
  3. 查看小说分类:言情、悬疑、仙侠、科幻、玄幻……
  4. 根据小说分类查看小说列表
  5. 阅读小说:简单模式 只查看某个小说的前100字左右内容
  6. 下载阅读:
  7. 上传小说
  8. 退出

2.架构设计

C(Client客户端)/S(Server服务端)架构

客户端:Socket相关API

服务端:ServerSocket Thread

数据库:XML DOM4J

3.难点分析

如何让服务端区分不同的请求

Data Transfer Object 它一般用于后端->前端的交互使用

VO -> View Object 它一般用于前端视图层->后端的交互使用

POJO -> Plain Old Java Object 主要用于后期ORM框架映射使用

创建DTO作为数据交换对象

上传需要客户端再开启线程

下载需要服务器在开启线程

这两者socket、输入输出不能太早关闭

上传的文件要添加到对应小说分类和小说XML中,并更新MAP集合

如何讲服务处理进行拆分

我们定义了一系列的服务类,不同的服务类专门用于处理不同的请求。

LoginService

RegisterService

GetNovelClassesService

GetNovelsService

DownloadService

UploadService

在抽取一个接口Service

让服务继承

简单工厂模式

场景:根据客户的描述 产生符合要求的实例。
1.产品规范 接口

Service

2.符合规范的产品 接口的实现类

LoginService

RegisterService

GetNovelClassesService

GetNovelsService

DownloadService

UploadService

3.生产产品的工厂

ServiceFactory
4.客户

main

反射

字节码类型 Class
3种获取方法
1.类名.class
2.对象名.getClass()
3.Class.forName(全类名)

可以获取一个类的所有构造、方法、属性、注解…
也可以对这些构造…进行使用

/**
 * 服务端工厂
 * 简单工厂模式:3.生产产品的工厂类
 * @author 周太阳
 * 2019年5月13日
 */
public class ServiceFactory {
	
	/**存储产品的列表*/
	private static HashMap<String,String> map = new HashMap<String,String>();
	
	/**
	 * 初始化工厂
	 */
	static {
		// 创建SAXReader读取XML中的DOM树
		SAXReader reader = new SAXReader();
		try {
			Document document = reader.read(InitProperties.getPropertyValue("sunshine.server.config.service"));
			// 获得根元素并遍历元素
			Element rootElement = document.getRootElement();
			List<Element> elements = rootElement.elements();
			for (Element element : elements) {
				// 将对应的元素的属性放入map集合中
				map.put(element.attributeValue("id"), element.attributeValue("class"));
			}
		} catch (DocumentException e) {
			e.printStackTrace();
		}
	}
	/**
	 * @return 用反射返回对应的服务的对象
	 * @throws Exception 
	 */
	public static Service getService(String service) throws Exception {
		try {
			// 得到需要服务的Class地址
			String className = map.get(service);
			// 判断Class地址是否为空
			if (className == null) {
				// 自定义抛出
				throw new ClassNotFoundException();
			}else {
				// 利用反射得到对应的对象
				Class<?> clazz = Class.forName(className);
				// 返回对象
				return (Service)clazz.newInstance();
			}
		} catch (ClassNotFoundException e) {
			throw new NoSuchServiceException("该功能尚未研发成功,敬请期待!");
		}
	}
}

利用自定义的XML存储一些类信息

借鉴于未来一些框架思想

如小说分类

<?xml version="1.0" encoding="UTF-8"?>
<!-- 服务器小说分类 -->
<novelClasses>
	<novelClass>
		<classname>武侠</classname>
		<catalog>txtcatalog/wuxia/</catalog>
		<config>resource/novel/txt_wuxia.xml</config>
	</novelClass>
	<novelClass>
		<classname>言情</classname>
		<catalog>txtcatalog/yanqing/</catalog>
		<config>resource/novel/txt_yanqing.xml</config>
	</novelClass>
</novelClasses>

小说

<?xml version="1.0" encoding="UTF-8"?>
<!-- 武侠小说 -->
<novellist> 
	<novel> 
		<name>古侠今遇</name>  
		<author>醉人岁月</author>  
		<description>与世隔绝三百多年的“碧湖山庄”四少侠出现在大都市中,既有古人的风采,又有现代人的韵味!</description>  
		<filename>古侠今遇.txt</filename> 
	</novel>  
	<novel> 
		<name>寒剑孤灯</name>  
		<author>忧郁丁香</author>  
		<description>寒剑孤灯,传奇故事</description>  
		<filename>寒剑孤灯.txt</filename> 
	</novel>  
</novellist>

5.使用BasiceService去实现了接口中的方法

接口实现类都需要重复实现某些方法。

继承:减少重复代码 ,提升代码复用性。

package org.sunshine.server.service;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import org.sunshine.server.utility.DTO;

/**
 * 服务器基本方法
 * 实现Service接口
 * 提取输入输出流的开启与关闭
 * 和继承service方法
 * @author 周太阳
 * 2019年5月15日
 */
public abstract class BasicService implements Service{
	/**创建接收套接字的属性*/
	private Socket socket;
	private ObjectInputStream ois;
	private ObjectOutputStream oos;
	/**创建保存传输的data对象*/
	private Object data;
	private int receive;

	/**
	 * 初始化输入输出
	 * @param socket 
	 */
	public void init(Socket socket, ObjectInputStream ois, ObjectOutputStream oos, Object data) throws Exception {
		// 得到当前对象的socket
		this.socket = socket;
		this.ois = ois;
		this.oos = oos;
		// 返回数据传输对象DTO
		this.data = data;
		this.receive = receive;
	}
	/**
	 * Service方法
	 * @throws Exception 
	 */
	@Override
	public abstract void service() throws Exception;
	
	/**
	 * 释放资源
	 */
	@Override
	public void destroy() throws Exception {
		ois.close();
		oos.close();
		socket.close();
	}
	
	public Socket getSocket() {
		return socket;
	}
	public void setSocket(Socket socket) {
		this.socket = socket;
	}
	public ObjectInputStream getOis() {
		return ois;
	}
	public void setOis(ObjectInputStream ois) {
		this.ois = ois;
	}
	public ObjectOutputStream getOos() {
		return oos;
	}
	public void setOos(ObjectOutputStream oos) {
		this.oos = oos;
	}
	public Object getData() {
		return data;
	}
	public void setData(Object data) {
		this.data = data;
	}
	public int getReceive() {
		return receive;
	}
	public void setReceive(int receive) {
		this.receive = receive;
	}
	
}

DAO专门用于存放数据操作代码

JAVA三层架构:表现层 业务逻辑层 数据访问层

Data Access Object 数据存取对象

常用基本取名代码:
举例:对用户的操作
User getUserById(Long id);
User getUserByUserName(String name);
List getUserListByConditions(Map<String,Object> conditions);
void addUser(User user);
void deleteUser(User user);
void updateUser(User user);

/**
 * 通信工具类
 * @author 周太阳
 * 2019年5月16日
 */
public class Client2Service {
	private ObjectInputStream ois;
	private ObjectOutputStream oos;
	private Socket socket;
	
	
	public Client2Service(String host, int port) {
		super();
		try {
			socket = new Socket(host, port);
			oos = new ObjectOutputStream(socket.getOutputStream());
			ois = new ObjectInputStream(socket.getInputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * 释放资源
	 * @throws IOException 
	 */
	public void destroy() throws IOException {
		oos.close();
		ois.close();
		socket.close();
	}
	/**
	 * 发送请求
	 * @throws IOException 
	 */
	public DTO requestService(DTO dto) throws Exception {
		oos.writeObject(dto);
		DTO response = (DTO)ois.readObject();
        // 后期操作关流可以不写在这里
		destroy();
		return response;
	}
}

客户端的"永动机"

执行某个服务的方法的时候,这个服务的返回值是一个服务,然后通过循环继续执行。

public class ClientMain {
	
	private static Service service = ServiceFactory.getService(ServiceConstants.START_SERVICE);

	public static void main(String[] args) {
		while(true) {
			if (service == null) {
				System.out.println("程序结束!");
				break;
			}
			try {
				service = service.service();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

}

服务端和客户端公用一些代码 common 开发阶段采用工程型依赖

如user,constructs

/**
 * 用户类常量
 * @author 周太阳
 * 2019年5月19日
 */
public class UserConstants {
	/**用户登录成功*/
	public static final int USER_LOGIN_SUCCESS = 1;
	/**登录失败*/
	public static final int USER_LOGIN_FAILED = 2;
	/**登录发生错误*/
	public static final int USER_LOGIN_ERROR = 3;
	/**用户注册名存在*/
	public static final int USER_REGISTER_ALREADY_EXISTS = 4;
	/**用户注册发生错误*/
	public static final int USER_REGISTER_ERROR = 5;
	/**用户注册成功*/
	public static final int USER_REGISTER_SUCCES = 6;
	
}

常量解决"魔法值"

提升系统中的代码阅读性和维护性。
发现有很多值使用比较频繁,又不好记,可能后期要修改可以定义为常量

服务常量

反射类的服务常量

字符串常量等……

/**
 * 反射类的服务常量
 * @author 周太阳
 * 2019年5月19日
 */
public class ServiceConstants {
	/**开始*/
	public static final String START_SERVICE = "start";
	/**登录*/
	public static final String LOGIN_SERVICE = "login";
	/**注册*/
	public static final String REGISTER_SERVICE  = "register";
	/**获得小说分类*/
	public static final String GET_NOVEL_CLASS_SERVICE  = "getNovelClasses";
	/**获得小说*/
	public static final String GET_NOVEL_SERVICE  = "getNovels";
	/**获得小说预览*/
	public static final String GET_NOVEL_PREVIEW_SERVICE  = "getPreview";
	/**下载*/
	public static final String DOWNLOAD_SERVICE  = "downLoad";
	/**上传*/
	public static final String UPLOAD_SERVICE  = "upLoad";
}

properties配置文件和Properties类

Properties是继承HashTable的,里面内容=左边是键,=右边是值

可以将其用到反射上,配合永动机更方便。

#服务器配置
#服务器端口号
sunshine.socket.server.port=8080
#获取服务器配置文件
sunshine.server.config.service=resource/Service.xml
#用户配置文件地址
sunshine.server.config.user=resource/user/UserInfo.xml
#小说分类配置文件地址
sunshine.server.config.novel.class=resource/novel/NovelClass.xml

将配置信息存放在配置文件中(XML不推荐,因为XML需要DOM解析,使用比较复杂)

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值