一、原生MVC服务框架
1、组件化重构
(1)分而治之
将大问题分解成小问题
- 视图View:用户界面的呈现方式,通常由HTML、CSS和JavaScript等前端技术组成,用于展示数据和与用户进行交互。
- 视图解析器ViewResolver:一个用于查找并解析视图的组件。将视图查找和渲染算法抽取这个类中更有利于软件维护。
- 控制器 Controller: MVC 设计模式中的一个组件,它负责接收客户端发来的请求,并根据请求中的参数或路径,决定调用哪个业务逻辑处理程序或模型进行处理,然后将处理结果返回给客户端。
- 控制器映射 HandlerMapping: MVC中的一个关键组件,它的作用是将请求映射到相应的处理器(即控制器)上。
2、视图和视图解析器
动态化的网页写在java中不方便,应该将html进行模版化,将可能会变化的数据用${}括起来
/**
* @document: 视图解析器,把Thymeleaf模版文件转换为HTML文件
* @Author:SmallG
* @CreateTime:2023/8/18+15:37
*/
public class ViewResolver {
private static ViewResolver instance = new ViewResolver();
private TemplateEngine templateEngine;
private ViewResolver() {
//初始化模版引擎
templateEngine = new TemplateEngine();
//基于Thymeleaf的类加载器
ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
//加载模版文件目录
resolver.setPrefix("/template/");
templateEngine.setTemplateResolver(resolver);
}
public static ViewResolver getInstance() {
return instance;
}
/**
* 定义一个渲染模版的方法
*
* @param view 视图模版文件
* @param model 数据集合
*/
public String render(String view, Map<String, Object> model) {
Context context = new Context();
//替换数据
context.setVariables(model);
return templateEngine.process(view, context);
}
}
3、控制器
Web应用程序的核心组件,它负责接收并处理客户端请求,根据请求的URI来调用相应的业务逻辑,并将处理结果封装为数据模型。控制器的主要职责是将请求和业务逻辑分离,使得代码更加清晰和易于维护。
使用注解和反射实现控请求路由
引入两个自定义的注解:@Controller 和 @RequestMapping。
- @Controller 注解用于标记控制器类,表示该类是一个控制器,负责处理请求和业务逻辑。
- @RequestMapping 注解用于标记控制器类中的方法,指明处理的请求路径。
- 在控制器类中,我们可以使用 @Controller 注解标记类,并在需要处理请求的方法上使用 @RequestMapping 注解指明对应的请求路径。
- 在 DispatcherServlet 中利用反射API实现路由功能。
/**
* @document:
* @Author:SmallG
* @CreateTime:2023/8/18+17:02
*/
@Controller //声明这是一个控制器类
public class AccountController {
/**
* 注册
*
* @param request 请求
* @param response 响应
* @param model 请求参数
* @return 模版页面
*/
@RequestMapping("/accounts/register") //声明请求地址访问的方法
public String register(HttpServletRequest request,
HttpServletResponse response,
Map<String, Object> model) {
//获取注册数据
String username = request.getParameter("username");
String gender = request.getParameter("gender");
String email = request.getParameter("email");
String hometown = request.getParameter("hometown");
//把注册数据存入model对象中
model.put("username", username);
model.put("gender", gender);
model.put("email", email);
model.put("hometown", hometown);
return "templateRegister.html";
}
/**
* 登录
*
* @param request 请求
* @param response 响应
* @param model 请求参数
* @return 模版页面
*/
@RequestMapping("/accounts/login") //声明请求地址访问的方法
public String login(HttpServletRequest request,
HttpServletResponse response,
Map<String, Object> model) {
//获取登录数据
String username = request.getParameter("username");
String password = request.getParameter("password");
//把注册数据存入model对象中
model.put("username", username);
model.put("password", password);
return "template.html";
}
}
4、映射处理器
抽取HandlerMapping
/**
* @document: 根据用户请求的uri,查找匹配的处理器
* 找到那些类加了@Controller注解和那些方法加了@RequestMapping注解
* @Author:SmallG
* @CreateTime:2023/8/19+14:09
*/
public class HandlerMapping {
private static HandlerMapping instance = new HandlerMapping();
//保存uri所对应的方法 保存加了@RequestMapping注解的方法
private Map<String, Method> handlers = new HashMap<>();
//私有化构造方法
private HandlerMapping() {
//加载所有的控制器类(具有注解@Controller的类)
try {
loadHandlers();
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//提供获取当前类的对象的方法
public static HandlerMapping getInstance() {
return instance;
}
//根据用户请求的uri,获取需要调用的方法
public Method getHandlerMethod(String uri){
return handlers.get(uri);
}
//加载所有的加了@RequestMapping注解的方法
private void loadHandlers() throws URISyntaxException, ClassNotFoundException {
//找到控制器所在的包
File dir = new File(HandlerMapping.class
.getClassLoader().getResource("cn/highedu/server01").toURI());
//加载这个包里的所有的class文件
File[] files = dir.listFiles(file -> file.getName().endsWith(".class"));
//遍历数组逐个查看class文件 是否包含@Controller注解
for (File file : files) {
System.out.println("找到class文件:" + file);
//获取文件名(*.class)
String fileName = file.getName();
//获取类名
String className = fileName.substring(0, fileName.lastIndexOf("."));
//获取类的对象
Class cla = Class.forName("cn.highedu.server01." + className);
//判断这个类是否有controller注解
if (cla.isAnnotationPresent(Controller.class)) {
System.out.println("找到标注了Controller注解的类" + cla);
//获取这个控制器类里所有的方法
Method[] methods = cla.getDeclaredMethods();
//循环遍历查看是否有@RequstMapping注解
for (Method method : methods) {
if (method.isAnnotationPresent(RequestMapping.class)) {
System.out.println("找到了标注@RequstMapping注解的方法:" + method);
//获取注解的value值
//先获取注解
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
String value = requestMapping.value();
//把uri和方法保存到handlers集合中
handlers.put(value, method);
}
}
}
}
}
}
二、线程池和性能优化
1、线程池
**线程池有5个重要的参数,包括:
- 核心线程数(Core Pool Size):线程池中最小的线程数量。即使线程池中没有任务要执行,核心线程也会一直存在,不会被销毁。
- 最大线程数(Maximum Pool Size):线程池中最大的线程数量。当任务数量超过核心线程数时,线程池会创建新的线程,直到达到最大线程数。
- 任务队列(Work Queue):用于保存待执行的任务的队列。当线程池中的线程都在执行任务且任务数量达到最大线程数时,新提交的任务会被放入任务队列中等待执行。
- 空闲线程存活时间(Keep-Alive Time):当线程池中的线程数量超过核心线程数,且空闲时间达到指定时间时,多余的线程会被销毁,保持线程池的大小不超过核心线程数。
- 拒绝策略(Rejected Execution Handler)。拒绝策略定义了当线程池已满并且任务队列也已满时,如何处理新提交的任务。
自定义参数创建Java线程池
我们创建一个自定义线程池CustomThreadPool,使用ThreadPoolExecutor类来实现。在这里,我们设置了以下参数:
- corePoolSize:将核心线程数设置为5,表示线程池中始终保持5个线程在运行。这些线程会一直存在,即使它们处于空闲状态,以便快速响应新任务的到来。
- maximumPoolSize:将最大线程数设置为10,表示线程池允许创建的最大线程数为10。当任务数量超过核心线程数,并且任务队列已满时,线程池将创建新的线程,但不会超过最大线程数。
- keepAliveTime:将线程空闲时间设置为5000毫秒,表示如果线程在空闲时间内没有任务可执行,那么它将被终止以释放资源。
- queueCapacity:将任务队列容量设置为20,表示线程池可以同时接受的最大任务数为20。当任务数量超过核心线程数时,新的任务会被放入任务队列中等待执行。
- ThreadFactory:使用ThreadFactoryBuilder创建线程工厂,并设置线程名称格式为"CustomThreadPool-%d",方便我们在日志中识别不同的线程。
- RejectedExecutionHandler:使用AbortPolicy拒绝策略,表示当任务队列和线程池都已满时,直接抛出异常。这样可以避免任务被丢弃,而是让调用者进行适当的处理。
/**
* @document:
* @Author:SmallG
* @CreateTime:2023/8/19+15:36
*/
public class CustomerThreadPool {
public static void main(String[] args) {
// 定义线程池参数
int corePoolSize = 5; //线程的核心线程数
int maximumPoolSize = 10; //线程池的最大线程数
long keepAliveTime = 5000; //线程最大空闲时间
int queueCapacity = 20; //队列容量
// 创建自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS,
//任务队列,使用LinkedBlockingDeque,线程池的最大容量
new LinkedBlockingDeque<>(queueCapacity),
//线程工厂 使用ThreadFactoryBuilder 线程的名字CustomerThreadPool-%d
new ThreadFactoryBuilder().setNameFormat("CustomerThreadPool-%d").build(),
//拒绝策略 如果线程池不够了就使用AbortPolicy直接抛出异常
new ThreadPoolExecutor.AbortPolicy()
);
for (int i = 0; i < 30; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "在运行" + taskId);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
//任务结束,关闭线程池
executor.shutdown();
}
}
2、性能优化
/**
* @document: 接收客户端的HTTP请求
* @Author:SmallG
* @CreateTime:2023/8/16+10:43
*/
public class ServerBootApplication {
private ServerSocket serverSocket;
private ThreadPoolExecutor executor; //定义线程池
/**
* 初始化线程池方法
*/
private void initThreadPool() {
//定义线程池参数
int corePoolSize = 10; //核心线程数
int maximumPoolSize = 50; //最大线程数
long keepAliveTime = 5000; //非核心线程最大等待时间,超时则销毁
int queueCapacity = 20; //等待队列的大小
//创建线程池
executor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<>(queueCapacity),
new ThreadFactoryBuilder().setNameFormat("WebServer-%d").build(),
new ThreadPoolExecutor.AbortPolicy() //拒绝策略 抛异常
);
}
public void start() {
try {
//先初始化线程池
initThreadPool();
// 创建ServerSocket对象,监听8088端口
serverSocket = new ServerSocket(8088);
while (true) {
System.out.println("等待客户端连接...");
// 等待客户端连接
Socket socket = serverSocket.accept();
//创建线程对象
ClientHandler handler = new ClientHandler(socket);
//提交任务
executor.submit(handler);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ServerBootApplication application = new ServerBootApplication();
//启动服务器
application.start();
}
}
三、校园管理系统
到此我们已经基本打造了原生Web服务框架,这是一个基于 Java 的Web框架,它使用模型-视图-控制器(MVC)的架构模式,可以帮助我们更好地组织和管理Web应用程序的代码。
原生Web框架的完整请求处理流程如下:
1、客户端向服务器发送一个HTTP请求,这个请求首先被ServerSocket接收到,并且创建了对应线程任务ClientHandler,然后将线程任务提交给线程池执行。
2、线程负责处理HTTP请求及响应:首先创建HttpServletRequest解析请求消息,然后创建了HttpServletResponse缓存响应信息,创建负责请求流程的DispatcherServlet对象,执行其service方法处理请求,最后HttpServletResponse对象负责发送响应消息。
3、通过 DispatcherServlet对请求进行详细处理:DispatcherServlet 是原生Web框架的核心组件,它作为中央调度器协调所有的请求处理过程。
四、练习
1 编写视图解析器(View Resolver)
视图解析器(View Resolver)是一个用于查找并解析视图的组件,编写要求如下:
1、视图解析器采用单例模式;
2、视图解析器中的 render() 方法,根据传入的视图名和模型数据,读取对应的模板文件,进行替换并渲染出 HTML 代码,然后返回渲染后的 HTML;
3、视图解析器提供一个 main() 方法用于测试视图解析器的功能,传入一些模型数据,输出渲染后的 HTML 代码。
提示:
编写被解析的视图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>地址信息</title>
</head>
<body>
${user}的收货地址是${address}
</body>
</html>
添加视图解析器和测试案例
/**
* @document: 视图解析器
* @Author:SmallG
* @CreateTime:2023/8/21+8:27
*/
public class ViewResolver {
//单例模式中的饿汉式
private static ViewResolver instance = new ViewResolver();
private ViewResolver() {
}
public static ViewResolver getInstance() {
return instance;
}
private static File root;
private static File templateDir; //路径
static {
try {
//类加载路径:target/classes
root = new File(
ViewResolver.class.getClassLoader().getResource(".").toURI());
templateDir = new File(root, "templates");
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
public String render(String view, Map<String, Object> model) throws IOException {
File file = new File(templateDir, view);
byte[] bytes = new byte[(int) file.length()];
FileInputStream in = new FileInputStream(file);
in.read(bytes);
in.close(); //关闭输入流
String html = new String(bytes, StandardCharsets.UTF_8);
for (Map.Entry<String, Object> entry : model.entrySet()) {
String key = entry.getKey();
//避免空指针异常
String value = entry.getValue() + "";
//正则表达式
String tag = "\\$\\{" + key + "\\}";
//替换模版
html = html.replaceAll(tag, value);
}
return html;
}
/**
* 模版渲染测试方法
*/
public static void main(String[] args) throws IOException {
HashMap<String, Object> model = new HashMap<>();
model.put("user", "SmallG");
model.put("address", "河北");
String html = getInstance().render("address.html", model);
System.out.println(html);
}
}
2 编写HandlerMapping类
HandlerMapping是MVC中的一个关键组件,它的作用是将请求映射到相应的处理器(即控制器)上。编写要求:
1、定义HandlerMapping的单例类;
2、加载所有 cn.highedu.boot.controller 包下的类,并在这些类中查找 @Controller 注解,以及这些类中的 @RequestMapping 注解的方法,并将它们添加到 HashMap<String, Method> handlers 中;
3、getHandlerMethod 方法接受一个 URI,返回与之匹配的处理方法。如果找不到匹配的处理器,则会返回一个null。
编写注解:
/**
* @document: 用于注解控制器
* @Author:SmallG
* @CreateTime:2023/8/21+18:04
*/
@Target(ElementType.TYPE) //用于注解在类上
@Retention(RetentionPolicy.RUNTIME) //用于反射
public @interface Controller {
}
/**
* @document: 用于映射请求路径到控制器方法
* @Author:SmallG
* @CreateTime:2023/8/21+18:06
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value();
}
注解标注UserController
/**
* @document:
* @Author:SmallG
* @CreateTime:2023/8/21+18:03
*/
@Controller
public class UserController {
@RequestMapping("/register")
public String register(HttpServletRequest request,
HttpServletResponse response,
Map<String, Object> model) {
//获取请求
String name = request.getParameter("name");
String gender = request.getParameter("male");
//注册逻辑
model.put("name", name);
model.put("gender", gender);
return "register.html";
}
@RequestMapping("/submit")
public String submit(HttpServletRequest request,
HttpServletResponse response,
Map<String, Object> model) {
String name = request.getParameter("name");
String gender = request.getParameter("male");
//表单提交测试逻辑
model.put("name", name);
model.put("gender", gender);
return "view.html";
}
}
定义HandlerMapping
/**
* HandlerMapping会根据请求的URL和其他相关信息,查找匹配的处理器
* 如果找到匹配的处理器,则HandlerMapping会将其返回给DispatcherServlet进行进一步处理
* 如果找不到匹配的处理器,则会返回一个null
*/
public class HandlerMapping {
//创建HandlerMapping的唯一实例 饿汉模式
private static HandlerMapping instance = new HandlerMapping();
//获取HandlerMapping的实例
public static HandlerMapping getInstance() {
return instance;
}
//私有构造函数,只能通过getInstance()获取实例
private HandlerMapping() {
try {
loadHandlers();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
//保存处理器的URI和对应的方法
private HashMap<String, Method> handlers = new HashMap<>();
//加载所有的处理器
private void loadHandlers() throws URISyntaxException, ClassNotFoundException {
//获取处理器所在目录
File dir = new File(HandlerMapping.class
.getResource("/cn/highedu/boot/controller").toURI());
//获取目录下的所有类文件
File[] files = dir.listFiles(file -> file.getName().endsWith(".class"));
for (File file : files) {
//获取类名
String fileName = file.getName();
String className = fileName.substring(0, fileName.lastIndexOf("."));
//加载类
Class clazz = Class.forName("cn.highedu.boot.controller." + className);
//如果类上有@Controller注解
if (clazz.isAnnotationPresent(Controller.class)) {
//遍历类中所有的方法
for (Method method : clazz.getDeclaredMethods()) {
//如果方法上有@RequestMapping注解
if (method.isAnnotationPresent(RequestMapping.class)) {
//获取@RequestMapping注解中的值,uri
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
String path = requestMapping.value();
//将URI和方法保存到handlers中
handlers.put(path,method);
}
}
}
}
}
public Method getHandlerMethod(String uri){
return handlers.get(uri);
}
/**
* 测试案例
*/
public static void main(String[] args) {
HandlerMapping mapping = getInstance();
System.out.println(mapping.handlers);
Method method = mapping.getHandlerMethod("/register");
System.out.println(method);
}
}
3 编写使用自定义参数创建 Java 线程池的示例
线程池参数要求如下:
- 线程池核心线程数 3
- 线程池最大线程数 20
- 线程空闲时间 20秒
- 等待队列容量 50
- 使用LinkedBlockingQueue 作为等待队列
- 自定义线程名称为 "Worker=%d"
- 拒绝策略为:AbortPolicy当工作队列已满,且线程池的线程数已经达到最大值时,抛出一个异常
- 创建50个线程任务进行测试,任务结束后关闭线程池
/**
* @document: 编写使用自定义参数创建 Java 线程池
* @Author:SmallG
* @CreateTime:2023/8/21+18:51
*/
public class CustomThreadPool {
public static void main(String[] args) {
//定义线程池参数
int corePoolSize = 3;
int maximumPoolSize = 20;
long keepAliveTime = 20;
int queueCapacity = 50;
//创建自定义线程
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(queueCapacity),
new ThreadFactoryBuilder().setNameFormat("Worker-%d").build(),
new ThreadPoolExecutor.AbortPolicy()
);
//向线程池提交任务
for (int i = 1; i <= 50; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println(Thread.currentThread().getName()
+ " is running task" + taskId);
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//关闭线程池
executor.shutdown();
}
}
4 编写原生MVC服务框架其他组件
要求:
1、使用注解标注控制器类和方法;
2、使用HandlerMapping查找URL对应的控制器方法;
3、使用ViewResolver解析视图;
4、使用MVC框架实现注册和登录功能,账户数据采用对象序列化方式保存到文件中。
1 条评论
《雾山五行之犀川幻紫林篇》国产动漫高清在线免费观看:https://www.jgz518.com/xingkong/35091.html