0%

文章字数:131,阅读全文大约需要1分钟

ResponseBodyAdvice可以在注解@ResponseBody将返回值处理成相应格式之前操作返回值。实现这个接口即可完成相应操作。可用于返回值加密

@ControllerAdvice标记类

接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public interface ResponseBodyAdvice<T>{
/**
* Whether this component supports the given controller method return type
* and the selected {@code HttpMessageConverter} type.(此组件是否支持给定的控制器方法返回值类型)
* @param returnType the return type(返回类型)
* @param converterType the selected converter type(选中的转换器类型)
* @return {@code true} if {@link #beforeBodyWrite} should be invoked;
* {@code false} otherwise(返回是否调用处理方法)
*/
boolean support(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

/**
* Invoked after an {@code HttpMessageConverter} is selected and just before
* its write method is invoked.
* @param body the body to be written(需要写操作的body)
* @param returnType the return type of the controller method
* @param selectedContentType the content type selected through content negotiation
* @param selectedConverterType the converter type selected to write to the response
* @param request the current request
* @param response the current response
* @return the body that was passed in or a modified (possibly new) instance
*/
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
}

其它

在参数被方法调用之前截取参数的是RequestBodyAdvice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@ControllerAdvice
public class LogRequestBodyAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}

@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
return httpInputMessage;
}

@Override
public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
Method method=methodParameter.getMethod();
log.info("{}.{}:{}",method.getDeclaringClass().getSimpleName(),method.getName(),JSON.toJSONString(o));
return o;
}

@Override
public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
Method method=methodParameter.getMethod();
log.info("{}.{}",method.getDeclaringClass().getSimpleName(),method.getName());
return o;
}
}

文章字数:192,阅读全文大约需要1分钟

阿里巴巴用java开发的消息中间件,支持大规模topic数量,性能好。

推送方式

  1. pull模式:拉取,DefaultMQPullConsumer模式。是由客户端主动向MQ请求数据,主动权在客户端,先拉取数据再消费。不会因为推送太快而处理不及时。

  2. push模式:推送,DefaultMQPushConsumer模式。一般的pushMQ主动推送信息,但是可能会导致消费者跟不上推送速度。RocketMQ采用的是长轮询的方式,客户端访问MQ,有信息就拉取关闭连接消费,然后再请求并拉取。没有信息请求就会等待新信息,知道超时,超时会关闭连接并再次发送新的请求。


文章字数:97,阅读全文大约需要1分钟

RuntimeException 和 Exception

相同

  1. RuntimeException 继承与 Exception
  2. RuntimeExceptionException都能被try...catch

不同

  1. 如果程序抛出Exception,编译器会要求代码中处理,即try...catchthrows
  2. RuntimeException可以不在代码中处理,运行时遇到则会抛出。例如空指针异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
runtimeException(); // 可以不处理,运行时会直接抛出异常
try {
exception(); // 必须try catch
} catch (Exception e) {
System.out.println(e.getMessage());
}
}

private static void runtimeException() {
throw new RuntimeException("runtime exception");
}

private static void exception() throws Exception {
throw new Exception("exception");
}

文章字数:37,阅读全文大约需要1分钟

SQLServerException:列名‘true’无效

mysql里支持true,在sql server里只有0,1所以语句不兼容。只有改字段类型了。


文章字数:667,阅读全文大约需要2分钟

响应式编程

  1. 传统方式
    前端访问后端,后端调用其他服务。
    然而调用的过程中(即网络IO)需要等待结果,这种同步阻塞的过程会产生大量无用的等待。
    占用服务端连接池(tomcat处理请求的线程)、降低吞吐量。

    1
    前端--> tomcat ---> tomcate
  2. 响应式系统

  • 有请求才会相应,快速响应
  • 系统吞吐量增高

RxJava

  • 一种响应式编程的类库
  • 基于观察者模式 (订阅-发布)

生产者

image.png

三种类型的消费者

image.png

image.png

image.png

订阅关系

image.png

简写

1
2
Observable.just("hello world")//简单创建一个生产者
.subscribe(System.out::println);// 简易订阅

变换操作符

1
2
3
Observable.just("hello world")//简单创建一个生产者
.map(v->v+"xxxx")
.subscribe(System.out::println);// 简易订阅

中间操作使用不同线程

中间操作和消费者使用不同线程池

image.png

