从零实现一个 Java 微框架 - 前言
前言
自制的 JavaWeb 框架 XK-Java 至今也开发了快一年的时间了,如果算上第一个 commit 之前的准备时间差不多就一年了,最近应该也不会再有大的结构更新了,所以就打算和之前 PHP 框架系列一样,写一个系列文章。也算是对这些技术的复盘吧,顺便如果可以的话也可以连 Spring 一起分析下。
观前提示:XK-Java 并不是 Spring 的缩小版,大部分模块都是由我自行实现的,所以原理也大不相同(当然有一些是 copy 自 Spring 的就另外说,逃),如果你是想通过本框架来学习 Spring 的话基本可以绕道了。
目录
项目地址
整体结构

这张图只展示了框架中包含的架构和各模块之间的关系,模块中的东西会在后续的文章中说明。
框架成果
_304@Controller_304@RequestMapping("/test")_304public class TestController {_304_304 private static final Logger log = LoggerFactory.getLogger(_304 TestController.class_304 );_304_304 @Autowired_304 List<Advice> list;_304_304 @Autowired_304 Advice[] advice;_304_304 @Autowired_304 List<Advice>[] lists;_304_304 @Autowired_304 ObjectProvider<Advice> adviceObjectProvider;_304_304 @Autowired_304 ObjectProvider<String> notFoundObjectProvider;_304_304 @Autowired(value = "name", required = false)_304 private String name;_304_304 @GetMapping("/{id}")_304 public String test(final int id) {_304 return "test";_304 }_304_304 @GetMapping("/get-a")_304 public int getAnnotation(@QueryValue final int id) {_304 return id;_304 }_304_304 @PostMapping("/post-a")_304 public int postAnnotation(@BodyValue final int id) {_304 return id;_304 }_304_304 @CrossOrigin_304 @GetMapping("/header-a")_304 public String headerAnnotation(@HeaderValue final String host) {_304 return host;_304 }_304_304 @PostMapping("/post")_304 @ResponseStatus_304 // @VerifyCsrf_304 public String post(_304 @BodyValue final JsonNode user,_304 @DataBind(name = "user") final User user2,_304 @WebBind(_304 name = "name",_304 type = Type.PATH,_304 converter = TestConverter.class_304 ) final String name,_304 @WebBind(name = "user3") final User2 user3,_304 @Valid @DataBind(name = "user4") final User2 user4,_304 // 如果不传入这两个其中一个参数,则会抛出异常_304 final ValidGroup validGroup,_304 final ValidResult<User2> validResult_304 ) {_304 return "post";_304 }_304_304 @PostMapping("/body")_304 public String body(_304 @DataBind(name = "&body") final JsonNode body,_304 @DataBind final User2 user2,_304 @DataBind(name = "request") final HttpServletRequest request_304 ) {_304 return "body";_304 }_304_304 @GetMapping("/case")_304 public String getCase(final String userName) {_304 return userName;_304 }_304_304 @GetMapping("/session-context")_304 public String sessionContext(final SessionTest sessionTest) {_304 if (sessionTest.getName() == null) {_304 sessionTest.setName("Otstar Lin");_304 return sessionTest.getName();_304 } else {_304 return sessionTest.getName() + "-Copy";_304 }_304 }_304_304 @GetMapping("/result-empty")_304 public String resultEmpty() {_304 return "empty:";_304 }_304_304 @GetMapping("/result-html")_304 public String resultHtml() {_304 return "html:<h1>Html Result</h1>";_304 }_304_304 @GetMapping("/result-json")_304 public String resultJson() {_304 return "json:{\"type\": \"Json Result\"}";_304 }_304_304 @GetMapping("/result-redirect")_304 public String resultRedirect() {_304 return "redirect:/result-json";_304 }_304_304 @GetMapping("/result-text")_304 public String resultText() {_304 return "text:Text Result";_304 }_304_304 @GetMapping("/result-view")_304 public String resultView(final Model model) {_304 model.addAttribute("name", "View Result");_304 return "view:index";_304 }_304_304 @GetMapping("/result-no-match")_304 public String resultNoMatch() {_304 return "No Match Result";_304 }_304_304 @GetMapping("/result-ignore")_304 public String resultIgnore() {_304 return ":empty:";_304 }_304_304 @GetMapping("/result-status")_304 public String resultStatus(final Model model) {_304 model.status(HttpStatus.BAD_REQUEST);_304 return "text:Status Result";_304 }_304_304 @GetMapping("/default")_304 public String defaultValue(_304 @QueryValue(name = "name", defaultValue = "default") final String name_304 ) {_304 return name;_304 }_304_304 @GetMapping("/stream")_304 public StreamResult stream() throws FileNotFoundException {_304 return Result.stream(_304 "video/mp4",_304 IoUtil.toStream(_304 ResourceUtils.getFile(_304 "file:/E:/Data/Videos/天气之子/天气之子.mp4"_304 )_304 )_304 );_304 }_304_304 @GetMapping("/file")_304 public File file() {_304 return FileUtil.file("file:/E:/Data/Videos/天气之子/天气之子.mp4");_304 }_304_304 @GetMapping("/async")_304 public Callable<String> async() {_304 return () -> {_304 log.info("Callable");_304 return "result";_304 };_304 }_304_304 @GetMapping("/async-task")_304 public WebAsyncTask<String> asyncTask() {_304 final WebAsyncTask<String> asyncTask = new WebAsyncTask<>(_304 () -> {_304 log.info("WebAsyncTask");_304 return "result";_304 }_304 );_304 asyncTask.onCompletion(_304 () -> {_304 log.info("WebAsyncTask completion");_304 }_304 );_304 return asyncTask;_304 }_304_304 @GetMapping("/async-result")_304 public AsyncResult<String> asyncResult() {_304 return context -> {_304 Result_304 .file("file:/E:/Data/Videos/天气之子/天气之子.mp4")_304 .toResponse(context.request(), context.response(), null);_304 return null;_304 };_304 }_304_304 @GetMapping("/deferred")_304 public WebDeferredTask<String> deferred() {_304 final WebDeferredTask<String> deferredTask = new WebDeferredTask<>();_304 new Thread(_304 () -> {_304 try {_304 Thread.sleep(1000L);_304 } catch (final InterruptedException e) {_304 log.error("Deferred sleep error", e);_304 }_304 log.info("Deferred set result");_304 deferredTask.result("deferred");_304 }_304 )_304 .start();_304 return deferredTask;_304 }_304_304 @GetMapping("/async-timeout")_304 public WebAsyncTask<String> asyncTimeout() {_304 final WebAsyncTask<String> asyncTask = new WebAsyncTask<>(_304 () -> {_304 log.info("Async timeout sleep");_304 try {_304 Thread.sleep(3000L);_304 } catch (final Exception e) {_304 log.error("Async sleep error", e);_304 }_304 return "result";_304 }_304 );_304 asyncTask.timeout(1000L);_304 asyncTask.onTimeout(_304 () -> {_304 log.info("Timeout");_304 return "timeout";_304 }_304 );_304 return asyncTask;_304 }_304_304 @GetMapping("/async-pool")_304 @WebAsync("testAsyncExecutor")_304 public Callable<String> asyncPool() {_304 return () -> {_304 log.info("Callable");_304 return "result";_304 };_304 }_304_304 @GetMapping("/reactive-mono")_304 public Mono<String> reactiveMono() {_304 return Mono.just("mono");_304 }_304_304 @GetMapping("/reactive-flux")_304 public Flux<String> reactiveFlux(final Response response) {_304 response.contentType("text/event-stream");_304 response.header("Cache-Control", "no-cache");_304 return Flux_304 .interval(Duration.ofSeconds(1))_304 .map(_304 seq ->_304 String.format("id:%d\nevent:random\ndata:%d\n\n", seq, seq)_304 )_304 .doOnNext(System.out::println);_304 }_304_304 @Bean_304 public static ExecutorService testAsyncExecutor() {_304 return new ThreadPoolExecutor(_304 3,_304 3,_304 0L,_304 TimeUnit.SECONDS,_304 new LinkedBlockingQueue<>(),_304 ThreadUtil.newNamedThreadFactory("testAsyncExecutor-", true)_304 );_304 }_304_304 @InitBinder_304 public void binder(final WebDataBinder binder) {_304 final User2 user2 = new User2();_304 user2.setName("user3");_304 user2.setAge(17);_304 binder.addDefault("user3", user2);_304 binder.addDefault("user4.name", "user4");_304 }_304_304 @PreDestroy_304 public void destroy() {_304 System.out.println("TestController destroy");_304 }_304_304 public static class TestConverter implements Converter {_304_304 @Override_304 public Object before(_304 final Object object,_304 final String name,_304 final TypeWrapper<?> type,_304 final MergedAnnotation annotation,_304 final Container container_304 ) {_304 return "test-converter";_304 }_304 }_304}
更多的样例可以到 xkjava-app 模块里查看。
结语
本篇文章只是一个开篇,所以是一篇水文 2333。
XK-Java 参考了以下框架:
感谢这些框架为我提供了实现和学习的思路。
从零实现一个 Java 微框架 - 前言
https://blog.ixk.me/post/implement-a-java-microframework-from-zero-1许可协议
发布于
2021-04-08
本文作者
Otstar Lin
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!