SpringBoot 随记

服务压力向前放

1 浏览器缓存 2 nginx缓存+lua缓存  3 服务本地缓存 4 redis缓存 5 数据库 

注意带宽控制:500k的文件瞬时200的请求就能填满100M的带宽。可以开启GZIP压缩部分静态文件。

推荐张开涛的《亿级流量网站架构核心技术》

前后端分离部署

视频:video/BV1bJ41157W7

方法1:build后的前端项目拷贝到boot项目resources/static目录,项目自动映射这个目录。安全框架不要拦截静态路径。

方法2:build后的前端项目,由Nginx代理。

配置文件

注意结尾不要有多余的空字符或空格,注意:和= 不要错

通过配置文件配置Servlet容器

配置Server属性: https://www.cnblogs.com/softidea/p/6068128.html

server.tomcat.accept-count=10000
server.tomcat.max-connections=10000
server.tomcat.max-threads=500
server.tomcat.min-spare-threads=100
server.tomcat.uri-encoding=UTF-8

JVM

springboot2默认堆大小是2G。

Gzip压缩静态文件

优化网络带宽,可以配置针对哪些格式压缩

server.compression.enabled=true

统一JSON返回格式

统一的返回格式更容易做前后端交互。包含:是否成功+返回码+信息+返回体value{}

Boolean success;
String code;
String msg;
HashMap value;  //HashMap可以保存多个对象,且转换成JSON后,每个对象都以key最为name

Json时间与数据库时间相差8小时

未出现不一致则不需要设置

//设置jackson时区,其根据数据库时区将数据库得到的时间转化为GMT+8时区
spring.jackson.time-zone=GMT+8
//jackson时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

Jackson

jackson 基本用法_jackson ignoreunknown-CSDN博客

通过URL暴露本机静态资源(图片/js等)

图片上传的文本某个文件路径下保存,外界通过URL访问此图品。推荐用NGINX做静态文件代理,减轻后端服务的带宽压力。

测试Chrome通过URL访问图片、JS、TXT、PDF等格式会直接在浏览器显示。访问doc、xls等格式触发下载。

# 静态资源url访问的格式
spring.mvc.static-path-pattern= /static/**
# 静态资源真实存储路径,多个以逗号分隔。可以直接通过spring.mvc.static-path-pattern配置的URL映射路径访问。
# 例如:http://localhost:8080/static/xxx.png
spring.resources.static-locations=classpath:/static/,file:E://static/

性能优化配置

#开启gzip
server.compression.enabled=true
#仅会压缩 1mb=1048576字节 以上的内容
server.compression.min-response-size=1048576
server.compression.mime-types=image/jpeg,image/jpg,text/html
# 启用静态资源缓存(用户浏览器本地缓存静态资源css/js)
spring.resources.chain.cache=true
# 资源缓存时间,单位秒
spring.resources.cache.period=60480s
# 开启MD5版本控制策略
spring.resources.chain.strategy.content.enabled=true
# 指定要应用的版本的路径,多个以逗号分隔,默认为:[/**]
spring.resources.chain.strategy.content.paths=/**

非常简单的springboot上传静态文件保存到指定目录,由Nginx代理静态资源的例子

例子:https://www.cnblogs.com/flypig666/p/11747548.html

若需要springboot将上传文件保存到远程目录下,请将远程目录开启FTP服务。大量文件管理推荐使用FASTDFS、CDN或用云OSS等。

判断文件相同

上传后保存文件的原文件名、服务器分配名、真实路径、URL、MD5、SHA1、大小等信息

1、通过MD5、SHA1对比两个文件。

     单独的MD5存在很小概率的错判,尤其是大文件。可以结合MD5、SHA1、大小因素等一起对比。

2、统一管理业务对文件的引用URL

Session

是一个会话级别的缓存。无论登录与否,只要服务与浏览器发生链接就会创建session,浏览器拿到sessionid存入cookie。在单体应用网站中session可以做用户(浏览器)的会话级别缓存,例如:缓存验证码、用户信息等

Cookie、Session丢失

原因1:Cookie-Domain与请求服务器的域名不统一。只要二级域名相同服务器端就可以获得Cookie。比如:a.su.com 和 b.su.com

例1:如果有Nginx做反向代理,后端生成的Cookie要配置Domain和Path为Nginx的,保证请求发到Nginx时是携带Cookie的。

@RequestParam与@RequestBody

@RequestParam可以获得url、form中的参数,一般不支持复杂的对象。例如: localhost:18083/sendparam?name=123   ==>@RequestParam("name")

@RequestBody用于接收请求体,多数情况是json格式。可以解析复杂的json对象和泛型。底层解析器默认使用Jackson的ObjectMapper。两个注解可以混合使用,同时解析url参数和请求体。

    @PostMapping(value = "/a")
    @ResponseBody
    public void getObjectParam( @RequestParam("id") String id, @RequestBody  UserInfo userInfo){}

    @PostMapping(value = "/b")
    @ResponseBody
    public void getUserInfos(  @RequestBody  List<UserInfo> userInfos){}


public class UserInfo implements Serializable {
    private Integer id;
    private String name;
    private List<UserInfo> users;

    get/set.....
}

mvn打包跳过test测试环节

spring-boot-maven-plugin插件已经集成了maven-surefire-plugin插件,只需要在pom.xml里增加

<properties>
    <skipTests>true</skipTests>
</properties>

Post提交数据常用编码方式

Content-Type:application/x-www-form-urlencoded、application/json、multipart/form-data、text/xml

post提交数据的四种编码方式 - 简书

URL-callback

1、发送请求时带上redirect地址,这中方式可以告诉对方服务器,当执行完业务后将用户浏览器重定向到指定页面。

http://....../toredirect?redirect=url地址&携带参数=x

2、发送请求时带上callback地址,这中方式可以告诉对方服务器,当执行完业务后回调指定url地址,通过msgid双方建立关系。

可以实现简单的异步回调功能。

http://....../tocallback?msgid=1&callback=url地址&回调时携带参数=x

3、携带参数内容最好是加密/加签名的

前端轮序 或 WebSocket 实现页面动态感知效果

有时某些操作需要异步通知前端页面。最简单的实现就是前端页面通过ajax定时轮序请求后端restapi获取结果。

比如:1)手机扫二维码登录(二维码中带有唯一ID,手机扫描二维码后携带ID将手机用户信息保存在redis中,前端页面js定时轮训请求查找redis中ID对应的用户信息)。2)订单付款时,用户同时开启多个二维码支付页面,为尽可能避免多个页面都可以支付,支付页面轮序后端restapi查看当前订单状态,发现已支付则当前页二维码图片隐藏。

BeanFactory-API

BeanFactory—接口篇 - 简书

Bean对象配置方式

@Component、@Service...等、JavaConfig@Bean、XML:<bean></bean>、Api:BeanDefinitionBuilder

Bean依赖查找

常用API

getbean(String name)、getbean(Class)、getbean(String name,Class)、

getbean(Class,Object....)覆盖默认参数、getBeanProvider(Class) 延迟查找、

getBeanNamesForType(Class):List<String>、getBeanNamesForAnnotation(Class):List<String>

更底层API

44 | 集合类型依赖查找:如何查找已知类型多个Bean集合?--极客时间

依赖查找常见异常

Bean实例化源码过程

Spring IOC容器源码分析(XML)_configurableapplicationcontext constructor must no-CSDN博客

@ModelAttribute三种使用场景

更多详细用法:https://www.cnblogs.com/ilinuxer/p/6444930.html

1 、Controller中的方法上   2 、ControllerAdvice中的方法上  3、@RequestMapping的方法参数上

1、2 都是在执行@RequestMapping前,将@ModelAttribute方法返回值注入到model中。3是从model中取值注入到参数中

@Controller  
    public class HelloWorldController {  
  
        /**
          在@RequestMapping执行前注入user到model
        */
        @ModelAttribute("user")  
        public User addAccount() {  
           return new User("jz","123");  
        }  
  
        @RequestMapping(value = "/helloWorld")  
        public String helloWorld(@ModelAttribute("user") User user) {  
           //从model中取出user,此user为addAccount中添加的
           user.setUserName("jizhou");  
           return "helloWorld";  
        }  
    }

