开发经验

service中如果有复杂的Dao查询,可以封装子mapper

子mapper继承默认mapper,实现复杂dao层接口,service调用子mapper

简单的add edit对前端的接口,可以直接controller层接收DO对象,

避免复杂的冗余的接收对象定义 和 到DO的转换
在service层create时,可调用mapper的insertSelective方法(因为此时id等参数为null)

  1. @Override
  2. public int create(DpsPhysicalTableDO dpsLogicalTableDO) {
  3. return mapper.insertSelective(dpsLogicalTableDO);
  4. }

Controller层测试(postman)

对post接口的测试,接收对象一般为JSON类型的数据结构(含结构化的类),postman测试时body传递可以选择”raw“,并记得选择content-type:JSON
image.png

父子线程之间的trace传递

调用链追踪:在调用链系统设计中,为了优化系统运行速度,会使用多线程编程,为了保证调用链ID能够自然的在多线程间传递。对于父线程中定义为ThreadLocal的变量,需要考虑其传递问题

https://www.jianshu.com/p/94ba4a918ff5

通过反射获取实现一个接口的所有子类

场景:在工厂模式中, 经常需要将所有实现(定义一个接口,不同的类不同的实现方式)统一管理起来,一般存储到一个map结构,暂且称之为“对象工厂”,这就涉及到对象的注册。如果用Spring,可以有两种方式
(1)在类的默认构造方法中,统一调用register到map中
(2)实现BeanPostProcessor,在后处理中register(这时候也可以定义一个注解,写一个单独的类实现BeanPostProcessor,在其post方法中处理该注解)

如果不用Spring,单纯的一个Java工程,可使用反射来做。利用类库

  1. <dependency>
  2. <groupId>org.reflections</groupId>
  3. <artifactId>reflections</artifactId>
  4. <version>0.9.12</version>
  5. </dependency>

https://github.com/ronmamo/reflections

  1. Reflections reflections = new Reflections("my.package");
  2. Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);
  3. 然后Class.newInstance()创建实例,registermap

如何在spring中获取代理对象的目标对象

https://blog.csdn.net/xiongyouqiang/article/details/80401703

  1. public static Object getTarget(Object proxy) throws Exception {
  2. if (!AopUtils.isAopProxy(proxy)) {
  3. //不是代理对象
  4. return proxy;
  5. }
  6. Object obj = null;
  7. if (AopUtils.isJdkDynamicProxy(proxy)) {
  8. obj = getJdkDynamicProxyTargetObject(proxy);
  9. } else { //cglib
  10. obj = getCglibProxyTargetObject(proxy);
  11. }
  12. return getTarget(obj);
  13. }
  14. /**
  15. * 多层代理的时候递归到原始对象 获取 目标对象
  16. *
  17. * @param proxy 代理对象
  18. * @return
  19. * @throws Exception
  20. */
  21. @Deprecated
  22. public static Class<?> getTargetClass(Class<?> proxy) throws Exception {
  23. if (!isProxyClass(proxy)) {
  24. //不是代理对象
  25. return proxy;
  26. }
  27. return getTargetClass(proxy.getSuperclass());
  28. }
  29. public static boolean isProxyClass(Class<?> clazz) {
  30. return Proxy.isProxyClass(clazz) || AopUtils.isCglibProxy(clazz);
  31. }
  32. /**
  33. * @param proxy
  34. * @return
  35. * @throws Exception
  36. */
  37. private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
  38. Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
  39. h.setAccessible(true);
  40. Object dynamicAdvisedInterceptor = h.get(proxy);
  41. Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
  42. advised.setAccessible(true);
  43. Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource()
  44. .getTarget();
  45. return target;
  46. }
  47. /**
  48. * 获取AOP生成的代理
  49. *
  50. * @param proxy
  51. * @return
  52. * @throws Exception
  53. */
  54. private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
  55. Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
  56. h.setAccessible(true);
  57. AopProxy aopProxy = (AopProxy)h.get(proxy);
  58. Field advised = aopProxy.getClass().getDeclaredField("advised");
  59. advised.setAccessible(true);
  60. Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
  61. return target;
  62. }

如何优雅地统计方法耗时-stopWatch

https://www.yuque.com/antone/zv3ypu/abgos9#X1sDe

如何创建内存缓存

https://www.yuque.com/antone/zv3ypu/abgos9#MsKjY

