Spring

3.x

如果使用annoation查找bean需要

  1. <context:component-scan base-package="com.aliyun.mpp.censor.background"/>

也可以排除一些

  1. <context:component-scan base-package="org.example">
  2. <context:include-filter type="regex" expression=".*Stub.*Repository"/>
  3. <context:exclude-filter type="annotation"
  4. expression="org.springframework.stereotype.Repository"/>
  5. </context:component-scan>

开启task的annoation

  1. <task:annotation-driven executor="executor" scheduler="scheduler" />

Tomcat

在server.xml中Connector指定了一些关于线程和可接受请,所以可能极大的影响性能
参考tomcat8.5文档

  1. <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
  2. maxThreads="4000" minSpareThreads="500" maxSpareThreads="1000" acceptCount="4000" />
  3. <Connector executor="tomcatThreadPool"
  4. port="80" protocol="HTTP/1.1"
  5. connectionTimeout="20000"
  6. redirectPort="8443" />

返回状态值

默认Java程序不会返回一个状态值,执行成功返回0,如果需要,要调用System。exit

  • Java没有无符号整形

  • char 是一个UTF-16编码的,强烈不要使用char

  • Java中boolean值是不能和整形比较的

  • strictfp 强制严格浮点计算

  • >>> 以0填充高位的位移

  • ==不能用于比较字符串

问题

  • codepoint

Jar

解压,压缩Jar

和tar非常相似
jar cvf
jar xvf

IDE里运行web application

这个是个maven工程,所以需要用maven编译成war再在tomcat里运行
Java - 图1

读取系统变量

  • 系统属性的设置: 通过JVM参数: -D属性名=值 或者在代码中通过Sytem.setProperty(String key, String value)来设置.

  • 系统属性的获取: 在Java中通过System.getProperty(String key)获取属性值.

  • Spring默认按照properties文件 jvm env variable, system variable这个顺序来查找需要替换的placeholder

JVisualVm排查性能问题

  1. 需要给tomcat catalina.sh 加一个启动选项 JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote=true -Djava.rmi.server.hostname=visualvmhost -Dcom.sun.management.jmxremote.port=6999 -Dcom.sun.management.jmxremote.rmi.port=6998 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.local.only=false

  2. visualvmhost 需要在各个主机上进行绑定,绑定到远程主机的公网IP以及内网IP

  3. 在要监控的机器上运行jstatd: jstatd -J-Djava.security.policy=jstatd.policy -J-Djava.rmi.server.hostname=visualvmhost -J-Djava.rmi.server.logCalls=true -p 6099
    对应需要一个policy文件

    1. grant codebase "file:${java.home}/../lib/tools.jar" {
    2. permission java.security.AllPermission;
    3. };
  4. 6099, 6999, 6998需要expose

  5. 打开jvisualvm

Java - 图2

Java - 图3

TOMCAT

参数解释

检查heap

  1. jmap -dump:format=b,file=dump.hprof <pid>
  2. jhat -port 9998 ./dump.hprof

Maven

问题#1:依赖的三方包依赖的包没有被导入到依赖中

maven导入包依赖于库中的pom.xml,这次碰到的是收工install的一个jar包,这个组件的pom却是空的所以没有引入
Java - 图4

Java - 图5

打包问题

  1. <dependency>
  2. <groupId>routines</groupId>
  3. <artifactId>routines</artifactId>
  4. <version>1.0</version>
  5. <scope>system</scope>
  6. <systemPath>${basedir}/lib/routines.jar</systemPath>
  7. </dependency>

这样会有个问题,在打一个war包的时候,是不会把这个jar打进去的,注意maven的文档说:
“Dependencies with the scope system are always available and are not looked up in repository. They are usually used to tell Maven about dependencies which are provided by the JDK or the VM. Thus, system dependencies are especially useful for resolving dependencies on artifacts which are now provided by the JDK, but where available as separate downloads earlier. Typical example are the JDBC standard extensions or the Java Authentication and Authorization Service (JAAS).”