吞吐量、并发量优化

Springboot吞吐量优化解决方案_springboot吞吐量计算-CSDN博客

吞吐量的提高是指整个系统可以响应更多的请求,不一定是单次请求响应提高。有时候需要牺牲单次响应换取吞吐量。

例如:Nginx反向代理,增加了单次请求的复杂度,但是为后台应用提供了水平拓展能力(集群)。提高了整个系统的用户承载量。

例如:NIO模型通过异步处理提高吞吐量,避免的大量请求无法连接到服务器,单位时间服务器能处理更多的请求了。但是异步执行会产生线程切换,其对于单次请求来说开销变大、响应变慢。一个宗旨就是在单次响应能接受的情况下优化吞吐量。比如:原本请求响应500ms,现在请求响应800ms,对于用户的体验影响并不大。

以下方案在下文中有详细解释。

1 tomcat换为undertow。

2 增加容器线程池的线程总数和初始数、容器链接数。减少请求排队时间。加速回收socket减少keepalive时间。

3 缓存数据、html文本。空间换时间,本地缓存+外部缓存减少重复作业。

4 启用异步处理请求的@Controller,提高吞吐量。

5 限流。虽然会拒绝部分请求,但是会加快请求处理速度,避免大量请求排队或超时。

   也避免了因为队列过长,造成平均响应速度大幅提升、内存和CPU爆满。保护服务高可用的同时,提升用户体验。

6 优化JVM,且主机有足够的性能。

7 集群+分布式部署,拓展服务消费能力。

8 动静分离。压力分离,带宽分离。

9 服务压力向前放 (1 浏览器缓存 2 Nginx缓存  3 服务本地缓存 4 Redis缓存  5 数据库 )

10 同步转异步、串行转并行。

thymeleaf手工渲染HTML模板,生成HTML文本

本质上是浏览器与服务器建立socket短连接,通过http协议请求服务器端资源。服务器端通过文本形式把<HTML>响应给浏览器,浏览器再请求<HTML>中引用的静态资源, 展示<HTML>样式。

thymeleafViewResolver手工渲染出html的文本,返回给浏览器,浏览器会解析并展示html页面,加载静态资源css\js\img。

固定的HTML文本(String)可以放入缓存中,下次同样的请求直接查缓存,节省了数据查询、model装配、渲染html的时间。

import org.thymeleaf.context.WebContext;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;

@Autowired
    ThymeleafViewResolver thymeleafViewResolver;

@RequestMapping(value = "/gethtml" ,produces="text/html")
    @ResponseBody
    public String getHtml(HttpServletRequest request, HttpServletResponse response, Model model){

        model.addAttribute("user","username");
        //list
        model.addAttribute("items",getItems());

        //准备手动渲染html(springboot2以前用SpringWebContext)
        WebContext ctx = new WebContext(request,response,
                request.getServletContext(),request.getLocale(), model.asMap() );
      
        //渲染生成html文本。 第一个参数为页面模板路径。/templates/目录下
        String html = thymeleafViewResolver.getTemplateEngine().process("htmlfrom", ctx);

        return html;
    }

缓存版

import org.thymeleaf.context.WebContext;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;

@Autowired
    ThymeleafViewResolver thymeleafViewResolver;
/**
     * 可以用redis替代
     */
    private final ConcurrentHashMap<String,String> localCache= new ConcurrentHashMap<>(6);

@RequestMapping(value = "/gethtmlfromcache" ,produces="text/html")
    @ResponseBody
    public String getHtmlFomeCache(HttpServletRequest request, HttpServletResponse response, Model model){

        //查找页面缓存
        String html = localCache.get(request.getRequestURI());
        if (html!=null){
            return html;
        }

        LOGGER.info("com.example.dubboconsumer.web.SpringControllter.getHtmlFomeCache 手动渲染html");

        model.addAttribute("user","username");
        model.addAttribute("items",getItems());

        //准备手动渲染html(springboot2以前用SpringWebContext)
        final WebContext ctx = new WebContext(request,response,
                request.getServletContext(),request.getLocale(), model.asMap() );

        //存入缓存
        html =localCache.computeIfAbsent(request.getRequestURI(),
                    (key)->{
                        //jdk8的computeIfAbsent,此处方法内不能修改map,否则会出现死循环bug
                        //渲染生成html文本。 第一个参数为页面模板路径
                        String html1 = thymeleafViewResolver.getTemplateEngine().process("htmlfromcache", ctx);
                        if( !"".equals(html1)){
                            return  html1;
                        }
                        else {
                            return null;
                        }
                    } );

        return html;
    }

thymeleaf生成静态html