如何优雅的创建线程池

  1. public class xxx {
  2. private ThreadPoolTaskExecutor dpsQueryExecutor;
  3. /**
  4. import javax.annotation.PostConstruct;
  5. */
  6. @PostConstruct
  7. public void init() {
  8. dpsQueryExecutor = new ThreadPoolTaskExecutor();
  9. dpsQueryExecutor.setCorePoolSize(50);
  10. dpsQueryExecutor.setMaxPoolSize(500);
  11. dpsQueryExecutor.setKeepAliveSeconds(60);
  12. dpsQueryExecutor.setQueueCapacity(30);
  13. dpsQueryExecutor.setThreadFactory(new ThreadFactoryBuilder()
  14. .setNameFormat("DpsQueryExecutor-Pool-%d").build());
  15. // 对任务执行前后做一些事情,包装器
  16. dpsQueryExecutor.setTaskDecorator(runnable -> {
  17. final Object rpcContext = EagleEye.currentRpcContext();
  18. return () -> {
  19. try {
  20. // 这里是想做到 父子线程之间的trace传递,traceid不变?
  21. EagleEye.setRpcContext(rpcContext);
  22. runnable.run();
  23. } finally {
  24. EagleEye.clearRpcContext();
  25. }
  26. };
  27. });
  28. dpsQueryExecutor.initialize();
  29. }
  30. }

image.png

如何想在对象初始化后做一定的事情?

方法1:@PostConstruct

  1. import javax.annotation.PostConstruct;
  2. @PostConstruct
  3. public void init() {
  4. }

方法2:

  1. import org.springframework.beans.factory.config.BeanPostProcessor;
  2. public class ComponentHandlerMapping implements BeanPostProcessor {
  3. /**
  4. * postProcessBeforeInitialization 方法会在Bean构造完成后(构造方法执行完成),初始化方法(init-method)方法调用之前被调用
  5. *
  6. * @param bean 刚刚由Spring实例化出来的Bean
  7. * @param beanName 在Spring配置元数据中Bean的名称(id or name)
  8. * @return
  9. * @throws BeansException
  10. */
  11. @Override
  12. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  13. //如果不做任何事情,直接return bean
  14. return bean;
  15. }
  16. /**
  17. * Bean构造完成后(构造方法执行完成),初始化方法(init-method)方法完成后被调用
  18. *
  19. * @param bean
  20. * @param beanName
  21. * @return
  22. * @throws BeansException
  23. */
  24. @Override
  25. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  26. xxx
  27. }
  28. }

注意:postProcessAfterInitialization方法里bean是 spring 代理后的对象,要想获取实际目标对象,参考 https://www.yuque.com/antone/zv3ypu/vw5p0t/edit#SsW4m

方法3:

  1. import org.springframework.beans.factory.InitializingBean;
  2. public class BaseSmartInterceptor implements InitializingBean {
  3. /**
  4. * 在spring bean 被初始化后 会执行该方法,
  5. * @throws Exception
  6. */
  7. @Override
  8. public void afterPropertiesSet() throws Exception {
  9. this.init();
  10. }
  11. }

如何优雅地在完整地打印一次业务逻辑里的想追踪的trace信息

场景:在dps项目里,想打印一次请求中最终执行的sql语句
简单粗暴的方式,在最终生成sql的部分,直接log打印。
缺点:如果有更多信息要打印都要这么做,日志打印代码极不好看地掺杂在了业务逻辑之中。

解决办法:定义拦截器,同时采用ThreadLoacal存储所有trace信息(根据业务需要自定义Trace对象),在需要的时候 往ThreadLoacal set。在拦截器初始时,初始化ThreadLoacal,结束时,清空

  1. public class TraceHelper {
  2. private static final ThreadLocal<TraceInfo> TRACE_HOLDER = new InheritableThreadLocal<>();
  3. public static void set(TraceInfo traceInfo) {
  4. TRACE_HOLDER.set(traceInfo);
  5. }
  6. public static TraceInfo get() {
  7. return TRACE_HOLDER.get();
  8. }
  9. public static void clear() {
  10. TRACE_HOLDER.remove();
  11. }
  12. public static void appendSql(String sql) {
  13. try {
  14. TRACE_HOLDER.get().appendSql(sql);
  15. } catch (Throwable e) {
  16. log.error("traceError", e);
  17. }
  18. }
  19. }
  20. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
  21. @Component
  22. @Slf4j
  23. public class TraceHandlerInterceptor extends HandlerInterceptorAdapter {
  24. @Override
  25. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
  26. throws Exception {
  27. TraceHelper.set(new TraceInfo());
  28. return true;
  29. }
  30. @Override
  31. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
  32. ModelAndView modelAndView) throws Exception {
  33. log.info("trace \n{}", JSON.toJSONString(TraceHelper.get()));
  34. TraceHelper.clear();
  35. }
  36. }
  37. public class xxx {
  38. public void methodxxx() {
  39. //在这里 set trace
  40. TraceHelper.appendSql(rawSql);
  41. }
  42. }

List转数组