所以如果不想推到一个maven repos, 那么可以添加一个本地的
mvn install:install-file -Dfile=lib/routines.jar -DgroupId=org.talend -DartifactId=routines -Dversion=1.0 -Dpackaging=jar 这样就可以在本地./m2里保存一个本地的

注意:pom.xml里打包的版本需要和-Dversion=1.0指定的一致, 不然会导致routines.jar依赖的库不会被打包到最终的war中
参考文章

强制加入依赖

默认一个jar包是不包含他的所有依赖库的,如果要可以在pom.xml中这么写

  1. <plugin>
  2. <artifactId> maven-assembly-plugin </artifactId>
  3. <configuration>
  4. <descriptorRefs>
  5. <descriptorRef>jar-with-dependencies</descriptorRef>
  6. </descriptorRefs>
  7. <archive>
  8. <manifest>
  9. <mainClass>com.aliyuncs.live.liveproducer.util.CasterMain</mainClass>
  10. </manifest>
  11. </archive>
  12. </configuration>
  13. <executions>
  14. <execution>
  15. <id>make-assembly</id>
  16. <phase>package</phase>
  17. <goals>
  18. <goal>single</goal>
  19. </goals>
  20. </execution>
  21. </executions>
  22. </plugin>

Exclude

exclude可以在导入一个包的时候去掉他的某个依赖,让他依赖于外部的某个包

JNDI

是一个通过名字寻找对象的标准
在javax.naming的包包中提供Context接口,提供了两个很好用的方法:
void bind( String name , Object object )
将名称绑定到对象。所有中间上下文和目标上下文(由该名称最终原子组件以外的其他所有组件指定)都必须已经存在。
Object lookup( String name )
检索指定的对象。如果 name为空,则返回此上下文的一个新实例(该实例表示与此上下文相同的命名上下文,但其环境可以独立地进行修改,而且可以并发访问)

例如

TOMCAT数据源配置(Server.xml和Context.xml两种方式)

Context.xml方式:

  1. <Resource name="jdbc/MySQL" auth="Container" type="javax.sql.DataSource"
  2. maxActive="100" maxIdle="30" maxWait="60" wait_timeout="18800" timeBetweenEvictionRunsMillis="300000" minEvictableIdleTimeMillis="600000"
  3. username="root" password="jdzxdb" driverClassName="com.mysql.jdbc.Driver"
  4. url="jdbc:mysql://localhost:3306/sxtele?comautoReconnect=true&amp;failOverReadOnly=false" removeAbandoned="true" removeAbandonedTimeout="60" logAbandoned="true"/>
  5. <Resource name="jdbc/db2" auth="Container" type="javax.sql.DataSource"
  6. maxActive="100" maxIdle="30" maxWait="60" wait_timeout="18800" timeBetweenEvictionRunsMillis="300000" minEvictableIdleTimeMillis="600000"
  7. username="lcgluser" password="lcgluser" driverClassName="com.ibm.db2.jcc.DB2Driver"
  8. url="jdbc:db2://133.64.46.65:50000/STEDWDB" removeAbandoned="true" removeAbandonedTimeout="60" logAbandoned="true"/>
  • name 表示指定的jndi名称
  • auth 表示认证方式,一般为Container
  • type 表示数据源床型,使用标准的javax.sql.DataSource
  • maxActive 表示连接池当中最大的数据库连接
  • maxIdle 表示最大的空闲连接数
  • maxWait 当池的数据库连接已经被占用的时候,最大等待时间
  • logAbandoned 表示被丢弃的数据库连接是否做记录,以便跟踪
  • username 表示数据库用户名
  • password 表示数据库用户的密码
  • driverClassName 表示JDBC DRIVER

url 表示数据库URL地址
注意,这里你配置的name值要和程序中使用的是一样的,比如按照这个例子,程序就应该是这样的

  1. String gENV = "java:comp/env/";
  2. Context ctx = new InitialContext();
  3. DataSource ds = (DataSource)ctx .lookup(gENV+"jdbc/mysql");
  4. Connection conn = ds.getConnection();
  5. String gENV = "java:comp/env/";
  6. Context ctx = new InitialContext();
  7. DataSource ds = (DataSource)ctx.lookup(gENV+"jdbc/db2");
  8. Connection conn = ds.getConnection();