创建线程池操作

  • Schedulers.computation():和cup数量相关,cpu密集型使用
  • Schedulers.io():io操作相关的操作
  • Schedulers.newThread():直接创建线程,一般不用
  • Schedulers.from():使用自定义线程池
    出现多个subscribeon以第一个为准,即被观察者的线程池只有一个。

应用于业务代码

  • Controller层直接返回Observable,Spring会自动订阅该生产者(RxJava的)
  • 可以节约连接池线程,连接池不用处理业务代码。

优点

  1. 减少代码量
  2. 简化线程操作,几乎不用考虑如何创建操作线程

Spring Reactor

DeferredResult

  • 基于Servlet3.0的异步Servlet(设置@Servlet(asyncSupported=true))

image.png

  • Controller层直接返回DeferredResult
  • Servlet进入两次,一次是请求进入,然后调用业务线程。自身直接返回(连接线程返回线程池)。业务线程执行完通过DeferredResult再次找到Servlet获取连接对象,返回信息

文章字数:377,阅读全文大约需要1分钟

channel最主要的作用就是作为一个传输Message的管道,Spring Integration中实现了各种各样的channel可以满足不同的需求。

一、顶级接口

  1. MessageChannel: 该接口中没有提供从channel中接收的方法,因为接收的方法被两个子接口表示。
1
2
boolean send(Message<?> message);
boolean sned(Message<?> message, long timeout);
  1. PollableChannel: 接收的子接口,具备轮询获取消息的能力。要求消息消费者或者框架周期性检测消息是否可达。
1
2
Message<?> receive();
Message<?> receive(long timeout);
  1. SubscribableChannel: 发送消息给订阅了MessageHanlder的订阅者
1
2
boolean subscribe(MessageHandler handler);
boolean unsubscribe(MessageHandler hanlder);

二、常用channel

  1. DirectChannel: 默认的Spring Integration默认的消息通道,它允许将消息发送给一个订阅者,然后阻碍发送直到消息被接收。同步发送。

  2. QueueChannel: 允许消息接收者轮询获得消息,用一个队列queue接收消息,队列的容量大小可配置。异步发送

  3. PublishSubscribeChannel: 允许广播消息给所有订阅者,不支持缓存即Quenue

  4. PriorityChannel: 可以按照优先级将数据存储到队列


文章字数:770,阅读全文大约需要3分钟

一、什么是循环依赖

循环依赖就是两个或者以上的bean相互持有对方,形成了一个依赖闭环。

循环依赖的场景有

  1. 构造器的循环依赖
  2. field属性的循环依赖

二、检测循环依赖

  1. Spring创建Bean时是递归创建的,即发现属性有依赖的Bean就回去创建属性对象的Bean
  2. 检测循环依赖就是在创建Bean的时候做个标记,当再次递归调用创建的时候,发现有创建的标记就知道了

三、解决循环依赖

  1. 三级缓存

    1
    2
    3
    4
    5
    6
    7
    8
    /** 存放单例bean 1级*/
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

    /** 存放初始化,但还没赋值的bean的工厂(存放函数接口、钩子函数等) 3级*/
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

    /** 存放提前曝光的bean 2级*/
    private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
  2. bean实例化过程

  • createBeanInstance实例化bean
  • populateBean填充属性
  • initializeBean初始化bean

依赖循环发送在实例化bean(调用构造方法)和填充属性(递归创建bean)的时候

  1. 创建流程
  • 执行构造函数,创建一个未初始化的类
  • 将初始化的类暴露到singletonFactories中,并标记已创建(加入registeredSingletons)
  • 填充属性,属性中包含其他bean则递归进入创建流程
  • 递归创建的属性假如也依赖最早的类,则会先尝试获取它
  • 尝试在一级缓存(singletonObjects)获取
  • 当获取不到,且改bean被标记过正在创建尝试在二级缓存(earlySingletonObjects)中获取提前暴露的类
  • 找不到则从三级缓存(singletonFactories)获取。
  • 此时就找到了创建但未初始化的类
  • 找到后将三级缓存中的类工厂移除,将类添加到提前曝光的二级缓存中earlySingletonObjects
  • 然后返回本身,此时最初的类获取到属性的bean后也能创建成功了。
  • 四、三级缓存的意义

  1. 二级缓存也可以解决循环依赖,一个存正常的单例,一个存提前曝光的类
  2. 三级缓存主要是在曝光且提前引用时能够做一下拓展操作,二级缓存只能知道提前曝光,三级缓存转移到二级缓存才是曝光并提前引用
  • 每个对象创建之后会放在三级缓存中,暴露自己的构造工厂
  • 然后执行初始化方法,填充需要的依赖。
  • 递归创建依赖对象时,如果发现需要注入的对象是之前递归过程中创建并初始化的(二级缓存中的),或者是创建未初始化的(三级缓存中),就从缓存中拿出来
  • 如果是从三级缓存拿的,从三级缓存中移除,放到二级缓存中