方式1:
把Stream的元素输出为数组和输出为List类似,我们只需要调用toArray()方法,并传入数组的“构造方法”:

  1. List<String> list = List.of("Apple", "Banana", "Orange");
  2. String[] array = list.stream().toArray(String[]::new);

方式1:

  1. list.toArray(new String[0])

为什么可以传入 0,可以看api,不够会运行时再分配
image.png

List<父类>转List<子类> List A 转List B

  1. List<AbstractMonitorObject> abstractMonitorObjectList = xxx;
  2. // 这里为什么可以写new TopicMonitorObject[0], 因为实际上会新建一个TopicMonitorObject类型的数组
  3. //然后把abstractMonitorObjectList的元素一个一个的放进去,为什么放得进去,这里前提是abstractMonitorObjectList
  4. // 里的元素 本来存的就是TopicMonitorObject类型。这里只是做一个List<?>定义上的转换
  5. return Arrays.asList(abstractMonitorObjectList.toArray(new TopicMonitorObject[0]));
  1. return Optional.ofNullable(abstractMonitorObjs)
  2. .orElse(ImmutableList.of())
  3. .stream()
  4. .map(o -> (TopicMonitorObject)o)
  5. .collect(Collectors.toList());

通用程序设计

如何快速创建一个对象、map、list

场景:写测试用例时,有时需要构建一个list,原始写法

  1. List list = new ArrayList();
  2. T t1= new T();
  3. t1.set();
  4. ...
  5. T t2 = new T();
  6. t2.set();
  7. ...
  8. list.add(t1);
  9. list.add(t2);

这样写代码繁重,如何简便?

采用google guava

构建list:ImmutableList.of(, , , ,);
image.png
构建Map,ImmutableMap.of()
image.png

如何生成一个N以内的随机整数

采用Random.nexInt(N)
而不是自己编写以下函数,弊端:

  • 极端情况下 生成的数字 可能重复、可能不够随机
    1. Math.abs(new Random().nextInt()) % N

生成随机数,以及能提升多线程情况下性能的ThreadLocalRandom

image.pngimage.png

Arrays.asList构造的list是immutable的

代码逻辑中使用断言

比如使用 Assert.notEmpty 来保证 集合不空,空则抛出异常。省略繁琐的集合判空,并抛异常的代码。

从json字符中获取array

场景

  1. {
  2. "GOC": [123, 456, 789],
  3. "CUSTOM": [213, 87]
  4. }

获取”GOC”对应的 数组,并判断是否包含某个Long值

错误的写法:

  1. JSONObject jsonObject = JSON.parseObject(monitorSceneConfig);
  2. JSONArray configIds = jsonObject.getJSONArray(monitorScene);
  3. Long currentConfigId = 123L;
  4. // 拿着当前的ID 和配置参数里的ID进行匹配
  5. if (CollectionUtils.contains(configIds.iterator(), currentConfigId)) { // 这里不能被判断为 包含,因为JSONArray返回的实际是个Integer类型,底层equals时会判为不相等
  6. ......
  7. }

正确的写法

  1. JSONArray configIds = jsonObject.getJSONArray(monitorScene);
  2. 改为
  3. List<Long> configIds= JSONObject.parseArray(jsonObject.getString(monitorScene), Long.class);

文件读写

读取文件内容,所有内容转换为大的String串

思路:BufferedReader逐一(循环)读取单行,以“\n”拼接;
编写方法:常见的while(br.read() != null) { s += br.read() + “\n” };优化方法:采用Stream,可以看到Stream的灵活使用使代码简洁,并且某些时候比较易懂

  1. // classloader 读取文件流,file path为resource目录下的相对路径
  2. InputStream inputStream = XXX.class.getResourceAsStream("file path");
  3. BufferedReader br = new BufferedReader(inputStream)
  4. .lines() // lines stream
  5. .collect(Collectors.joining("\n"));

方式二:继续优化

例如,Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容:

  1. try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) {
  2. ...
  3. }

此方法对于按行遍历文本文件十分有用。

以上方法位于 JDK 1.8之后的NIO包。

方式三:

  1. java.nio.file.Files#readAllLines(java.nio.file.Path, java.nio.charset.Charset)

Path的构造方式:Paths.get(“src/test/resources/indicator/repo3.json”)

可以为相对路径,也可以为绝对路径。但都是当前操作系统下的文件路径(pwd),而不是Java工程编译后的相对路径和绝对路径。
一般当前路径为当前工程的根目录。可通过Paths.get(“.”).toAbsolutePath().toString()看下当前路径是什么,以决定相对路径如何写。

方式四:guava的Files类有更直接的方法:

  1. com.google.common.io.Files#readLines(java.io.File, java.nio.charset.Charset)

方式五:apache common库有IOUtils,应该更简单

通过自定义命令行参数执行java程序