Config

image.png

判断非空

普通:

  1. @Getter
  2. @Setter
  3. @ToString
  4. public class UserCreateVO {
  5. private String name;
  6. private Long companyId;
  7. }
  8. @Service
  9. public class UserService {
  10. public Long createUser(UserCreateVO create) {
  11. // 验证参数
  12. if (StringUtils.isBlank(create.getName())) {
  13. throw new IllegalArgumentException("用户名称不能为空");
  14. }
  15. if (Objects.isNull(create.getCompanyId())) {
  16. throw new IllegalArgumentException("公司标识不能为空");
  17. }
  18. // TODO: 创建用户
  19. return null;
  20. }
  21. }

Copy
精简:

  1. @Getter
  2. @Setter
  3. @ToString
  4. public class UserCreateVO {
  5. @NotBlank(message = "用户名称不能为空")
  6. private String name;
  7. @NotNull(message = "公司标识不能为空")
  8. private Long companyId;
  9. ...
  10. }
  11. @Service
  12. @Validated
  13. public class UserService {
  14. public Long createUser(@Valid UserCreateVO create) {
  15. // TODO: 创建用户
  16. return null;
  17. }
  18. }

避免空值判断

普通:

  1. if (userList != null && !userList.isEmpty()) {
  2. // TODO: 处理代码
  3. }

Copy
精简:

  1. if (CollectionUtils.isNotEmpty(userList)) {
  2. // TODO: 处理代码
  3. }

5.3.简化赋值语句

普通:

  1. public static final List<String> ANIMAL_LIST;
  2. static {
  3. List<String> animalList = new ArrayList<>();
  4. animalList.add("dog");
  5. animalList.add("cat");
  6. animalList.add("tiger");
  7. ANIMAL_LIST = Collections.unmodifiableList(animalList);
  8. }

Copy
精简:

  1. // JDK流派
  2. public static final List<String> ANIMAL_LIST = Arrays.asList("dog", "cat", "tiger");
  3. // Guava流派
  4. public static final List<String> ANIMAL_LIST = ImmutableList.of("dog", "cat", "tiger");

5.4.简化数据拷贝

普通:

  1. UserVO userVO = new UserVO();
  2. userVO.setId(userDO.getId());
  3. userVO.setName(userDO.getName());
  4. ...
  5. userVO.setDescription(userDO.getDescription());
  6. userVOList.add(userVO);

Copy
精简:

  1. UserVO userVO = new UserVO();
  2. BeanUtils.copyProperties(userDO, userVO);
  3. userVOList.add(userVO);

Copy
反例:

  1. List<UserVO> userVOList = JSON.parseArray(JSON.toJSONString(userDOList), UserVO.class);

Copy
精简代码,但不能以过大的性能损失为代价。例子是浅层拷贝,用不着JSON这样重量级的武器。

6.3.利用容器类简化

Java不像Python和Go,方法不支持返回多个对象。如果需要返回多个对象,就必须自定义类,或者利用容器类。常见的容器类有Apache的Pair类和Triple类,Pair类支持返回2个对象,Triple类支持返回3个对象。
普通:

  1. @Setter
  2. @Getter
  3. @ToString
  4. @AllArgsConstructor
  5. public static class PointAndDistance {
  6. private Point point;
  7. private Double distance;
  8. }
  9. public static PointAndDistance getNearest(Point point, Point[] points) {
  10. // 计算最近点和距离
  11. ...
  12. // 返回最近点和距离
  13. return new PointAndDistance(nearestPoint, nearestDistance);
  14. }

Copy
精简:

  1. public static Pair<Point, Double> getNearest(Point point, Point[] points) {
  2. // 计算最近点和距离
  3. ...
  4. // 返回最近点和距离
  5. return ImmutablePair.of(nearestPoint, nearestDistance);
  6. }

7.利用Optional

