{"id":15211381,"url":"https://github.com/taketoday/today-web","last_synced_at":"2025-10-03T04:30:42.738Z","repository":{"id":34294638,"uuid":"144986243","full_name":"TAKETODAY/today-web","owner":"TAKETODAY","description":"😐 A Java library for building web applications","archived":true,"fork":false,"pushed_at":"2023-05-10T01:23:41.000Z","size":2983,"stargazers_count":33,"open_issues_count":1,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-09-28T08:40:12.153Z","etag":null,"topics":["framework","high-performance","java","java-web","lightweight","servlet","springmvc"],"latest_commit_sha":null,"homepage":"https://taketoday.cn/articles/1553922073210.html","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TAKETODAY.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-16T12:42:47.000Z","updated_at":"2023-05-10T01:24:13.000Z","dependencies_parsed_at":"2024-09-24T11:02:19.759Z","dependency_job_id":"319e191e-b84e-4725-816d-2f0b5b212314","html_url":"https://github.com/TAKETODAY/today-web","commit_stats":{"total_commits":903,"total_committers":6,"mean_commits":150.5,"dds":"0.023255813953488413","last_synced_commit":"c5f27c499159ad403147fcde17b49eb7c9ca9a79"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TAKETODAY%2Ftoday-web","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TAKETODAY%2Ftoday-web/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TAKETODAY%2Ftoday-web/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TAKETODAY%2Ftoday-web/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TAKETODAY","download_url":"https://codeload.github.com/TAKETODAY/today-web/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219876149,"owners_count":16554739,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["framework","high-performance","java","java-web","lightweight","servlet","springmvc"],"created_at":"2024-09-28T08:40:18.912Z","updated_at":"2025-10-03T04:30:42.323Z","avatar_url":"https://github.com/TAKETODAY.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"moved to https://github.com/TAKETODAY/today-infrastructure/tree/master/today-web\r\n\r\n# TODAY Web\r\n\r\n🍎 A Java library for building web applications\r\n\r\n![Java8](https://img.shields.io/badge/JDK-8+-success.svg)\r\n[![GPLv3](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE)\r\n[![Author](https://img.shields.io/badge/Author-TODAY-blue.svg)](https://github.com/TAKETODAY)\r\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/811723d275dc409ba6a823c9e08a5b3b)](https://app.codacy.com/app/TAKETODAY/today-web?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=TAKETODAY/today-web\u0026utm_campaign=Badge_Grade_Dashboard)\r\n[![Java CI](https://github.com/TAKETODAY/today-web/workflows/Java%20CI/badge.svg)](https://github.com/TAKETODAY/today-web/actions)\r\n\r\n\r\n## 背景\r\n\u003cdetails\u003e\r\n  \u003csummary\u003e先不看\u003c/summary\u003e\r\n\u003cblockquote\u003e\r\n\t\u003cp\u003e\r\n  本人从2016年大一开始学Java，准确的说是高三最后的几周开始的. 果然兴趣是最好的老师， 在大一下学期自己独自一人从前端到后台写了我的个人网站：\u003ca href=\"https://taketoday.cn\"\u003eTODAY BLOG\u003c/a\u003e 。 从注册域名到备案再到网站成功上线，我遇到过的困难数不计其数。因为感兴趣所以我坚持了下来。第一个版本使用的纯Servlet写的。\r\n    后来了解到Java有很多开源框架可以简化我的开发。于是又投入到新一轮的学习之中...... 学了Struts2后自己学着写了一个小框架：\u003ca href=\"https://gitee.com/I-TAKE-TODAY/today-web/tree/v1.1.1/\"\u003eTODAY Web 1.0 \u003c/a\u003e，几百行搞定从解析xml定义的action到处理对应的请求。学了Spring MVC后，我写了此项目：\u003ca href=\"https://gitee.com/I-TAKE-TODAY/today-web\"\u003eTODAY Web 2.0\u003c/a\u003e。\r\n\t\u003c/p\u003e\r\n\u003c/blockquote\u003e\r\n \r\n\u003c/details\u003e\r\n\r\n## 安装\r\n\r\n```xml\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003ecn.taketoday\u003c/groupId\u003e\r\n    \u003cartifactId\u003etoday-web\u003c/artifactId\u003e\r\n    \u003cversion\u003e3.0.2.RELEASE\u003c/version\u003e\r\n\u003c/dependency\u003e\r\n```\r\n- [Maven Central](https://search.maven.org/artifact/cn.taketoday/today-web/3.0.0.RELEASE/jar)\r\n\r\n## 案例\r\n- [DEMO](https://github.com/TAKETODAY/today-web-demo)\r\n\r\n## 文档\r\n- [Wiki](https://gitee.com/I-TAKE-TODAY/today_web/wikis)\r\n\r\n## 使用说明\r\n\r\n### 函数式路由\r\n\r\n```java\r\n@Component\r\n@ResponseBody\r\npublic class FunctionController {\r\n\r\n    public String function(RequestContext request) {\r\n        return \"body:\" + request.method() + \" requestURI -\u003e \" + request.requestURI();\r\n    }\r\n\r\n    public String test(RequestContext request) {\r\n        return \"body:\" + request.method() + \" test -\u003e \" + request.requestURI();\r\n    }\r\n\r\n    public void script(RequestContext request) throws IOException {\r\n\r\n        ModelAndView modelAndView = new ModelAndView();\r\n        request.modelAndView(modelAndView);\r\n\r\n        modelAndView.setContentType(\"text/html;charset=UTF-8\");\r\n        modelAndView.setView(new StringBuilder(\"\u003cscript\u003ealert('HELLO， 你好 script');\u003c/script\u003e\"));\r\n    }\r\n}\r\n\r\n@Configuration\r\n//@EnableDefaultMybatis\r\n//@EnableRedissonCaching\r\npublic class WebMvcConfig implements WebMvcConfiguration {\r\n\r\n    @Autowired\r\n    private FunctionController functionController;\r\n\r\n    @Override\r\n    public void configureFunctionHandler(FunctionHandlerRegistry registry) {\r\n\r\n        registry.get(\"/function\", functionController::function);\r\n        registry.get(\"/function/test\", functionController::test);\r\n        registry.get(\"/function/script\", functionController::script);\r\n\r\n        registry.get(\"/function/error/500\", (context) -\u003e {\r\n            context.sendError(500);\r\n        });\r\n    }\r\n}\r\n```\r\n\r\n### 注解路由\r\n\r\n```java\r\n//@Controller\r\n@RestController\r\n@RequestMapping(\"/users\")\r\npublic class UserController {\r\n    \r\n\r\n    @GET(\"index\")\r\n    @POST(\"post\")\r\n    @PUT(\"articles/{id}\")\r\n    ......\r\n    @RequestMapping(\"/users/{id}\")\r\n    @RequestMapping(value = \"/**\", method = {RequestMethod.GET})\r\n    @RequestMapping(value = \"/*.html\", method = {RequestMethod.GET})\r\n    @RequestMapping(value = {\"/index.action\", \"/index.do\", \"/index\"}, method = RequestMethod.GET)\r\n    @Interceptor({LoginInterceptor.class, ...})\r\n    public (String|List\u003c?\u003e|Set\u003c?\u003e|Map\u003c?\u003e|void|File|Image|...) \\\\w+ (request, request, session,servletContext, str, int, long , byte, short, boolean, @Session(\"loginUser\"), @Header(\"User-Agent\"), @Cookie(\"JSESSIONID\"), @PathVariable(\"id\"), @RequestBody(\"users\"), @Multipart(\"uploadFiles\") MultipartFile[]) {\r\n        service...\r\n        return \u003c/\u003e;\r\n    }\r\n}\r\n\r\n```\r\n\r\n### ViewController\r\n\r\n```java\r\n@Configuration\r\npublic class WebMvcConfig implements WebMvcConfiguration {\r\n\r\n    @Override\r\n    public void configureViewController(ViewControllerHandlerRegistry registry) {\r\n        registry.addViewController(\"/github\", \"redirect:https://github.com\");\r\n        registry.addRedirectViewController(\"/login.do\", \"/login\");\r\n        registry.addViewController(\"/login.action\")\r\n                .setAssetsPath(\"redirect:/login\");\r\n    }\r\n}\r\n\r\n```\r\n\r\n### 静态资源\r\n\r\n```java\r\n@Singleton\r\n@Profile(\"dev\")\r\npublic ResourceHandlerRegistry devRsourceMappingRegistry(@Env(\"site.uploadPath\") String upload,\r\n                                                         @Env(\"site.assetsPath\") String assetsPath) //\r\n{\r\n    final ResourceHandlerRegistry registry = new ResourceHandlerRegistry();\r\n\r\n    registry.addResourceMapping(\"/assets/**\")//\r\n            .addLocations(assetsPath);\r\n\r\n    registry.addResourceMapping(\"/upload/**\")//\r\n            .addLocations(upload);\r\n\r\n    registry.addResourceMapping(\"/logo.png\")//\r\n            .addLocations(\"file:///D:/dev/www.yhj.com/webapps/assets/images/logo.png\");\r\n\r\n    registry.addResourceMapping(\"/favicon.ico\")//\r\n            .addLocations(\"classpath:/favicon.ico\");\r\n\r\n    return registry;\r\n}\r\n\r\n@Singleton\r\n@Profile(\"prod\")\r\npublic ResourceHandlerRegistry prodResourceMappingRegistry() {\r\n\r\n    final ResourceHandlerRegistry registry = new ResourceHandlerRegistry();\r\n\r\n    registry.addResourceMapping(LoginInterceptor.class)//\r\n            .setPathPatterns(\"/assets/admin/**\")//\r\n            .setOrder(Ordered.HIGHEST_PRECEDENCE)//\r\n            .addLocations(\"/assets/admin/\");\r\n\r\n    return registry;\r\n}\r\n\r\n@Override\r\npublic void configureResourceHandler(ResourceHandlerRegistry registry) {\r\n\r\n    registry.addResourceMapping(LoginInterceptor.class)//\r\n            .setPathPatterns(\"/assets/admin/**\")//\r\n            .setOrder(Ordered.HIGHEST_PRECEDENCE)//\r\n            .addLocations(\"/assets/admin/\");\r\n}\r\n```\r\n\r\n### 自定义参数转换器\r\n\r\n```java\r\n\r\n@Singleton\r\npublic class UserSessionParameterResolver implements OrderedParameterResolver {\r\n  private final WebSessionManager sessionManager;\r\n\r\n  public UserSessionParameterResolver(WebSessionManager sessionManager) {\r\n    this.sessionManager = sessionManager;\r\n  }\r\n\r\n  @Override\r\n  public boolean supports(MethodParameter parameter) {\r\n    return parameter.isAnnotationPresent(UserSession.class);\r\n  }\r\n\r\n  @Override\r\n  public Object resolveParameter(final RequestContext context, final MethodParameter parameter) throws Throwable {\r\n    final WebSession session = sessionManager.getSession(context, false);\r\n    if (session != null) {\r\n      final Object attribute = session.getAttribute(Constant.USER_INFO);\r\n      if (attribute != null) {\r\n        return attribute;\r\n      }\r\n    }\r\n    throw new UnauthorizedException();\r\n  }\r\n\r\n  @Override\r\n  public int getOrder() {\r\n    return HIGHEST_PRECEDENCE;\r\n  }\r\n\r\n}\r\n\r\n@Singleton\r\n@Order(Ordered.HIGHEST_PRECEDENCE)\r\npublic class PageableMethodArgumentResolver implements ParameterResolver {\r\n\r\n  private static final String PARAMETER_SIZE = \"size\";\r\n  private static final String PARAMETER_CURRENT = \"page\";\r\n\r\n  private int maxListSize;\r\n  private int defaultListSize;\r\n\r\n  @Override\r\n  public boolean supports(MethodParameter parameter) {\r\n    return parameter.isAssignableTo(Pageable.class);\r\n  }\r\n\r\n  @Override\r\n  public Object resolveParameter(RequestContext request, MethodParameter parameter) throws Throwable {\r\n    return new RequestContextPageable(request, defaultListSize, maxListSize);\r\n  }\r\n\r\n  public int getMaxListSize() {\r\n    return maxListSize;\r\n  }\r\n\r\n  public void setMaxListSize(int maxListSize) {\r\n    this.maxListSize = maxListSize;\r\n  }\r\n\r\n  public int getDefaultListSize() {\r\n    return defaultListSize;\r\n  }\r\n\r\n  public void setDefaultListSize(int listSize) {\r\n    this.defaultListSize = listSize;\r\n  }\r\n\r\n  public final static class RequestContextPageable implements Pageable {\r\n\r\n    private final int maxListSize;\r\n    private final int defaultListSize;\r\n\r\n    private Integer size;\r\n    private Integer current;\r\n    private final RequestContext request;\r\n\r\n    public RequestContextPageable(RequestContext request, int listSize, int maxListSize) {\r\n      this.request = request;\r\n      this.defaultListSize = listSize;\r\n      this.maxListSize = maxListSize;\r\n    }\r\n\r\n    @Override\r\n    public int getCurrent() {\r\n\r\n      if (current == null) {\r\n        final String parameter = request.getParameter(PARAMETER_CURRENT);\r\n        if (StringUtils.isEmpty(parameter)) {\r\n          current = 1;\r\n        }\r\n        else if ((current = Integer.valueOf(parameter)) \u003c= 0) {\r\n          throw new IllegalArgumentException(\"only 'page \u003e 0'\");\r\n        }\r\n      }\r\n      return current.intValue();\r\n    }\r\n\r\n    @Override\r\n    public int getSize() {\r\n      if (size == null) {\r\n        int s;\r\n        final String parameter = request.getParameter(PARAMETER_SIZE);\r\n        if (StringUtils.isEmpty(parameter)) {\r\n          s = defaultListSize;\r\n        }\r\n        else {\r\n          s = Integer.parseInt(parameter);\r\n          if (s \u003c= 0) {\r\n            throw new IllegalArgumentException(\"only 'size \u003e 0'\");\r\n          }\r\n          if (s \u003e maxListSize) {\r\n            throw DemoUtils.accessForbidden();\r\n          }\r\n        }\r\n        return size = s;\r\n      }\r\n      return size.intValue();\r\n    }\r\n\r\n  }\r\n\r\n}\r\n\r\n@Component\r\npublic class DateConverter implements Converter\u003cString, Date\u003e {\r\n    @Override\r\n    public Date convert(String source) throws ConversionException {\r\n        ...\r\n    }\r\n}\r\n\r\n\r\n\r\n\r\n```\r\n\r\n### 也可以通过xml文件配置简单视图\r\n\r\n```xml\r\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\r\n\u003c!DOCTYPE Web-Configuration PUBLIC \r\n\t\t\"-//TODAY BLOG//Web - Configuration DTD 2.0//CN\"\r\n\t\t\t\"https://taketoday.cn/framework/web/dtd/web-configuration-2.3.7.dtd\"\u003e\r\n\r\n\u003cWeb-Configuration\u003e\r\n\r\n    \u003ccontroller prefix=\"/error/\"\u003e\r\n        \u003caction resource=\"400\" name=\"BadRequest\" status=\"400\" /\u003e\r\n        \u003caction resource=\"403\" name=\"Forbidden\" status=\"403\" /\u003e\r\n        \u003caction resource=\"404\" name=\"NotFound\" status=\"404\" /\u003e\r\n        \u003caction resource=\"500\" name=\"ServerIsBusy\" status=\"500\" /\u003e\r\n        \u003caction resource=\"405\" name=\"MethodNotAllowed\" status=\"405\" /\u003e\r\n    \u003c/controller\u003e\r\n\r\n    \u003ccontroller\u003e\r\n        \u003caction resource=\"redirect:http://pipe.b3log.org/blogs/Today\" name=\"today-blog-pipe\" /\u003e\r\n        \u003caction resource=\"redirect:https://taketoday.cn\" name=\"today\" /\u003e\r\n        \u003caction resource=\"redirect:https://github.com\" name=\"github\" /\u003e\r\n        \u003caction resource=\"redirect:/login\" name=\"login.do\" /\u003e\r\n    \u003c/controller\u003e\r\n\r\n    \u003ccontroller class=\"cn.taketoday.web.demo.controller.XMLController\" name=\"xmlController\" prefix=\"/xml/\"\u003e\r\n        \u003caction name=\"obj\" method=\"obj\" /\u003e\r\n        \u003caction name=\"test\" resource=\"test\" method=\"test\"/\u003e\r\n    \u003c/controller\u003e\r\n\r\n\u003c/Web-Configuration\u003e\r\n```\r\n### 登录实例\r\n\r\n```java\r\n@Controller\r\npublic class UserController {\r\n\r\n/* \r\n    \u003ccontroller prefix=\"/WEB-INF/view/\" suffix=\".ftl\"\u003e\r\n        \u003caction resource=\"login\" name=\"login\" /\u003e\r\n        \u003caction resource=\"register\" name=\"register\" /\u003e\r\n    \u003c/controller\u003e */\r\n    \r\n    // @GET(\"login\")\r\n    @RequestMapping(value = \"/login\" , method = RequestMethod.GET)\r\n    public String login() {\r\n        return \"/login/login\";//支持jsp,FreeMarker,Thymeleaf,自定义视图\r\n    }\r\n    \r\n    @Logger(\"登录\")\r\n    //@POST(\"/login\")\r\n    //@RequestMapping(value = \"/login\" , method = RequestMethod.POST)\r\n    @ActionMapping(value = \"/login\", method = RequestMethod.POST)\r\n    public String login(HttpSession session, RedirectModel redirectModel, @Valid User user, Errors error) {\r\n    \r\n        if (error.hasErrors()) {\r\n            System.err.println(error.getAllErrors());\r\n            redirectModel.attribute(\"msg\", error.getAllErrors().toString());\r\n            return \"redirect:/login\";\r\n        }\r\n    \r\n        User login = userService.login(user);\r\n        if (login == null) {\r\n            redirectModel.attribute(\"userId\", user.getUserId());\r\n            redirectModel.attribute(\"msg\", \"登录失败\");\r\n            return \"redirect:/login\";\r\n        }\r\n        redirectModel.attribute(\"msg\", \"登录成功\");\r\n        session.setAttribute(USER_INFO, login);\r\n        return \"redirect:/user/info\";\r\n    }\r\n    \r\n}\r\n```\r\n\r\n### 文件下载，支持直接返回给浏览器图片\r\n\r\n```java\r\n@RequestMapping(value = {\"/download\"}, method = RequestMethod.GET)\r\npublic File download(String path) {\r\n    return new File(path);\r\n}\r\n```\r\n\r\n```java\r\n@GET(\"/display\")\r\npublic final BufferedImage display(HttpServletResponse response) throws IOException {\r\n    response.setContentType(\"image/jpeg\");\r\n    return ImageIO.read(new File(\"D:/taketoday.cn/webapps/upload/logo.png\"));\r\n}\r\n\r\n@GET(\"captcha\")\r\npublic final BufferedImage captcha(HttpServletRequest request) throws IOException {\r\n    BufferedImage image = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_RGB);\r\n    Graphics graphics = image.getGraphics();\r\n    graphics.setColor(Color.WHITE);\r\n    graphics.fillRect(0, 0, IMG_WIDTH, IMG_HEIGHT);\r\n    Graphics2D graphics2d = (Graphics2D) graphics;\r\n    drawRandomNum(graphics2d, request);\r\n    return image;\r\n}\r\n```\r\n\r\n### 文件上传，支持多文件\r\n\r\n```java\r\n@RequestMapping(value = { \"/upload\" }, method = RequestMethod.POST)\r\npublic final String upload(@Multipart MultipartFile uploadFile) throws IOException {\r\n\r\n    String upload = \"D:/www.yhj.com/webapps/upload/\";\r\n    String path = upload + uploadFile.getFileName();\r\n    File file = new File(path);\r\n    uploadFile.save(file);\r\n\r\n    return \"/upload/\" + uploadFile.getFileName();\r\n}\r\n\r\n@POST({\"/upload/multi\"})\r\npublic final String multiUpload(HttpServletResponse response, @Multipart MultipartFile[] files) throws IOException {\r\n\r\n    String upload = \"D:/www.yhj.com/webapps/upload/\";\r\n    \r\n    for (MultipartFile multipartFile : files) {\r\n        String path = upload + multipartFile.getFileName();\r\n        File file = new File(path);\r\n        System.out.println(path);\r\n        if (!multipartFile.save(file)) {\r\n            return \"\u003cscript\u003ealert('upload error !')\u003c/script\u003e\";\r\n            //response.getWriter().print(\"\u003cscript\u003ealert('upload error !')\u003c/script\u003e\");\r\n        }\r\n    }\r\n    //response.getWriter().print(\"\u003cscript\u003ealert('upload success !')\u003c/script\u003e\");\r\n    return \"\u003cscript\u003ealert('upload success !')\u003c/script\u003e\";\r\n}\r\n```\r\n\r\n\r\n## 🙏 鸣谢\r\n本项目的诞生离不开以下开源项目：\r\n* [Freemarker](https://github.com/apache/freemarker): Apache Freemarker\r\n* [Slf4j](https://github.com/qos-ch/slf4j): Simple Logging Facade for Java\r\n* [Spring](https://github.com/spring-projects/spring-framework): Spring Framework\r\n* [EL](https://github.com/TAKETODAY/today-expression): Java Unified Expression Language\r\n* [FastJSON](https://github.com/alibaba/fastjson): A fast JSON parser/generator for Java\r\n* [Lombok](https://github.com/rzwitserloot/lombok): Very spicy additions to the Java programming language\r\n* [Today Context](https://github.com/TAKETODAY/today-context): A Java library for dependency injection and aspect oriented programing\r\n* [Hibernate Validator](https://github.com/hibernate/hibernate-validator): Hibernate Validator - Bean Validation 2.0 (JSR 380) Reference Implementation\r\n\r\n\r\n## 📄 开源协议\r\n请查看 [GNU GENERAL PUBLIC LICENSE](https://github.com/TAKETODAY/today-web/blob/master/LICENSE)\r\n\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaketoday%2Ftoday-web","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftaketoday%2Ftoday-web","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaketoday%2Ftoday-web/lists"}