1 浏览器缓存 2 nginx缓存+lua缓存 3 服务本地缓存 4 redis缓存 5 数据库
注意带宽控制:500k的文件瞬时200的请求就能填满100M的带宽。可以开启GZIP压缩部分静态文件。
推荐张开涛的《亿级流量网站架构核心技术》
视频:video/BV1bJ41157W7
方法1:build后的前端项目拷贝到boot项目resources/static目录,项目自动映射这个目录。安全框架不要拦截静态路径。
方法2:build后的前端项目,由Nginx代理。
注意结尾不要有多余的空字符或空格,注意:和= 不要错
配置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
springboot2默认堆大小是2G。
优化网络带宽,可以配置针对哪些格式压缩
server.compression.enabled=true
统一的返回格式更容易做前后端交互。包含:是否成功+返回码+信息+返回体value{}
Boolean success;
String code;
String msg;
HashMap value; //HashMap可以保存多个对象,且转换成JSON后,每个对象都以key最为name
未出现不一致则不需要设置
//设置jackson时区,其根据数据库时区将数据库得到的时间转化为GMT+8时区
spring.jackson.time-zone=GMT+8
//jackson时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
https://blog.csdn.net/shanchahua123456/article/details/89484066
图片上传的文本某个文件路径下保存,外界通过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,浏览器拿到sessionid存入cookie。在单体应用网站中session可以做用户(浏览器)的会话级别缓存,例如:缓存验证码、用户信息等
Cookie、Session丢失:
原因1:Cookie-Domain与请求服务器的域名不统一。只要二级域名相同服务器端就可以获得Cookie。比如:a.su.com 和 b.su.com
例1:如果有Nginx做反向代理,后端生成的Cookie要配置Domain和Path为Nginx的,保证请求发到Nginx时是携带Cookie的。
@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 userInfos){}
public class UserInfo implements Serializable {
private Integer id;
private String name;
private List users;
get/set.....
}
spring-boot-maven-plugin插件已经集成了maven-surefire-plugin插件,只需要在pom.xml里增加
true
Content-Type:application/x-www-form-urlencoded、application/json、multipart/form-data、text/xml
https://www.jianshu.com/p/3c3157669b64
1、发送请求时带上redirect地址,这中方式可以告诉对方服务器,当执行完业务后将用户浏览器重定向到指定页面。
http://....../toredirect?redirect=url地址&携带参数=x
2、发送请求时带上callback地址,这中方式可以告诉对方服务器,当执行完业务后回调指定url地址,通过msgid双方建立关系。
可以实现简单的异步回调功能。
http://....../tocallback?msgid=1&callback=url地址&回调时携带参数=x
有时某些操作需要异步通知前端页面。最简单的实现就是前端页面通过ajax定时轮序请求后端restapi获取结果。
比如:1)手机扫二维码登录(二维码中带有唯一ID,手机扫描二维码后携带ID将手机用户信息保存在redis中,前端页面js定时轮训请求查找redis中ID对应的用户信息)。2)订单付款时,用户同时开启多个二维码支付页面,为尽可能避免多个页面都可以支付,支付页面轮序后端restapi查看当前订单状态,发现已支付则当前页二维码图片隐藏。
https://www.jianshu.com/p/0d830b152922
@Component、@Service...等、JavaConfig@Bean、XML:
常用API
getbean(String name)、getbean(Class)、getbean(String name,Class)、
getbean(Class,Object....)覆盖默认参数、getBeanProvider(Class) 延迟查找、
getBeanNamesForType(Class):List
更底层API
https://time.geekbang.org/course/detail/265-189600
https://blog.csdn.net/shanchahua123456/article/details/87616661
更多详细用法: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";
}
}
https://blog.csdn.net/qq_32447301/article/details/88046026
吞吐量的提高是指整个系统可以响应更多的请求,不一定是单次请求响应提高。有时候需要牺牲单次响应换取吞吐量。
例如: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 数据库 )
本质上是浏览器与服务器建立socket短连接,通过http协议请求服务器端资源。服务器端通过文本形式把响应给浏览器,浏览器再请求中引用的静态资源, 展示样式。
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 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;
}
https://jingyan.baidu.com/article/cd4c29792d02c9756f6e607f.html
同步请求
异步请求
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。不要用默认线程池,请单独配置线程池。
https://blog.csdn.net/w47_csdn/article/details/86010165
https://blog.csdn.net/luckykapok918/article/details/79105698
配置优化与测试
https://blog.csdn.net/cdnight/article/details/83718314
AOP实现原理实在为bean创建代理对象,所以要执行bean代理对象的方法,AOP才会生效。
@Transactional失效:https://blog.csdn.net/shanchahua123456/article/details/89766116
都可以作为非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:信息转换器
}
原贴:https://blog.csdn.net/cl_andywin/article/details/54605831
在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 initParameters = new HashMap();
initParameters.put("excludes", excludes);//相当于web.xml配置的
initParameters.put("enabled", enabled);
registration.setInitParameters(initParameters);
return registration;
}
(2)使用@WebFilter
使用@WebFilter注册,需要在Filter类上使用该注解即可,但是需要在@Configuration类中使用Spring Boot提供的注解@ServletComponentScan扫描注册相应的Filter。
( 3 ) Filter实现 implements Filter
public List 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请求
https://blog.csdn.net/geloin/article/details/7444321
注册Listener
(1)使用ServletListenerRegistrationBean注册
使用ServletListenerRegistrationBean注册Listener只需要在@Configuration类中加入即可,例如以下代码:
@Bean
public ServletListenerRegistrationBean myServletListener() {
ServletListenerRegistrationBean myListener = new ServletListenerRegistrationBean();
myListener.setListener(new MyListener());
return myListener;
}
(2)使用@WebListener
使用@WebListener注册,需要在Filter类上使用该注解即可,但是需要在@Configuration类中使用Spring Boot提供的注解@ServletComponentScan扫描注册相应的Listener。
SpringBoot中通过DispatcherServletAutoConfiguration配置类注册DispatcherServlet
按顺序依次执行
过滤器Filter:作用在DispacherServlet之前,过滤/包装request对象。
拦截器:作用在进入DispacherServlet调用目标Controllter方法之前,目的拦截Servlet中Http请求的整个生命周期过程。
preHandle通过HandlerMethod取得Controllter中目标Method。但此时spring未完成数据绑定,所以其无法获得目标Method的参数值。只能通过request对象获取请求信息。
AOP:是Bean的代理功能,是在Spring创建Bean时,为Bean生成代理对象,最终注册到Spring中。
通过JoinPoint取得目标Method和其参数值。
继承HttpServletRequestWrappe包装类。为不可变的HttpServletRequest增加功能。在Filter中包装Request,在Filter向下链式调用时将Wrapper对象的引用传入下一层 chain.doFilter(requestWrapper, response)。
例如:
防止xss攻击:利用HttpServletRequestWrapper包装Request的取值方法,对Request中的值作二次处理。变向修改原本不可变Request。
解决Request中getInputStream只能读取一次:Wrapper包装getInputStream方法,第一次去的流后缓存信息,之后都取缓存。
http://blog.didispace.com/books/spring-mvc-4-tutorial/publish/21-14/4-shallow-etag-support.html
过滤器会将响应内容缓存起来,然后以此生成一个MD5哈希值,并把这个值作为ETag头的值写回响应中。下一次客户端再次请求这个同样的资源时,它会将这个ETag的值写到If-None-Match
头中。本次响应返回时,将本次响应数据计算ETag比较If-None-Match,
如果是相同的,那么服务器会返回一个304
。节省响应带宽。
此功能不会减少服务器的响应计算,只是为了节省响应带宽。
https://time.geekbang.org/course/detail/265-189578
1) 调用无参数构造器
2) 带参数构造器
3)FactoryBean
4) 工厂方法
注意静态/非静态工厂的使用区别
// 工厂,创建对象
public class ObjectFactory {
// 实例方法创建对象
public User getInstance() {
return new User(100,"工厂:调用实例方法");
}
// 静态方法创建对象
public static User getStaticInstance() {
return new User(101,"工厂:调用静态方法");
}
}
方法一:
通过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
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,包含了所有
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配置中。
builder.addPropertyValue(String name, Object v);
//给类中userService变量注入名为userService的bean。
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
方式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"));
}
通过XmlBeanDefinitionReader实现
https://blog.csdn.net/abc123lzf/article/details/89354623
创建自定义扫描器继承Spring的ClassPathBeanDefinitionScanner类。
其为子类提供了Set
MyBatis、Dubbo、Fegin等第三方功能,就是通过此方法将开发人员自定义的接口都注册成BeanDefinition(FactoryBean)的。通过FactoryBean生成接口类型的动态代理,接口执行方法时通过动态代理拿到Method + arg[]。还可以为扫描器配置自定义Filters过滤掉部分无效类。
@Component
https://www.jianshu.com/p/2b993ced6a4c
@EnableXXX -> @Import(ImportBeanDefinitionRegistrar) 拿到容器Registrar ->继承ClassPathBeanDefinitionScanner->扫描包获得Set
现实案例:
1、org.springframework.cloud.openfeign.FeignClientsRegistrar
2、org.mybatis.spring.annotation.MapperScannerRegistrar
在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
https://blog.csdn.net/shanchahua123456/article/details/86552224
https://blog.csdn.net/shanchahua123456/article/details/87286148
spring mvc项目中需要在web.xml添加CharacterEncodingFilter过滤器
因为springboot启动时默认有CharacterEncodingFilter过滤器,只要在配置文件中为过滤器设置属性参数即可修改默认过滤属性,
所以springboot中只需要在配置文件中添加以下配置:
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
[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也会报错)
参考:https://blog.csdn.net/zhangchao19890805/article/details/54694114
b 将项目BuildPath指向对应的jdk版本
参考:https://blog.csdn.net/lslk9898/article/details/73836745
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就可以正常链接数据库
org.springframework.boot
spring-boot-starter-jdbc
com.microsoft.sqlserver
mssql-jdbc
runtime
com.alibaba
druid-spring-boot-starter
1.1.9
1 java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder.
maven包之间有依赖冲突,实测boot2.1.3 与 dubbo-zipkin-spring-starter 1.0.2 会引发冲突
2 springboot 错误: 找不到或无法加载主类......
右击该程序 --> Maven --> Update Project。右击该程序 --> Maven -->clean/install 进行编译。
3 系统代码修改后,在eclipce中直接运行发现没有变化。右击该程序 --> Maven -->clean 清除原编译版本。
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会有下图的图标提示
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 annotationType) ;
boolean b= handlerMethod.hasMethodAnnotation(Class 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
实现了HandlerInterceptor接口,并且加入了String[] includePatterns、excludePatterns。默认使用AntMatcher实现url的映射匹配功能。
在 application.properties 中 server.servlet.context-path=/b,则所有请求URL前都要加上/b (不同版本设置略有不同)。
非常适合配合NGINX路由分配使用
例如:项目中 @GetMapping("/getcookie") 其对应的请求url:/b/getcookie
RequestContextHolder中的ThreadLocal保存了ServletRequestAttributes,这也是RequestContextHolder最主要的作用。
ServletRequestAttributes中包含当前线程对应请求的request、response 、session。
方法一:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
方法二:
@autowired
HttpServletRequest request;
在配置文件中设置多个 profiles不同配置,在main方法启动项目时,指定启用的profiles。还可以指定其他配置参数
public static void main(String[] args) {
SpringApplication.run(ErpApplication.class, "spring.proflies.active=node1");
}
可以在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){
}
......
}
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 切入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,提高单次请求响应速度。
异步日志配置
启动异步任务支持@EnableAsync。若有返回值,异步任务需要返回实现Future接口的对象,否则执行时调用方会报错。AsyncResult
@Async
public Future getBool() throws InterruptedException {
Thread.sleep(3000);
return new AsyncResult(new Boolean(true));
}
定义LIST
express:
vendors:
- code: "ZTO"
name: "中通快递"
- code: "YTO"
name: "圆通速递"
@ConfigurationProperties(prefix = "express")
private List
全局解决:
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(可不填):准备响应前的缓存持续的最大时间(以秒为单位)。
注意不要重复解决跨域问题,例如:网关解决跨域、下游服务在此处理跨域问题。这样会造成请求失败。
涉及到@RequestBody
和@ResponseBody
的类型转换问题一般都在MappingJackson2HttpMessageConverter
中解决,想要自动加密 / 解密只需要继承这个类并重写readInternal
/writeInternal
方法
将其中的敏感词替换为 * 等特殊字符
https://blog.csdn.net/lycit/article/details/79668184
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)
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请求参数,是否与本次相等且是否过期。可以与思路二结合使用。
https://www.glxxw2018.com/study/blog/detail/9K6elibXWb.html
https://my.oschina.net/xpx/blog/1845829
在WebDataBinder参数绑定后进行@Valid进行验证,验证失败抛出异常,或用BindingResult参数捕获错误。
https://blog.csdn.net/jinjiankang/article/details/89711493
https://blog.csdn.net/litte_frog/article/details/82963906
https://www.cnblogs.com/cjsblog/p/8946768.html
校验List中每个元素
public class ObjectWithArray {
@NotBlank(message = "name不能为空")
private String name;
@NotNull(message = "strings不能为空")
@NotEmpty(message = "strings不能为空")
private List strings;
/**
* @Valid 嵌套校验 UserInfo
*/
@Valid
private List 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) {