https://jingyan.baidu.com/article/cd4c29792d02c9756f6e607f.html

@Controller 异步处理请求,优化吞吐量

同步请求

异步请求 

servlet3增加了异步处理特性。Spring中提供了Callable、WebAsyncTask、DeferredResult 3种方式支持了异步请求处理的功能。异步请求处理中tomcat线程不做业务处理,可以快速被释放去处理新的请求。传统阻塞servlet框架,当tomcat线程满载时,后续请求会被阻塞排队等待,队列越长响应时间越长。在同样线程数情况下有更高的吞吐量,并发量增大时响应时间更加稳定。

其与NIO、Netty Reactor模型、WebFlux等思路接近。通过异步处理业务,快速释放处理外界链接的线程,从而提高吞吐量、并发性能。但是由于处理更加复杂所以单次响应时间会变久。

例如:

场景:10000个并发请求,200个tomcat线程,单次业务平均用时1s

传统阻塞框架中,200个tomcat线程会打满,剩余请求排队等待。排在最后的请求可能要等待很久才能有空闲的tomcat线程来处理,单次响应时间飙升,所以统计平均响应时间会很高。

异步请求处理模式中,tomcat线程不处理业务很快就能返回处理新的请求,减少了请求阻塞排队等待tomcat线程的时间,单次响应时间会更加平稳,平均响应时间也会更少。

优点:适合IO/阻塞密集型业务

1、快速释放tomcat线程,提高服务的吞吐量、并发性能也随之提高。

      将那些Tomcat线程经常被打满的传统阻塞业务/服务,改造成异步模式效果最为明显。

缺点:不适合CPU密集型业务

1、增加代Controller复杂度,单次请求的用时会少许增加。

2、业务代码异步执行,存在线程切换。ThreadLocal线程等绑定信息会失效

代码用例

Callable、WebAsyncTask、DeferredResult。不要用默认线程池,请单独配置线程池。

[springboot] 异步开发之异步请求_异步请求超时里面的时间是什么单位-CSDN博客

SpringBoot实践之---使用异步请求,提高系统的吞吐量_接口改成异步没有提升吞吐量-CSDN博客

配置优化与测试

spring异步请求action以及默认配置坑爹的性能以及对应性能调优_springmvc mvc:async-support-CSDN博客

AOP失效

AOP实现原理实在为bean创建代理对象,所以要执行bean代理对象的方法,AOP才会生效。

@Transactional失效:Spring 事务_spring反射会有事务问题吗-CSDN博客

WebMvcConfigurerAdapter、WebMvcConfigurationSupport 与WebMvcConfigurer接口

都可以作为非XML配置类。WebMvcConfigurerAdapter(2.0以废弃)、WebMvcConfigurationSupport都实现了WebMvcConfigurer接口。

WebMvcConfigurationSupport和@EnableWebMvc同时使用时会造成配置失效BUG。因为@EnableWebMvc也是注入一个WebMvcConfigurationSupport配置类。

如果使用继承WebMvcConfigurationSupport,DelegatingWebMvcConfiguration,或者使用@EnableWebMvc,需要注意其会覆盖application.properties中关于WebMvcAutoConfiguration的设置。

推荐自定义实现WebMvcConfigurer接口作为配置类,其不会覆盖application.properties配置,可以良好的共存。WebMvcConfigurer接口中都是default方法,所以只需要实现需要的即可。WebMvcConfigurer API说明

@Configuration
public class MyConfig implements WebMvcConfigurer {

2.1 addInterceptors:拦截器

2.2 addViewControllers:页面跳转

2.3 addResourceHandlers:静态资源

2.4 configureDefaultServletHandling:默认静态资源处理器

2.5 configureViewResolvers:视图解析器

2.6 configureContentNegotiation:配置内容裁决的一些参数

2.7 addCorsMappings:跨域

2.8 configureMessageConverters:信息转换器

}

注册Servlet、Filter、Listener

原贴:18.Spring Boot 注册Servlet、Filter、Listener_springboo3 注册引入jar包的listener-CSDN博客

在Servlet 3.0之前我们都是使用web.xml进行配置,需要增加Servlet、Filter或者Listener都是在web.xml增加相应的配置即可。这里我们使用的是使用Java配置来注册Servlet、Filter、Listener。

进入的顺序是Filter-->Interceptor-->ControllerAdvice-->Aspect-->Controller(后进先出)

注册Servlet

(1)使用ServletRegistrationBean注册

  使用ServletRegistrationBean注册只需要在@Configuration类中加入即可,例如以下代码:

@Bean
	public ServletRegistrationBean myServlet() {
		ServletRegistrationBean myServlet = new ServletRegistrationBean();
		myServlet.addUrlMappings("/servlet");
		myServlet.setServlet(new MyServlet());
		return myServlet;
	}

注册成功后,启动时控制台可以看到自定义servlet的信息

(2)使用@WebServlet

 使用@WebServlet注册,需要在Servlet类上使用该注解即可,但是需要在@Configuration类中使用Spring Boot提供的注解@ServletComponentScan扫描注册相应的Servlet。

注册Filter

  ( 0 ) 推荐继承Spring提供的抽象类OncePerRequestFilter,他能够确保在任何Servlet版本的容器中,每一次请求只通过一次filter,而不需要重复执行。此方式是为了兼容不同的web container。如内部的forward不会再多执行一次

简单用例
@Component
public class RequestFilter extends OncePerRequestFilter {

  @Override  
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)  
            throws ServletException, IOException {  
       
            // 包装重置request  
            request = new RequestCachingRequestWrapper(request);  
  
            // 自定义过滤前执行的方法  
            beforeRequest(request, getBeforeMessage(request));  

            try {  
                // 执行过滤  
                filterChain.doFilter(request, response);  
            }  
            finally {  
                // 自定义过滤后执行的方法  
                afterRequest(request, getAfterMessage(request));  
            }  
    }  

.......

}

(1)使用FilterRegistrationBean注册

  使用FilterRegistrationBean注册Filter,只需要在@Configuration类中加入即可,例如以下代码:

@Value("${xss.urlPatterns}")
    private String urlPatterns;