在Java 8里,引入了一个Optional类,该类是一个可以为null的容器对象。

7.1.保证值存在

普通:

  1. Integer thisValue;
  2. if (Objects.nonNull(value)) {
  3. thisValue = value;
  4. } else {
  5. thisValue = DEFAULT_VALUE;
  6. }

Copy
精简:

  1. Integer thisValue = Optional.ofNullable(value).orElse(DEFAULT_VALUE);

7.3.避免空判断

普通:

  1. String zipcode = null;
  2. if (Objects.nonNull(user)) {
  3. Address address = user.getAddress();
  4. if (Objects.nonNull(address)) {
  5. Country country = address.getCountry();
  6. if (Objects.nonNull(country)) {
  7. zipcode = country.getZipcode();
  8. }
  9. }
  10. }

Copy
精简:

  1. String zipcode = Optional.ofNullable(user).map(User::getAddress)
  2. .map(Address::getCountry).map(Country::getZipcode).orElse(null);

8.利用Stream

流(Stream)是Java 8的新成员,允许你以声明式处理数据集合,可以看成为一个遍历数据集的高级迭代器。流主要有三部分构成:获取一个数据源→数据转换→执行操作获取想要的结果。每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象,这就允许对其操作可以像链条一样排列,形成了一个管道。流(Stream)提供的功能非常有用,主要包括匹配、过滤、汇总、转化、分组、分组汇总等功能。

8.1.匹配集合数据

普通:

  1. boolean isFound = false;
  2. for (UserDO user : userList) {
  3. if (Objects.equals(user.getId(), userId)) {
  4. isFound = true;
  5. break;
  6. }
  7. }

Copy
精简:

  1. boolean isFound = userList.stream()
  2. .anyMatch(user -> Objects.equals(user.getId(), userId));

Copy

8.2.过滤集合数据

普通:

  1. List<UserDO> resultList = new ArrayList<>();
  2. for (UserDO user : userList) {
  3. if (Boolean.TRUE.equals(user.getIsSuper())) {
  4. resultList.add(user);
  5. }
  6. }

Copy
精简:

  1. List<UserDO> resultList = userList.stream()
  2. .filter(user -> Boolean.TRUE.equals(user.getIsSuper()))
  3. .collect(Collectors.toList());

Copy

8.3.汇总集合数据

普通:

  1. double total = 0.0D;
  2. for (Account account : accountList) {
  3. total += account.getBalance();
  4. }

Copy
精简:

  1. double total = accountList.stream().mapToDouble(Account::getBalance).sum();

Copy

8.4.转化集合数据

普通:

  1. List<UserVO> userVOList = new ArrayList<>();
  2. for (UserDO userDO : userDOList) {
  3. userVOList.add(transUser(userDO));
  4. }

Copy
精简:

  1. List<UserVO> userVOList = userDOList.stream()
  2. .map(this::transUser).collect(Collectors.toList());

Copy

8.5.分组集合数据

普通:

  1. Map<Long, List<UserDO>> roleUserMap = new HashMap<>();
  2. for (UserDO userDO : userDOList) {
  3. roleUserMap.computeIfAbsent(userDO.getRoleId(), key -> new ArrayList<>())
  4. .add(userDO);
  5. }

Copy
精简:

  1. Map<Long, List<UserDO>> roleUserMap = userDOList.stream()
  2. .collect(Collectors.groupingBy(UserDO::getRoleId));

Copy

8.6.分组汇总集合

普通:

  1. Map<Long, Double> roleTotalMap = new HashMap<>();
  2. for (Account account : accountList) {
  3. Long roleId = account.getRoleId();
  4. Double total = Optional.ofNullable(roleTotalMap.get(roleId)).orElse(0.0D);
  5. roleTotalMap.put(roleId, total + account.getBalance());
  6. }

Copy
精简:

  1. roleTotalMap = accountList.stream().collect(Collectors.groupingBy(Account::getRoleId, Collectors.summingDouble(Account::getBalance)));

Copy

8.7.生成范围集合