关键词: java, spring

文章字数:122,阅读全文大约需要1分钟

  1. bean实例化
  2. bean属性注入
  3. 调用BeanNameAware.setBeanName()
  4. 调用BeanClassLoaderAware.setBeanClassLoader()
  5. 调用BeanFactoryAware.setBeanFactory()
  6. 调用EnvironmentAware.setEnviroment()
  7. 调用EmbeddedValueResolverAware.setEmbedValueResolver()
  8. 调用ResourceLoaderAware.setResourceLoader()
  9. 调用ApplicationEventPublisherAware.setApplicationEventPublisher()
  10. 调用MessageSourceAware.setMessageSource()
  11. 调用ApplicationContextAware.setApplicationContext()
  12. 如果是WebApplicationContext,调用ServletContextAware.setServletContext()
  13. 调用BeanPostProcessor初始化方法
  14. 调用InitializingBean.afterPropertitesSet()
  15. 调用自定义初始化方法
  16. 调用BeanPostProcessor初始化之后方法
  17. 结束

销毁环境

  1. 调用DisposableBean.destory()
  2. 调用自定义销毁功能

文章字数:191,阅读全文大约需要1分钟

https://www.jianshu.com/p/e2eae08f3255

配置参数(bean)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Configuration
public class LocaleConfig {

/**
* 默认解析器 其中locale表示默认语言
*/
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
//设置默认英语
localeResolver.setDefaultLocale(Locale.US);
//注入Bean
return localeResolver;
}

/**
* 默认拦截器 其中lang表示切换语言的参数名
* 例如: ?lang=zh_CN
*/
@Bean
public WebMvcConfigurer localeInterceptor() {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor localeInterceptor = new LocaleChangeInterceptor();
localeInterceptor.setParamName("lang");
registry.addInterceptor(localeInterceptor);
}
};
}
}

配置文件

1
2
3
4
# 设置国际化文件存放的地方
spring:
messages:
basename: static/i18n/messages #相对路径,开头没有/

classpath:/static/i18n目录中添加文件:

  1. 默认文件messages.properties
    1
    默认翻译
  2. 美式英语messages_en_US.properties
    1
    user.title=User Login
  3. 简体中文messages_zh_CN.properties
    1
    user.title=用户登陆