@Bean
	public FilterRegistrationBean myFilter() {
		FilterRegistrationBean myFilter = new FilterRegistrationBean();
		myFilter.addUrlPatterns("/*");
		myFilter.setFilter(new MyFilter());
		return myFilter;
	}

 @Bean
    public FilterRegistrationBean xssFilterRegistration()
    {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setDispatcherTypes(DispatcherType.REQUEST);
        registration.setFilter(new XssFilter());
        registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
        registration.setName("xssFilter");
        registration.setOrder(Integer.MAX_VALUE);
        Map<String, String> initParameters = new HashMap<String, String>();
        initParameters.put("excludes", excludes);//相当于web.xml配置的<init-param>
        initParameters.put("enabled", enabled);
        registration.setInitParameters(initParameters);
        return registration;
    }

(2)使用@WebFilter

  使用@WebFilter注册,需要在Filter类上使用该注解即可,但是需要在@Configuration类中使用Spring Boot提供的注解@ServletComponentScan扫描注册相应的Filter。

 ( 3 ) Filter实现  implements Filter

    public List<String> excludes = new ArrayList<>();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException
    {
        String tempExcludes = filterConfig.getInitParameter("excludes");
        String tempEnabled = filterConfig.getInitParameter("enabled");
        if (StringUtils.isNotEmpty(tempExcludes))
        {
            String[] url = tempExcludes.split(",");
            for (int i = 0; url != null && i < url.length; i++)
            {
                excludes.add(url[i]);
            }
        }
        if (StringUtils.isNotEmpty(tempEnabled))
        {
            enabled = Boolean.valueOf(tempEnabled);
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
    {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if (handleExcludeURL(req, resp)) //自定义方法 排除不需要过滤的
        {
            chain.doFilter(request, response); 
            return;  //这个return一定要有 否则下层过滤完成后,返回来会继续执行
        }
        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
        chain.doFilter(xssRequest, response);
    }

FilterConfig作用也是获取Filter的相关配置信息:

    1.初始化参数的获取

            String getInitparameter(String name);

            Enumeration EnumerngetInitParameterNames();

    2.Filter的名称获取

            getFilterName();

    3.ServletContext对象的获取

            getServletContext();
 

常用过滤器:

1、OncePerRequestFilter :保证任何版本servlet框架,只执行一次过滤

2、HiddenHttpMethodFilter:浏览器form表单只支持GET与POST请求。此过滤器将这些请求转换为标准的http方法,使得支持GET、POST、PUT与DELETE请求

Spring MVC过滤器-HiddenHttpMethodFilter-CSDN博客

注册Listener

(1)使用ServletListenerRegistrationBean注册

  使用ServletListenerRegistrationBean注册Listener只需要在@Configuration类中加入即可,例如以下代码:

@Bean
	public ServletListenerRegistrationBean<MyListener> myServletListener() {
		ServletListenerRegistrationBean<MyListener> myListener = new ServletListenerRegistrationBean<MyListener>();
		myListener.setListener(new MyListener());
		return myListener;
	}

(2)使用@WebListener

  使用@WebListener注册,需要在Filter类上使用该注解即可,但是需要在@Configuration类中使用Spring Boot提供的注解@ServletComponentScan扫描注册相应的Listener。

注册DispatcherServlet

SpringBoot中通过DispatcherServletAutoConfiguration配置类注册DispatcherServlet

Filter、拦截器、AOP

按顺序依次执行

过滤器Filter:作用在DispacherServlet之前,过滤/包装request对象。

拦截器:作用在进入DispacherServlet调用目标Controllter方法之前,目的拦截Servlet中Http请求的整个生命周期过程。

               preHandle通过HandlerMethod取得Controllter中目标Method。但此时spring未完成数据绑定,所以其无法获得目标Method的参数值。只能通过request对象获取请求信息。

AOP:是Bean的代理功能,是在Spring创建Bean时,为Bean生成代理对象,最终注册到Spring中。

           通过JoinPoint取得目标Method和其参数值。

HttpServletRequestWrapper 包装不可变request对象

继承HttpServletRequestWrappe包装类。为不可变的HttpServletRequest增加功能。在Filter中包装Request,在Filter向下链式调用时将Wrapper对象的引用传入下一层 chain.doFilter(requestWrapper, response)。

例如:

防止xss攻击:利用HttpServletRequestWrapper包装Request的取值方法,对Request中的值作二次处理。变向修改原本不可变Request。

解决Request中getInputStream只能读取一次:Wrapper包装getInputStream方法,第一次去的流后缓存信息,之后都取缓存。

ShallowEtagHeaderFilter实现弱ETag

弱ETag · Spring MVC 4.2.4 RELEASE 中文文档

过滤器会将响应内容缓存起来,然后以此生成一个MD5哈希值,并把这个值作为ETag头的值写回响应中。下一次客户端再次请求这个同样的资源时,它会将这个ETag的值写到If-None-Match头中。本次响应返回时,将本次响应数据计算ETag比较If-None-Match,如果是相同的,那么服务器会返回一个304。节省响应带宽。

此功能不会减少服务器的响应计算,只是为了节省响应带宽。

创建Bean的方式

36 | 实例化Spring Bean:Bean实例化的姿势有多少种?--极客时间

1) 调用无参数构造器 
2) 带参数构造器 

<!-- 2. 带参数构造器 -->
    <bean id="user" class="com.nwpu.geeker..User">
        <constructor-arg index="0" type="int" value="100"></constructor-arg>
        <constructor-arg index="1" type="java.lang.String" value="Jack"></constructor-arg>
    </bean>

3)FactoryBean

4) 工厂方法

注意静态/非静态工厂的使用区别

// 工厂,创建对象
public class ObjectFactory {
    // 实例方法创建对象
    public User getInstance() {
        return new User(100,"工厂:调用实例方法");
    }
    // 静态方法创建对象
    public static User getStaticInstance() {
        return new User(101,"工厂:调用静态方法");
    }
}
<!-- 先创建工厂 -->
<bean id="factory" class="com.nwpu.geeker.ObjectFactory"></bean>
<!-- 在创建user对象,用factory方的实例方法 -->
<bean id="user" factory-bean="factory" factory-method="getInstance"></bean>

<bean id="user" class="com.nwpu.geeker.ObjectFactory" factory-method="getStaticInstance"></bean>

在非Spring组件中获取SpringBean

方法一:

通过ApplicationContextAware 接口在初始化时获取applicationContext赋值到静态变量

非Spring组件中只需要调用静态方法SpringIocUtil.getBean(name) 即可。


@Component
public class SpringIocUtil implements ApplicationContextAware {

    /**
     * 当前IOC容器
     */
    private static ApplicationContext applicationContext;