Python的range非常方便,Stream也提供了类似的方法。
普通:

  1. int[] array1 = new int[N];
  2. for (int i = 0; i < N; i++) {
  3. array1[i] = i + 1;
  4. }
  5. int[] array2 = new int[N];
  6. array2[0] = 1;
  7. for (int i = 1; i < N; i++) {
  8. array2[i] = array2[i - 1] * 2;
  9. }

Copy
精简:

  1. int[] array1 = IntStream.rangeClosed(1, N).toArray();
  2. int[] array2 = IntStream.iterate(1, n -> n * 2).limit(N).toArray();

Copy

6.1.不要使用循环拷贝数组,尽量使用System.arraycopy拷贝数组

推荐使用System.arraycopy拷贝数组,也可以使用Arrays.copyOf拷贝数组。
反例:

  1. int[] sources = new int[] {1, 2, 3, 4, 5};
  2. int[] targets = new int[sources.length];
  3. for (int i = 0; i < targets.length; i++) {
  4. targets[i] = sources[i];
  5. }

Copy
正例:

  1. int[] sources = new int[] {1, 2, 3, 4, 5};
  2. int[] targets = new int[sources.length];
  3. System.arraycopy(sources, 0, targets, 0, targets.length);

7.2.不要使用循环拷贝集合,尽量使用JDK提供的方法拷贝集合

JDK提供的方法可以一步指定集合的容量,避免多次扩容浪费时间和空间。同时,这些方法的底层也是调用System.arraycopy方法实现,进行数据的批量拷贝效率更高。
反例:

  1. List<UserDO> user1List = ...;
  2. List<UserDO> user2List = ...;
  3. List<UserDO> userList = new ArrayList<>(user1List.size() + user2List.size());
  4. for (UserDO user1 : user1List) {
  5. userList.add(user1);
  6. }
  7. for (UserDO user2 : user2List) {
  8. userList.add(user2);
  9. }

Copy
正例:

  1. List<UserDO> user1List = ...;
  2. List<UserDO> user2List = ...;
  3. List<UserDO> userList = new ArrayList<>(user1List.size() + user2List.size());
  4. userList.addAll(user1List);
  5. userList.addAll(user2List);

mvnw

我们使用Maven时,基本上只会用到mvn这一个命令。有些童鞋可能听说过mvnw,这个是啥?
mvnw是Maven Wrapper的缩写。因为我们安装Maven时,默认情况下,系统所有项目都会使用全局安装的这个Maven版本。但是,对于某些项目来说,它可能必须使用某个特定的Maven版本,这个时候,就可以使用Maven Wrapper,它可以负责给这个特定的项目安装指定版本的Maven,而其他项目不受影响。
简单地说,Maven Wrapper就是给一个项目提供一个独立的,指定版本的Maven给它使用。

安装Maven Wrapper

安装Maven Wrapper最简单的方式是在项目的根目录(即pom.xml所在的目录)下运行安装命令:

  1. mvn -N io.takari:maven:0.7.6:wrapper

它会自动使用最新版本的Maven。注意0.7.6是Maven Wrapper的版本。最新的Maven Wrapper版本可以去官方网站查看。
如果要指定使用的Maven版本,使用下面的安装命令指定版本,例如3.3.3

  1. mvn -N io.takari:maven:0.7.6:wrapper -Dmaven=3.3.3

我们只需要把mvn命令改成mvnw就可以使用跟项目关联的Maven。例如:

  1. mvnw clean package

在Linux或macOS下运行时需要加上./

  1. ./mvnw clean package

Maven Wrapper的另一个作用是把项目的mvnwmvnw.cmd.mvn提交到版本库中,可以使所有开发人员使用统一的Maven版本。

Ibatis

在myBATIS的注解中使用$value$引入变量可能存在SQL注入漏洞,存在严重安全风险。

修复方案

方案1: 使用 # 替代 $

参考代码
${conditions} ==> #{conditions}

模糊查询需要使用%,应使用concat连接:
name LIKE concat(‘%’, #search#, ‘%’)

方案2: 针对不能使用#的场景