场景:java命令行执行一个main程序,希望动态传入一些参数。核心要解决 参数的定义和参数值的传递/解析 问题。

类库:jcommanderimage.png

JSON parse相关

json字符串转换为含自定义类的对象

比如转换为Map
使用TypeReference

  1. JSON.parseObject(s, new TypeReference<Map<String, MyCustomClass>>() {})

踩坑记录

当字符串采用 | 进行分割

当字符串采用 | 进行分割,想要split时,直接采用string.spit(“|”) 会有问题,这里默认的split方法 将其作为了正则表达式进行

这时应采用 apache的 StringUtils,这也是经验,涉及到字符串操作都要采用StringUtils

这种小问题不容易发现,一旦出现可能会浪费你好几个小时,踩坑了。

时刻注意 StringUtils.split的大坑,与splitByWholeSeperator的区别

  1. public static String[] split(final String str, final String separatorChars)
  2. public static String[] splitByWholeSeparator(final String str, final String separator);

split方法是按每个字符进行分隔,而splitByWholeSeparator是按整个字符串分隔

所以split(xx, “abc”) 会按 “a” “b” “c” 均做分隔,而splitByWholeSeparator(xx, “abc”) 按”abc”整体进行分隔。

编码问题

case:老项目是GBK编码的,通过IDEA导进来后,build会出错。提示“java:编码UTF-8的不可映射字符”,类似下图的错误。
image.png

解决办法:
(1)如果想继续采用GBK编码,那么更改IDEA配置,注意只更改当前project的配置。以下位置都改为GBK。
重新build看有没有问题。
image.png
(2)倾向所有项目的文件统一采用UTF-8编码。所以想用UTF-8编码加载工程。思路:先将所有文件都转化为UTF-8编码,再用utf-8加载工程。对于单个文件,方式如下:

  • 先用 GBK形式 reload文件,确保显示正常。然后转换为utf-8image.pngimage.png
  • 以上步骤操作完后会在下面图中显示一条记录。这也是idea的环境配置。也可从这里直接添加文件编码配置。image.png

但对于多个文件,如何批量操作呢?我并没有找到好的方法。上图中可以设置directory的编码,但并没有生效于其下的所有文件。

网上搜索到其他最原始的命令行的方法,尝试基本成功,但有部分文件出现转换后部分内容丢失。

  1. #!/bin/sh
  2. for file in `find ./ -name "*.java"`;
  3. do
  4. echo convering : $file
  5. iconv -f GBK -t utf-8 $file > $file.t
  6. mv $file.t $file
  7. done
  8. echo DONE

Debug时出现断点不可打,no executable code found …..

一般就是本地代码和远程代码不一致

排查过程踩了一些坑,但最终其实还是代码不一致的原因

多线程对同一集合Sort时,会报错,注意不要直接sort原集合,而是copy一份

stackoverflow问题

场景:一段代码中包含以下集合sort语句。该代码会多线程同时执行,在每个线程中都会排序同一个集合xxx。

  1. Collections.sort(xxx)

报错信息:

  1. java.util.ConcurrentModificationException
  2. at java.util.ArrayList.sort(Unknown Source)
  3. at java.util.Collections.sort(Unknown Source)
  4. at CollectionsTS.MyRunnable.run(ArrayListTS.java:37)
  5. at java.lang.Thread.run(Unknown Source)
  6. java.util.ConcurrentModificationException
  7. at java.util.ArrayList.sort(Unknown Source)
  8. at java.util.Collections.sort(Unknown Source)
  9. at CollectionsTS.MyRunnable.run(ArrayListTS.java:37)
  10. at java.lang.Thread.run(Unknown Source)

内部类 generic array creation

我遇到了如下代码一样的错误
image.png

原因还是没太明白。内部类听恶心的,还是尽量少用,容易踩坑。

解决方案
https://codejzy.com/posts-600581.html
1、加static
image.png

2、new的时候加上外部类
image.png

mysql

为什么设置db gmt_modified字段默认值后未生效,还是得显示传值?

一般gmt_modified字段这么设置
image.png
image.png
gmt_modified字段这么设置
image.png
image.png

但db insert 遇到gmtCreate还是得显示传值,否则报sql错误image.png
原因是,insert的时候显示配置了要insert gmt_create列,而实际传的是null,当然会报错啦。解决办法:insert语句不要包含gmt_create列就好啦。

运维经验

如何修改JVM参数?build 遇到 OOM问题?

https://blog.csdn.net/ahwsk/article/details/89921425

其他

postman

需要在heade中设置Cookie参数,从浏览器的network中找一个请求,copy下cookie。注意:不能采用 浏览器里的copy value,需要直接自己选择复制
否则可能出现空白字符,实际copy成字符串后会是%%image.png