    /**
     * 设置当前上下文环境,此方法由spring自动装配
     */
    @Override
    public void setApplicationContext(ApplicationContext arg0)
            throws BeansException {
        applicationContext = arg0;
    }

    /**
     * 从当前IOC获取bean
     * @param id bean的id
     * @return
     */
    public static Object getBean(String id) {
        return  applicationContext.getBean(id);

    }

}

方法二 

非SpringBean的实现类继承SpringBeanAutowiringSupport,则可以直接使用@Autowired

https://www.cnblogs.com/Johness/archive/2012/12/25/2833010.html

动态注册/销毁/删除Bean

1.Bean实现BeanFactoryAware、ApplicationContextAware接口的相应方法取得Spring容器

2.通过@Autowired,取得BeanFactory+ApplicationContext    

3 注意:手动注册的new Bean(),@Autowired,AOP等等都失效。因为registerSingleton()直接将对象注册到容器缓存中,所以getbean()时直接命中缓存,没有装配过程。

所以推荐注册BeanDefinition而不是直接注册bean对象。

DI需要手动将需要的成员变量Bean引用赋值给这个手动注册的Bean中。否则会出现NPE。

AOP是getBean()时BeanPostProcess在其初始化后用代理对象替换原有的bean实现。

  BeanFactory 删除/注册Bean

方式1(推荐): 通过Spring提供的BeanDefinitionBuilder构建BeanDefinition。

在BF.getBean()时创建bean时,BeanDefinition执行完整的bean实例化+初始化流程。@Autowired,AOP等功能都能实现,因为这两个功能是在BF.getbean(name)时通过BeanPostProcesser实现的。推荐查看BeanDefinitionBuilder源码API,包含了所有<bean>的xml内容的配置API。

BeanDefinitionBuilder的API样例:摘自于Mybatis - MapperScannerRegistrar.class源码

//实例化GenericBeanDefinition,类型为MapperScannerConfigurer
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
//构造器参数(参数顺序与构造器相同)
builder.addConstructorArgValue(Object value);
//构造器参数引用bean(参数顺序与构造器相同)
builder.addConstructorArgReference(String beanName);
//为BeanDefinition增加PropertyValue,name为MapperScannerConfigurer中的属性名,Object为bean初始化依赖注入的值。
//BeanFactory在Bean初始化时,依赖注入PropertyValue中属性。
//PropertyValue就是spring配置中<bean id="" class=""><property name="" value="">。
builder.addPropertyValue(String name, Object v);
//给类中userService变量注入名为userService的bean。<property name="userService" ref="userService">
builder.addPropertyReference("userService", "userService");
//BF注册BD
beanFactory.registerBeanDefinition(beanName, builder.getBeanDefinition());

使用样例: 

//注册BeanDefinition
public String registerBean() {
    //将applicationContext转换为ConfigurableApplicationContext
    ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) SpringContextUtil.getApplicationContext();
 
    // 获取bean工厂并转换为DefaultListableBeanFactory
    DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
 
    // 通过BeanDefinitionBuilder创建bean定义,也可以自己实例化BeanDefinition
    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserController.class);
        
    // 设置属性userService,此属性引用已经定义的bean:userService,这里userService已经被spring容器管理了.
    //给类中userService变量注入名为userService的bean
    beanDefinitionBuilder.addPropertyReference("userService", "userService");
 
    //BF注册BeanDefinition
    defaultListableBeanFactory.registerBeanDefinition("userController", beanDefinitionBuilder.getRawBeanDefinition());
 
 
    UserController userController = (UserController) defaultListableBeanFactory .getBean("userController");
 
    return userController.toAction("动态注册生成调用");
  
}

//定义一个没有被Spring管理的Controller
public class UserController implements InitializingBean{
 
    private UserService userService;
/*
    @AutoWired会在Bean初始化阶段通过BeanPostProcess扫描UserController类执行注入。
    BeanDefinition中不用配置。
*/
    @AutoWired
    private UserMapper userMapper ;
 
    public UserService getUserService() {
        return userService;
    }
 
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("我是动态注册的你,不是容器启动的时候注册的你");
    }
 
    public String toAction(String content){
        return "-->" +  userService.doService(content);
    }
 
}

方式2:直接注册Bean对象

@Autowired
private ApplicationContext applicationContext;

@Autowired
    private RedisTemplate<Object, Object> redisTemplate;


//BeanFactory
DefaultListableBeanFactory defaultListableBeanFactory = 
   (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();


//注册bean   new实例化Spring不负责@Autowired、AOP,将需要的bean引用手动传入
defaultListableBeanFactory.registerSingleton("mybean", new BeanObject(redisTemplate));

//注册bean  Spring创建Bean  负责@Autowired+AOP
BeanObject a=defaultListableBeanFactory.createBean(BeanObject.class); //Spring创建Bean
a.setBeanName("mybean"); //因为是手动创建此时还没有BeanName,BeanNameAware注入全类名。需要手动指定BeanName
defaultListableBeanFactory.registerSingleton("mybean",a );

//销毁
defaultListableBeanFactory.destroySingleton("mybean");

//删除beanDefinition
defaultListableBeanFactory.removeBeanDefinition("mybean");

方式3:手动实例化BeanDefinition

public class TestServiceImpl implements ImportBeanDefinitionRegistrar {
@Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata,         
        BeanDefinitionRegistry beanDefinitionRegistry) {
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestServiceImpl.class);
        beanDefinitionRegistry.registerBeanDefinition("beanname",rootBeanDefinition);
    }
}

