Springboot基础-基于SpringMVC的Web开发

前提:在SpringBoot中导入了web场景包

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

一、简单静态资源访问

1.静态资源目录

在这里插入图片描述
默认配置了这四个文件夹的静态资源访问/staticor /publicor/resourcesor/META-INF/resources
只要访问: 当前项目根路径/ + 静态资源名就可以拿到。
原理: 静态映射/**的所有请求,请求进来先去看Controller能不能处理,如果不能再转到静态资源处理, 最后再转到404页面。

2.静态资源前缀配置

默认无前缀

# 这表示只有静态资源的访问路径为/res/**时,才会处理请求
spring:
	mvc:
		static-path-pattern: /res/**

配置了之后可以使用: 项目根路径 + static-path-pattern + 资源名 = 静态资源文件夹下寻找

# 还可以配置
spring:
	web:
		resources:
			static-locations: classpath:/haha
# 这样就指定了/haha是静态资源文件夹, 取代了前面四个默认值(因为前四个默认值是一个数组,所以自定义写了值的话,就直接顶替掉了那个数组变量,详见下文源码)

3.欢迎页支持

  • 静态资源目录下放一个index.html
  • 静态资源目录下放一个favicon.ico
  • 能处理/index请求的Controller

4.原理

SpringBoot启动默认加载xxxAutoConfiguration类(自动配置类)
SpringMVC功能的自动配置类WebMVCAutoConfiguration,一系列@Conditional判断之后生效

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {}

那么这个自动配置类给容器中配置了什么?

@Configuration(proxyBeanMethods = false)
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {}

它里面有一个内部类WebMvcAutoConfigurationAdapter,与配置文件的相关属性进行了绑定。WebMvcProperties(prefix = "spring.mvc")ResourceProperties(prefix = "spring.resources")WebProperties(@ConfigurationProperties("spring.web"))
同时该内部类只有一个有参构造器

# 有参构造器所有参数的值都会从容器中确定
# ResourceProperties resourceProperties:获取和spring.resources绑定的所有值的对象
# WebProperties webProperties:获取和spring.mvc绑定的所有值的对象
# ListableBeanFactory beanFactory:Spring的beanFactory
# HttpMessageConverters 找到所有的HttpMessageConverters
# ResourceHandlerRegistrationCustomizer 找到资源处理器的自定义器 <-
# DispatcherServletPath
# ServletRegistrationBean 给应用注册ServletFilterpublic WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
    this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources());
    this.mvcProperties = mvcProperties;
    this.beanFactory = beanFactory;
    this.messageConvertersProvider = messageConvertersProvider;
    this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
    this.dispatcherServletPath = dispatcherServletPath;
    this.servletRegistrations = servletRegistrations;
    this.mvcProperties.checkConfiguration();
}

在该内部类中找到静态资源配置的处理器代码:

public void addResourceHandlers(ResourceHandlerRegistry registry) {
	// spring.resources.add-mappings=false的话,静态资源就无法访问(默认是true)
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
    	// 默认注册/webjars/**匹配到classpath:/META-INF/resources/webjars/路径
        this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
        // 再注册spring.mvc.static-path-pattern匹配到spring.web.resources.static-locations路径的静态资源访问,如果用户配置文件没写也有默认值(/**和四个静态资源文件夹,见下一段代码)
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                registration.addResourceLocations(new Resource[]{resource});
            }

        });
    }
}
// 节选spring.web.resources.static-locations默认文件夹
public static class Resources {
	private static final String[]CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
	private String[] staticLocations;
	
	public Resources() {
	    this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
	}
}

同时在内部类中还可以看到欢迎页的处理规则:

HandlerMapping: 处理器映射。保存了每一个Handler能处理哪些请求

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
	// 只能找到spring.mvc.static-path-pattern为/**(默认)的欢迎页,如果自己配置了访问前缀,就找不到index.html了
    if (welcomePage != null && "/**".equals(staticPathPattern)) {
        logger.info("Adding welcome page: " + welcomePage);
        this.setRootViewName("forward:index.html");
    } else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
    	// 如果没有找到符合条件的静态资源index.html,就去Controller找/index
        logger.info("Adding welcome page template: index");
        this.setRootViewName("index");
    }
}

二、请求参数处理

1.REST请求映射

  • @xxxMapping
  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
    • 以前: /getUser /deleteUser /editUser /saveUser
    • 现在: /user 然后以不同的请求方式-> GET DELETE PUT POST
    • 核心Filter HiddenHttpMethodFilter
    • 用法: 表单method=post, 隐藏域_method=put
    <form action="/user" method="get">
      <input type="submit" value="REST-get提交">
    </form>
    <form action="/user" method="post">
        <input type="submit" value="REST-post提交">
    </form>
    <form action="/user" method="post">
        <input type="hidden" name="_method" value="put">
        <input type="submit" value="REST-put提交">
    </form>
    <form action="/user" method="post">
        <input type="hidden" name="_method" value="delete">
        <input type="submit" value="REST-delete提交">
    </form>
    
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public String getUser(){
        return "GET";
    }
    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public String saveUser(){
        return "SAVE";
    }
    @RequestMapping(value = "/user", method = RequestMethod.PUT)
    public String putUser(){
        return "PUT";
    }
    @RequestMapping(value = "/user", method = RequestMethod.DELETE)
    public String delUser(){
        return "DELETE";
    }
    
    // 默认是关闭掉隐藏域过滤器的
    @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"},    
        matchIfMissing = false
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }
    
    # 在配置文件中显式开启该过滤器
    spring:
      mvc:
        hiddenmethod:
          filter:
            enabled: true # 开启页面表单的REST, 因为表单只能写POST请求, 客户端发送的请求可以直接是PUT, 则无需开启
    
    然后就可以通过Rest方式进入Controller同一个URL的不同method处理了
Rest原理(表单提交要使用REST时):
  • 表单提交会带上<input type="hidden" name="_method" value="put">
  • 请求过来被OrderedHiddenHttpMethodFilter拦截
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        // 首先必须是POST请求
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
        	// methodParam = "_method";然后拿到隐藏域的_method的值
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
            	// 然后把_method内的单词转为大写
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                // ALLOWED_METHODS = [PUT, DELETE, PATCH];
                if (ALLOWED_METHODS.contains(method)) {
                	// 进行了一个包装, 把符合要求的三种扩展请求方式, 包装进原请求
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                    // 见下段代码
                }
            }
        }
        // 最后带着包装后的请求, 执行过滤器链, 完成了REST风格的扩展
        filterChain.doFilter((ServletRequest)requestToUse, response);
    }
    
    public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
        super(request);
        this.method = method;
    }
    
Rest(使用客户端工具):
  • 如PostMan可以直接发送PUT, DELETE等请求, 无需Filter
扩展注解
// @RequestMapping(value = "/user", method = RequestMethod.GET)
@GetMapping("/user")
public String getUser() {
    return "GET";
}

// @RequestMapping(value = "/user", method = RequestMethod.POST)
@PostMapping("/user")
public String saveUser() {
    return "SAVE";
}

// @RequestMapping(value = "/user", method = RequestMethod.PUT)
@PutMapping("/user")
public String putUser() {
    return "PUT";
}

// @RequestMapping(value = "/user", method = RequestMethod.DELETE)
@DeleteMapping("/user")
public String delUser() {
    return "DELETE";
}

2.请求分发

在这里插入图片描述
接下来我们研究doDispatch()方法:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// 	...省略若干代码...
	// 找到当前请求使用哪个Handler处理(xxController.xxMethod())
	mappedHandler = this.getHandler(processedRequest);
	// 	...省略若干代码...
}
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// HandlerMappings保存了/url -> xxController.xxMethod() 的映射
    if (this.handlerMappings != null) {
        Iterator var2 = this.handlerMappings.iterator();

        while(var2.hasNext()) {
            HandlerMapping mapping = (HandlerMapping)var2.next();
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

在这里插入图片描述
默认有5个HandlerMapping, 比如我们熟悉的WelcomePageHandlerMapping内保存了/路径 -> /indexorclasspath:index.html的映射。

接下来我们的主角是RequestMappingHandlerMapping,它保存了所有@RequestMapping注解和handler的映射规则(在应用启动过程中,Spring扫描了所有Controller,并且将其信息保存到RequestMappingHandlerMapping中)。
在这里插入图片描述
比如我请求的是/user在匹配过程中, 会先根据路径匹配
在这里插入图片描述
然后发现有四个可以匹配到的路径, 再慢慢匹配Method, 直到最后拿到唯一确定的Handler返回给doDispatch()的mappedHandler,如果最终还有多个匹配的,就会报错。

总结
  • SpringBoot自动配置欢迎页的HandlerMapping,访问/能访问到index.html或者/index
  • SpringBoot自动配置了默认的RequestMappingHandlerMapping
  • 请求进来,挨个尝试所有HandlerMapping看是否有请求信息
    • 如果有就找到这个请求对应的handler
    • 如果没有就下一个HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping

3.普通参数与基本注解

@PathVariable 路径变量

@GetMapping("/car/{id}/window/{brand}")
public String getCar(@PathVariable("id") String id,
                     @PathVariable("brand") String brand,
                     @PathVariable Map<String, String> pv){
    return id + "\n" + brand + "\n" + pv.toString();
}

http://localhost:8089/car/no1/window/BYD
请求得到 =>
no1 BYD {id=no1, brand=BYD}

@RequestHeader 获取请求头

@GetMapping("/test")
public String getCar(@RequestHeader("User-Agent") String userAgent,
                     @RequestHeader Map<String, String> rh){
    return userAgent + "\n" + rh.toString();
}

http://localhost:8089/test
请求得到 =>
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 {host=localhost:8089, connection=keep-alive, pragma=no-cache, cache-control=no-cache, sec-ch-ua=" Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96", sec-ch-ua-mobile=?0, sec-ch-ua-platform="Windows", upgrade-insecure-requests=1, user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36, accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/ *;q=0.8,application/signed-exchange;v=b3;q=0.9, sec-fetch-site=none, sec-fetch-mode=navigate, sec-fetch-user=?1, sec-fetch-dest=document, accept-encoding=gzip, deflate, br, accept-language=zh-CN,zh;q=0.9,en;q=0.8, cookie=Webstorm-66b7c9ab=246565bc-c7a7-4785-ae87-22d92088296e; Idea-ca2b6365=a9f831df-c5d1-4aa9-b53c-a9adebb95a7a}

@RequestParam 获取请求参数

@GetMapping("/test1")
public String test1(@RequestParam("name") String name,
                    @RequestParam("age") String age,
                    @RequestParam("hobby") List<String> hobby,
                    @RequestParam Map<String, String> rp){
    return name + " " + age + " " + hobby.toString() + " " + rp.toString();
}

http://localhost:8089/test1?name=Aomrsou&age=23&hobby=篮球&hobby=足球&hobby=羽毛球
请求得到 =>
Aomrsou 23 [篮球, 足球, 羽毛球] {name=Aomrsou, age=23, hobby=篮球}

@CookieValue 获取cookie值

<script type="text/javascript">
    document.cookie="name=Aomrsou;path=/"
    // 时间可以不要,但路径(path)必须要填写,因为JS的默认路径是当前页,如果不填,此cookie只在当前页面生效!~
</script>
<a href="/test2">getCookie</a>
@GetMapping("/test2")
public String test2(@CookieValue("name") Cookie cookie){
    return cookie.getName() + " is " + cookie.getValue();
}

点击a链接
请求得到 =>
name is Aomrsou

@RequestBody 获取请求体[POST请求]

  • String接收所有参数(会得到一坨键值对):
    <form action="/test3" method="post">
        <input type="text" name="name" value="Aomrsou">
        <input type="text" name="age" value="23">
        <input type="submit" value="提交">
    </form>
    
    @PostMapping("/test3")
    public String test3(@RequestBody String content){
        return content;
    }
    
    提交表单得到 =>
    name=Aomrsou&age=23
    
  • 对象接收所有参数(会自动匹配名字到变量上):
    @PostMapping("/test3")
    public String test3(@RequestBody User user){
        return user.toString();
    }
    
    public class User {
        private String name;
        private Integer age;
    }PostMan发送Content type为'application/json'的POST请求,然后拿对象去接收 =>
    User{name='Aomrsou', age=23}
    

@RequestAttribute 获取请求域中的值

@Controller
public class HiController {

    @GetMapping("/goto")
    public String go(HttpServletRequest request){
        request.setAttribute("msg", "成了");
        return "forward:/success"; // 转发到success, 所以还是同一次请求
    }

    @ResponseBody
    @GetMapping("/success")
    public String to(@RequestAttribute("msg") String msg,
                     HttpServletRequest request){
        return msg + " " + request.getAttribute("msg");
    }
}

http://localhost:8089/goto
请求得到 =>
成了 成了

@MatrixVariable 获取矩阵变量中的值
Spring中默认是关掉了矩阵变量功能,我们需要自己实现,给UrlPathHelper类中的RemoveSemicolonContent(移除分号后的内容)属性设置为false

// 写法1:实现WebMvcConfigurer接口,然后重写configurePathMatch方法
@Configuration(proxyBeanMethods = true)
public class MyConfig implements WebMvcConfigurer{
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}
// 写法2: 直接@Bean给Spring注册一个WebMvcConfigurer实例
@Configuration(proxyBeanMethods = true)
public class MyConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }
}
// 矩阵变量的内容必须写在路径{}内
@GetMapping("/test4/{path}")
public Maptest4(@MatrixVariable("name") String name,
                 @MatrixVariable("hobby") List<String> hobby,
                 @PathVariable("path") String path){
    Map<String, String> map = new HashMap<>();
    map.put("name", name);
    map.put("path", path);
    map.put("hobby", hobby.toString());
    return map;
}

http://localhost:8089/test4/whoami;name=Aomrsou;hobby=eat;hobby=sleep;hobby=play
请求得到 => 
{"path":"whoami","name":"Aomrsou","hobby":"[eat, sleep, play]"}
// 如果有多个同名的矩阵变量,还可以指定pathVar来进行区分
@GetMapping("/test5/{bossId}/{empId}")
public Map test5(@MatrixVariable(value = "age", pathVar = "bossId") String bossAge,
                 @MatrixVariable(value = "age", pathVar = "empId") String empAge){
    Map<String, String> map = new HashMap<>();
    map.put("bossAge", bossAge);
    map.put("empAge", empAge);
    return map;
}

http://localhost:8089/test5/1;age=10/2;age=20
请求得到 =>
{"bossAge":"10","empAge":"20"}
1.0.5 从web项目迁移成maven项目 1.0.6 增加菜单框架ext实现,类路径调整 1.0.7 增加http工具类,demo例子 1.0.8 socket工具类,权限组件,菜单组件,jdbc分页支持多种数据库,ant路径工具类,增加jquery easyUI 1.0.9 版本管理,服务根路径工具类,文件上传工具类 1.0.10 集成ueditor在线编辑器 1.0.11 地址联动 1.0.12 Excel工具类 Word工具类 Java NIO实现socket工具类 分布式session jdk升级到1.7 嵌入式redis服务(只支持linux) 1.0.13 修改默认的beanName生成策略,controller参数扩展 1.0.14 分布式session使用zookeeper 1.0.15 zookeeper工具类优化 增加工具类 1.0.16 页面html标志修改 httpclient中文支持 工具类增强(zip,reflect,thread) 1.0.17 ftp服务端和客户端工具类,配置文件maven和web项目路径统一 1.1.0 soapui工具类(web版本) properties等工具类 1.1.1 工具类数据校验 jsp自定义标签 Spring自定义注解 默认requestMapping 1.1.2 代码生成器 1.1.3 首页修改 dateformat.js 时间参数转换 SpringMVC配置文件集中 快递参数接口 1.1.4 des加解密字符串和文件 1.1.5 redis 加锁,redis升级成2.8.2 freemarker工具类 1.1.6 spring websocket 实现在线聊天 maven升级jdk1.8 jetty9.2.4 web升级jdk1.7 tomcat7 1.1.7(maven only) 包名修改 从此不再支持web版本,只支持maven版本 1.1.8 jquery 图片预览插件 图片滚动显示插件 1.1.9 jquery实现鼠标在按钮上显示窗口,离开窗口和按钮时消失 1.1.10 rabbitMQ集成 视频截图 图片缩略图旋转 集成Mybatis 使用数据库连接池druid dubbo使用
# demoWeb 一个基于SpringMVCweb框架 1.0.5 从web项目迁移成maven项目 1.0.6 增加菜单框架ext实现,类路径调整 1.0.7 增加http工具类,demo例子 1.0.8 socket工具类,权限组件,菜单组件,jdbc分页支持多种数据库,ant路径工具类,增加jquery easyUI 1.0.9 版本管理,服务根路径工具类,文件上传工具类 1.0.10 集成ueditor在线编辑器 1.0.11 地址联动 1.0.12 Excel工具类 Word工具类 Java NIO实现socket工具类 分布式session jdk升级到1.7 嵌入式redis服务(只支持linux) 1.0.13 修改默认的beanName生成策略,controller参数扩展 1.0.14 分布式session使用zookeeper 1.0.15 zookeeper工具类优化 增加工具类 1.0.16 页面html标志修改 httpclient中文支持 工具类增强(zip,reflect,thread) 1.0.17 ftp服务端和客户端工具类,配置文件maven和web项目路径统一 1.1.0 soapui工具类(web版本) properties等工具类 1.1.1 工具类数据校验 jsp自定义标签 Spring自定义注解 默认requestMapping 1.1.2 代码生成器 1.1.3 首页修改 dateformat.js 时间参数转换 SpringMVC配置文件集中 快递参数接口 1.1.4 des加解密字符串和文件 1.1.5 redis 加锁,redis升级成2.8.2 freemarker工具类 1.1.6 spring websocket 实现在线聊天 maven升级jdk1.8 jetty9.2.4 web升级jdk1.7 tomcat7 1.1.7(maven only) 包名修改 从此不再支持web版本,只支持maven版本 1.1.8 jquery 图片预览插件 图片滚动显示插件 1.1.9 jquery实现鼠标在按钮上显示窗口,离开窗口和按钮时消失 1.1.10 rabbitMQ集成 视频截图 图片缩略图旋转 集成Mybatis 使用数据库连接池druid dubbo使用 1.1.11 集成Spring Cache,FastJson Spring Cache增加redis缓存实现 Mybatis使用二级缓存,增加redis实现 增加reactJs 增加Mybatis插件pageHelper,Mapper 1.1.12 使用draft富文本编辑器 增加ant design 代码生成器功能增强
# demoWeb 一个基于SpringMVCweb框架 1.0.5 从web项目迁移成maven项目 1.0.6 增加菜单框架ext实现,类路径调整 1.0.7 增加http工具类,demo例子 1.0.8 socket工具类,权限组件,菜单组件,jdbc分页支持多种数据库,ant路径工具类,增加jquery easyUI 1.0.9 版本管理,服务根路径工具类,文件上传工具类 1.0.10 集成ueditor在线编辑器 1.0.11 地址联动 1.0.12 Excel工具类 Word工具类 Java NIO实现socket工具类 分布式session jdk升级到1.7 嵌入式redis服务(只支持linux) 1.0.13 修改默认的beanName生成策略,controller参数扩展 1.0.14 分布式session使用zookeeper 1.0.15 zookeeper工具类优化 增加工具类 1.0.16 页面html标志修改 httpclient中文支持 工具类增强(zip,reflect,thread) 1.0.17 ftp服务端和客户端工具类,配置文件maven和web项目路径统一 1.1.0 soapui工具类(web版本) properties等工具类 1.1.1 工具类数据校验 jsp自定义标签 Spring自定义注解 默认requestMapping 1.1.2 代码生成器 1.1.3 首页修改 dateformat.js 时间参数转换 SpringMVC配置文件集中 快递参数接口 1.1.4 des加解密字符串和文件 1.1.5 redis 加锁,redis升级成2.8.2 freemarker工具类 1.1.6 spring websocket 实现在线聊天 maven升级jdk1.8 jetty9.2.4 web升级jdk1.7 tomcat7 1.1.7(maven only) 包名修改 从此不再支持web版本,只支持maven版本 1.1.8 jquery 图片预览插件 图片滚动显示插件 1.1.9 jquery实现鼠标在按钮上显示窗口,离开窗口和按钮时消失 1.1.10 rabbitMQ集成 视频截图 图片缩略图旋转 集成Mybatis 使用数据库连接池druid dubbo使用 1.1.11 集成Spring Cache,FastJson Spring Cache增加redis缓存实现 Mybatis使用二级缓存,增加redis实现 增加reactJs 增加Mybatis插件pageHelper,Mapper 1.1.12 使用draft富文本编辑器 增加ant design 代码生成器功能增强
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值