后端使用国际化

  • 编写通用国际化工具类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Component
    public class MessageUtils{

    private static MessageSource messageSource;

    public MessageUtils(MessageSource messageSource) {
    MessageUtils.messageSource = messageSource;
    }

    /**
    * 获取单个国际化翻译值
    */
    public static String get(String msgKey) {
    try {
    return messageSource.getMessage(msgKey, null, LocaleContextHolder.getLocale());
    } catch (Exception e) {
    return msgKey;
    }
    }

freeMarker中获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 <!DOCTYPE html>
<html lang="en">
<head>
<!--引入-->
<#import "spring.ftl" as spring>
<#assign arg = ["AAA","张三"]>
<title><@spring.messageArgs "title" ,arg /></title>
<meta charset="UTF-8">
</head>
<body>
<br/>
<a class="changeLang" href="javascript:void(0);">english</a><br/>
<a class="changeLang" href="javascript:void(0);">中文</a><br/>
<!--使用-->
<@spring.message code="user.loginname"/><br/>
</body>
<script src="${request.contextPath}/static/jquery.js"></script>
<script>
$(".changeLang").on("click", function () {
switch ($(this).text()) {
case "中文": {
window.location.href = "index?_lang=zh_CN";
break;
}
case "english": {
window.location.href = "index?_lang=en_US";
break;
}
}
})
</script>
</html>

文章字数:298,阅读全文大约需要1分钟

Spring中默认有starter实现类Mail

依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

配置文件

1
2
3
4
5
6
7
8
9
# JavaMailSender 邮件发送的配置
spring.mail.host=smtp.qq.com
spring.mail.username=用户qq邮箱
#QQ邮箱的授权码
spring.mail.password=密码
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.default-encoding=UTF-8

简单邮件

1
2
3
4
5
6
7
8
9
10
11
12
public void sendSimpleMail(){
try{
SimpleMaileMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage .setFrom("sendFrom@qq.com");
simpleMailMessage .setTo("sendTo@qq.com");
simpleMailMessage.setSubject("主题");
simpleMailMessage.setText("内容");
javaMailSender.send(simpleMailMessage);//发送
}catch(Exception e){
logger.error("发送失败",e.getMessage());
}
}

自定义发件人名称

1
2
3
4
5
6
7
8
String nick = "";
try {
nick = javax.mail.internet.MimeUtility.encodeText("张三");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 设置发件人
mimeMsg.setFrom(new InternetAddress("sendFrom@qq.com", nick));

html格式邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void sendHTMLMail(MailBean mailBean) {
MimeMessage mimeMailMessage = null;
try {
mimeMailMessage = javaMailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMailMessage, true);
mimeMessageHelper.setFrom(MAIL_SENDER);
mimeMessageHelper.setTo(mailBean.getRecipient());
mimeMessageHelper.setSubject(mailBean.getSubject());
mimeMessageHelper.setText("<h1>hello</h1>", true);
javaMailSender.send(mimeMailMessage);
} catch (Exception e) {
logger.error("邮件发送失败", e.getMessage());
}
}

带附件格式的邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void sendAttachmentMail(MailBean mailBean) {
MimeMessage mimeMailMessage = null;
try {
mimeMailMessage = javaMailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMailMessage, true);
mimeMessageHelper.setFrom(MAIL_SENDER);
mimeMessageHelper.setTo(mailBean.getRecipient());
mimeMessageHelper.setSubject(mailBean.getSubject());
mimeMessageHelper.setText(mailBean.getContent());
//文件路径 spring的FileSystemResource,使用绝对路径访问文件资源
FileSystemResource file = new FileSystemResource(new File("src/main/resources/static/image/mail.png"));
mimeMessageHelper.addAttachment("mail.png", file);

javaMailSender.send(mimeMailMessage);
} catch (Exception e) {
logger.error("邮件发送失败", e.getMessage());
}
}

发送带静态资源的邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void sendInlineMail(MailBean mailBean) {
MimeMessage mimeMailMessage = null;
try {
mimeMailMessage = javaMailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMailMessage, true);
mimeMessageHelper.setFrom(MAIL_SENDER);
mimeMessageHelper.setTo(mailBean.getRecipient());
mimeMessageHelper.setSubject(mailBean.getSubject());
mimeMessageHelper.setText("<html><body>带静态资源的邮件内容,这个一张IDEA配置的照片:<img src='cid:picture' /></body></html>", true);
//文件路径
FileSystemResource file = new FileSystemResource(new File("src/main/resources/static/image/mail.png"));
mimeMessageHelper.addInline("picture", file);

javaMailSender.send(mimeMailMessage);
} catch (Exception e) {
logger.error("邮件发送失败", e.getMessage());
}
}

基于Freemarker的模板邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void sendTemplateMail(MailBean mailBean) {
MimeMessage mimeMailMessage = null;
try {
mimeMailMessage = javaMailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMailMessage, true);
mimeMessageHelper.setFrom(MAIL_SENDER);
mimeMessageHelper.setTo(mailBean.getRecipient());
mimeMessageHelper.setSubject(mailBean.getSubject());

Map<String, Object> model = new HashMap<String, Object>();
model.put("content", mailBean.getContent());
model.put("title", "标题Mail中使用了FreeMarker");
Template template = configuration.getTemplate("mail.ftl");
String text = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
mimeMessageHelper.setText(text, true);

javaMailSender.send(mimeMailMessage);
} catch (Exception e) {
logger.error("邮件发送失败", e.getMessage());
}

}

动态添加邮箱配置信息

和之前使用接口javaMailSender不同,这次使用的是接口的实现类。
使用JavaMailSenderImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void sendSimpleMail() throws Exception {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();//直接生产一个实例
String users[] = {"xxxxx@qq.com","xxxxx@126.com"};
mailSender.setHost("smtp.126.com");//动态添加配置
mailSender.setPassword("xxxxx");
mailSender.setPort(25);
mailSender.setProtocol("smtp");
mailSender.setUsername("xxxxx");
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("xxxxxxx");
message.setTo(users); // 使用数组的形式还可以群发
message.setSubject("羽毛球比赛");
message.setText("报名参加羽毛球赛");
mailSender.send(message);
}