替换BEAN
@Test
public void testBeanFactory() {
    DefaultListableBeanFactory defaultListableBeanFactory = 
          (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
    //注册
    defaultListableBeanFactory.registerSingleton("mybean","123");
    System.out.println(applicationContext.getBean("mybean"));
    //销毁(不注销抛出已存在异常)
    defaultListableBeanFactory.destroySingleton("mybean");
    //注册
    defaultListableBeanFactory.registerSingleton("mybean","456");
    System.out.println(applicationContext.getBean("mybean"));

}

扫描xml配置文件,转化为BeanDefinition

通过XmlBeanDefinitionReader实现

Spring 5.0源码解读:Bean XML配置文件解析类XmlBeanDefinitionReader-CSDN博客

扫描指定路径包Packages,将.java文件转化为BeanDefinition

创建自定义扫描器继承Spring的ClassPathBeanDefinitionScanner类。

其为子类提供了Set<BeanDefinitionHolder> doScan(String... basePackages)方法,为包下每一个类/接口创建最初始的BeanDefinition对象,包含beanid、class等,返回Set<BeanDefinitionHolder> 。可以遍历修改BeanDefinition的内容,进行条件判断等操作。然后注册到BeanFactory中。

MyBatis、Dubbo、Fegin等第三方功能,就是通过此方法将开发人员自定义的接口都注册成BeanDefinition(FactoryBean)的。通过FactoryBean生成接口类型的动态代理,接口执行方法时通过动态代理拿到Method + arg[]。还可以为扫描器配置自定义Filters过滤掉部分无效类。

借助ImportBeanDefinitionRegistrar接口实现bean的动态注入,类似@Component

借助ImportBeanDefinitionRegistrar接口实现bean的动态注入 - 简书

@EnableXXX -> @Import(ImportBeanDefinitionRegistrar) 拿到容器Registrar ->继承ClassPathBeanDefinitionScanner->扫描包获得Set<BeanDefinition>

现实案例:

1、org.springframework.cloud.openfeign.FeignClientsRegistrar

2、org.mybatis.spring.annotation.MapperScannerRegistrar

spring-boot-starter实现

spring.factories指定自己的Spring配置类【org.springframework.boot.autoconfigure.EnableAutoConfiguration=自定义@Configuration配置类】;几乎所有starter的配置入口都在spring.factories文件中。

SpringBoot启动时会自动搜索JAR包中src/main/resources/META-INF/spring.factories文件;
根据spring.factories文件EnableAutoConfiguration下配置的 @Configuration配置类 加载Bean ;
通过 自定义Spring配置类,加载满足条件(@ConditionalOnXxx)的@Bean到Spring IOC容器中;
使用者可以直接使用自动加载到IOC的bean。

实例:/p/7818700.html

SpringMvc环境的配置入口在web.xml

Spring IOC生命周期

Spring 生命周期_spring生命周期refresh abstractautowirecapablebeanfacto-CSDN博客

IOC环境构建完成后,项目启动完成前,进行业务逻辑

SpringBoot 启动加载类 ApplicationRunner 和 CommandLineRunner_applicationrunner 注入bean-CSDN博客

@Autowired可以DI自己

Http编码配置

spring mvc项目中需要在web.xml添加CharacterEncodingFilter过滤器

因为springboot启动时默认有CharacterEncodingFilter过滤器,只要在配置文件中为过滤器设置属性参数即可修改默认过滤属性,

所以springboot中只需要在配置文件中添加以下配置:

spring.http.encoding.charset=UTF-8 

spring.http.encoding.enabled=true 

spring.http.encoding.force=true 

SpringBoot 编译jar 右键项目run as选择maven instell,然后报错

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:compile (default-compile) on project jcseg-core: Compilation failure
[ERROR] No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?

解决方法: 

a Window → Preferences → 对话框中的左侧选择Java → Installed JREs → 对话框右侧点击 Add 按钮 → Standard VM → next → JRE home 选择 JDK 的安装路径 → Finish → 选中 jdk 的复选框 → 点击 OK 按钮

(一定要勾选与项目匹配JDK,而不能选择JRE。选择JRE也会报错)

参考:【062】解决Eclipse和Maven出[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin错误-CSDN博客

b 将项目BuildPath指向对应的jdk版本

参考:关于Maven项目build时出现No compiler is provided in this environment的处理_no compiler is provide in this-CSDN博客

使用springboot 链接sqlserver2005报SSL异常,异常内容如下

com.microsoft.sqlserver.jdbc.SQLServerException: The driver could not establish a secure connection to SQL Server by using Secure Sockets Layer (SSL) encryption. Error: "SQL Server did not return a response. The connection has been closed. ClientConnectionId:335af645-58ea-40f9-9d63-a282531142a3".
...
Caused by: java.io.IOException: SQL Server did not return a response. The connection has been closed. ClientConnectionId:335af645-58ea-40f9-9d63-a282531142a3
	

使用的依赖版本:

spring boot 2.0.3,JDK1.8

经测试后发现,是JDK版本问题:

使用JDK 8u171,JDK 8u181会发生异常

使用JDK 8u65,JDK 8u111就可以正常链接数据库

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
			<groupId>com.microsoft.sqlserver</groupId>
			<artifactId>mssql-jdbc</artifactId>
			<scope>runtime</scope>
</dependency>
<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.9</version>
</dependency>

启动异常

1 java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder.<init>([Ljava/lang/Object;)V

maven包之间有依赖冲突,实测boot2.1.3 与 dubbo-zipkin-spring-starter 1.0.2 会引发冲突

2 springboot 错误: 找不到或无法加载主类......

右击该程序 --> Maven --> Update Project。右击该程序 --> Maven -->clean/install 进行编译。

3 系统代码修改后,在eclipce中直接运行发现没有变化。右击该程序 --> Maven -->clean 清除原编译版本。

灵活运用自动装配@EnableAutoConfiguration

springboot启动会扫描所有jar包的META-INF/spring.factories,文件在org.springframework.boot.autoconfigure的spring.factories,在这个文件中,可以看到一系列Spring Boot自动配置的列表。这也是springboot-starter的作用原理。

例如:可以自己创建的common项目时,在common中创建META-INF/spring.factories,并配置boot自动扫描的的类。

这样当其他项目依赖common项目的jar时,就不用再次手动指定扫描common项目中的包/类了。

//spring.factories添加此配置后,boot自动扫描CommonConfiguration类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  cn.springcloud.book.common.config.CommonConfiguration

配置成功后IDEA会有下图的图标提示 

拦截器 HandlerInterceptor

springboot有两种方法注册拦截器

1   拦截器直接继承HandlerInterceptorAdapter实体类

2   实现HandlerInterceptor接口,然后在WebMvcConfiguration中通过addInterceptors()方法注册

可以覆盖三个不同时间点的拦截方法。以preHandle为例


import org.springframework.web.method.HandlerMethod;

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
//使用@RequestMapping时,handler是HandlerMethod类型
//2.5之前没有注解配置时,传入的是Controller接口实现
 if(handler instanceof HandlerMethod) {
    //handlerMethod 有很多实用方法
    HandlerMethod handlerMethod = (HandlerMethod) handler;

    Object bean=handlerMethod.getBean();
    Method method =handlerMethod.getMethod() ;
    MethodParameter[] MethodParameters= handlerMethod.getMethodParameters() ;
    A a= handlerMethod.getMethodAnnotation(Class<A> annotationType) ;
    boolean b= handlerMethod.hasMethodAnnotation(Class<A> annotationType);
 }
}

3  preHandle 返回false不向下继续执行,此时通过HttpServletResponse返回给客户端信息

private void render(HttpServletResponse response, CodeMsg cm)throws Exception {
      response.setContentType("application/json;charset=UTF-8");
      OutputStream out = response.getOutputStream();
      String str  = JSON.toJSONString(Result.error(cm));
      out.write(str.getBytes("UTF-8"));
      out.flush();
      out.close();
   }

4 afterCompletion相当于finally一定会执行,可以做关闭资源、线程解绑等工作

5 postHandle入参包含ModelAndView,这个参数可以为Null。

Null情况:1 Controller返回Null 。2 Controller使用@ResponseBody

自带匹配规则 拦截器 MappedInterceptor

实现了HandlerInterceptor接口,并且加入了String[] includePatterns、excludePatterns。默认使用AntMatcher实现url的映射匹配功能。

设置项目全局请求前缀

在  application.properties 中 server.servlet.context-path=/b,则所有请求URL前都要加上/b (不同版本设置略有不同)。

非常适合配合NGINX路由分配使用

例如:项目中 @GetMapping("/getcookie") 其对应的请求url:/b/getcookie

容器Bean中获取当前线程对应的request、response对象

RequestContextHolder中的ThreadLocal保存了ServletRequestAttributes,这也是RequestContextHolder最主要的作用。

ServletRequestAttributes中包含当前线程对应请求的request、response 、session。

方法一:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

方法二:
@autowired
HttpServletRequest request;

通过main()指定profiles的参数

在配置文件中设置多个 profiles不同配置,在main方法启动项目时,指定启用的profiles。还可以指定其他配置参数

public static void main(String[] args) {
        SpringApplication.run(ErpApplication.class, "spring.proflies.active=node1");
}

自定义Spring注解@应用

可以在Interceptor \ AOP 中获取方法的注解,并取得注解参数。而且可以已注解作为切点。

其思路都是先取得最终执行方法Method对象。

应用于AOP

通过AOP+@,实现日志管理,数据源切换等操作

项目实例:https://gitee.com/-/ide/project/y_project/RuoYi/edit/master/-/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java

//切点为@DataSource注解
@Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)")
    public void dsPointCut()
    {
    }

//切面
@Around("@annotation(DataSource)")
public Object around(ProceedingJoinPoint point,DataSource dataSource) throws Throwable
    {
       //注解可以直接注入方法参数

        return point.proceed();

    }


//切面
@Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable
    {
        MethodSignature signature = (MethodSignature) point.getSignature();
        //取得被代理方法对象
        Method method = signature.getMethod();
        //取得其DataSource注解
        DataSource dataSource = method.getAnnotation(DataSource.class);

        if (dataSource != null){
             //取得注解属性
             dataSource.value().name();
        }

        return point.proceed();

    }

应用于拦截器Interceptor

项目实例:https://gitee.com/-/ide/project/Exrick/xmall/edit/master/-/xmall-front-web/src/main/java/cn/exrick/front/interceptor/LimitRaterInterceptor.java

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        //取得方法上的RateLimiter注解
        RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class);
        if (rateLimiter != null){

        }
  ......
}

利用ApplicationContext实现事件监听

https://segmentfault.com/a/1190000011433514?utm_source=tag-newest

//发送事件
applicationContext.publishEvent(new SysLogEvent(logVo));
//监听事件
//默认是同步串行执行
@Async  //并发执行
@Order
@EventListener(SysLogEvent.class)
public void saveSysLog(SysLogEvent event) {
		SysLog sysLog = (SysLog) event.getSource();
		remoteLogService.saveLog(sysLog);
}
/**
 * 系统日志事件
 */
public class SysLogEvent extends ApplicationEvent { //必须继承ApplicationEvent 

	public SysLogEvent(SysLog source) {
		super(source);
	}
}

ControllerAdvice统一异常处理

@ControllerAdvice 切入Controller执行统一异常捕获处理,可以返回JSON或MV。若抛出异常类型,多个处理方法都满足捕获条件,优先命中最精准的。比如抛出MyException.class下面两个方法都满足,Spring会选择myErrorHandler方法执行。

@ControllerAdvice
public class MyControllerAdvice {

    /**
     * 全局异常捕捉处理
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Map errorHandler(Exception ex) {
        Map map = new HashMap();
        map.put("code", 100);
        map.put("msg", ex.getMessage());
        return map;
    }
    
    /**
     * 拦截捕捉自定义异常 MyException.class
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = MyException.class)
    public ModelAndView myErrorHandler(MyException ex) {
       ModelAndView modelAndView = new ModelAndView();
       modelAndView.setViewName("error");
       modelAndView.addObject("code", ex.getCode());
       modelAndView.addObject("msg", ex.getMsg());
       return modelAndView;
    }

}

统一日志

SpringBoot默认使用SLF4J作为日志门面,底层默认是LOGBACK。SLF4J作为门面其目的是用相同的开发代码适配底层不同的日志实现logback、log4j等等。当引入第三方框架时,框架底层的日志实现可能各不相同。未达到统一配置,可以在Maven引入第三方框架jar时排除其日志jar,这样SpringBoot的提供的底层LOGBACK伪装会代替其原有日志框架。

LOGBACK可以通过XML配置不同LOG级别打印的方式(控制台/文件)、文件存储位置、文件保存周期、文件命名分割规则(日期+大小)等等。必须配置滚动日志控制日志总量,否则有硬盘/内存溢出的风险

logging.level.root = INFO
logging.level.com.mypacket = INFO  #定义包的日志级别
logging.file.max-history = 30      #滚动日志最多保存多少个日志文件
logging.file.max-size = 10MB       #单文件大小

logging: 
    path: /var/logs          # 在项目根目录下/var/logs目录生成spring.log文件
    file: /var/logs/test.log # 在项目根目录下/var/logs目录生成test.log文件


private static final Logger LOG = LoggerFactory.getLogger(XXX.class);

异步日志:减少同步IO,提高单次请求响应速度。

异步日志配置

异步任务@Async

启动异步任务支持@EnableAsync。若有返回值,异步任务需要返回实现Future接口的对象,否则执行时调用方会报错。AsyncResult<V>是Spring框架提供的Future实现。调用方通过Future的isDone() 、cancel()、get()等方法控制并取得异步任务。 还可以自定义异步任务的线程池Bean。

@Async
public Future<Boolean> getBool() throws InterruptedException {
      Thread.sleep(3000);
      return new AsyncResult<Boolean>(new Boolean(true));
}

YAML

定义LIST

express:
  vendors:
    - code: "ZTO"
      name: "中通快递"
    - code: "YTO"
      name: "圆通速递"


@ConfigurationProperties(prefix = "express")
private List<Map<String, String>> vendors = new ArrayList<>();

允许跨域访问CORS

全局解决

1. 如果通过Nginx做反向代理,可以解决前端跨域访问的问题。

2 通过WebMvcConfigurerAdapter 配置

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }
}

局部解决:利用@CrossOrigin注解,可放至在控制层类上或者方法上。类上代表整个控制层所有的映射方法都支持跨域请求。

若在Controller注解上方添加@CrossOrigin注解后,仍然出现跨域问题。在@RequestMapping注解中没有指定Get、Post方式,具体指定后问题解决。 

@CrossOrigin(origins = "http://blog.lqdev.cn", maxAge = 3600) 


origins(可不填): 允许可访问的域列表

maxAge(可不填):准备响应前的缓存持续的最大时间(以秒为单位)。

注意不要重复解决跨域问题,例如:网关解决跨域、下游服务在此处理跨域问题。这样会造成请求失败。

自动加密 / 解密请求中的 Json

涉及到@RequestBody@ResponseBody的类型转换问题一般都在MappingJackson2HttpMessageConverter中解决,想要自动加密 / 解密只需要继承这个类并重写readInternal/writeInternal方法

 @SensitiveFormat基于注解的敏感词过滤功能

将其中的敏感词替换为 * 等特殊字符 

@SensitiveFormat基于注解的敏感词过滤功能_@sensitive 注解-CSDN博客

方法参数中使用注解

 public JedisSentinelPool jedisPool(@Qualifier("jedis.pool.config") JedisPoolConfig config,
                                       @Value("${spring.redis.sentinel.master}") String clusterName,
                                       @Value("${spring.redis.sentinel.nodes}") String sentinelNodes,
                                       @Value("${spring.redis.timeout}") int timeout,
                                       @Value("${spring.redis.password}") String password) 

RequestBodyAdvice和ResponseBodyAdvice 

https://www.hellojava.com/a/45272.html

下载文件

IO模式:将一个文件分批读入内存中,再写入输出流,全部写入流后最终全部冲刷到请求IO中。避免了大文件JVM内存溢出的问题。除了下载文件,当有比较大的数据需要返回时(超大json)也可以通过此方式解决,分多次读取到内存write来避免内存溢出。也可以拆分成多次请求。

注意下载方法需要return null或是void。否则会抛异常。

 @RequestMapping("/download")
        public String download( String fileName ,String filePath, HttpServletRequest request, HttpServletResponse response){             
            response.setContentType("text/html;charset=utf-8");
            try {
                request.setCharacterEncoding("UTF-8");
            } catch (UnsupportedEncodingException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }   
            java.io.BufferedInputStream bis = null;
            java.io.BufferedOutputStream bos = null;
        
            String downLoadPath = filePath;  //注意不同系统的分隔符
        //  String downLoadPath =filePath.replaceAll("/", "\\\\\\\\");   //replace replaceAll区别 *****  
            System.out.println(downLoadPath);
            
            try {
                long fileLength = new File(downLoadPath).length();
                response.setContentType("application/x-msdownload;");
                response.setHeader("Content-disposition", "attachment; filename=" + new String(fileName.getBytes("utf-8"), "ISO8859-1"));
                response.setHeader("Content-Length", String.valueOf(fileLength));
                bis = new BufferedInputStream(new FileInputStream(downLoadPath));
                bos = new BufferedOutputStream(response.getOutputStream());
                byte[] buff = new byte[2048];
                int bytesRead;
                while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
                    bos.write(buff, 0, bytesRead);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (bis != null)
                    try {
                        bis.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                if (bos != null)
                    try {
                        bos.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
            }
            //必须返回null,否则有异常
            return null;    
        }

重复提交 

前端处理思路:
点击按钮后,立即将按钮置灰且不可使用,然后调用处理逻辑接口,当接口有响应后重新使按钮重新亮起可用

后端处理思路:

重定向到其他页面。
思路一、建立数据库唯一索引,通过数据库唯一索引,保证数据唯一
思路二、通过token方式,调用业务接口前先调用接口获取token,调用业务接口时传入token,先进行token校验和处理,当token正确时删除该token(第二次传入相同token就会校验不通过),然后处理正常的业务逻辑。

思路三:用户session中保存k=url,v=prama (v中包含过期时间,例如:10秒内算重复提交)。下次请求时在拦截器/aop中验证上次的同url请求参数,是否与本次相等且是否过期。可以与思路二结合使用。

Springboot+thymeleaf+nginx实现动态页面静态化方案

https://www.glxxw2018.com/study/blog/detail/9K6elibXWb.html

https://my.oschina.net/xpx/blog/1845829

方法参数验证

在WebDataBinder参数绑定后进行@Valid进行验证,验证失败抛出异常,或用BindingResult参数捕获错误。

SpringBoot里参数校验/参数验证_spring boot校验字段正则-CSDN博客

SpringBoot注解校验请求参数_注解判断字段值为1或者2-CSDN博客

https://www.cnblogs.com/cjsblog/p/8946768.html

嵌套校验List<E>

校验List中每个元素

public class ObjectWithArray  {

    @NotBlank(message = "name不能为空")
    private String name;
    @NotNull(message = "strings不能为空")
    @NotEmpty(message = "strings不能为空")
    private List<String> strings;
    /**
     * @Valid 嵌套校验 UserInfo
     */
    @Valid
    private List<UserInfo> users;

....
}


public class UserInfo  {
    @NotNull
    private Integer id;
    @NotBlank(message = "name不能为空")
    private String name;
    private Boolean man;
    @Min(value = 1, message = "age范围为[1-99]")
    @Max(value = 99, message = "age范围为[1-99]")
    private Integer age;
....
}
@PostMapping("/postobjectwithlist")
    @ResponseBody
    public Object postObjectWithList(@RequestBody @Validated ObjectWithArray objectWithArray, BindingResult bindingResult) {

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值