In those years, we stepped on Java pit

1 Introduction


Chinese saying goes, called "Shibuguosan" means a person who commits the same mistake, once twice three times can be forgiven, more than three times on the unforgivable. It was pointed out that "three" is an imaginary number, used to refer to many times, so "Shibuguosan" does not include "three." As for the "Shibuguosan" package does not include the "three" may have relationships with everyone's bottom line, the domain of philosophy, beyond the scope of this article.

Write code, too, the same code "pit", the first step on called "long experience", stepped on second call "impress", stepped on third called "not long eye", step three above is called " hopeless". In this paper, the author summarizes some code pit, describes the phenomenon of the problem, the problem was analyzed, and the methods to avoid the pit. I hope everyone in everyday coding, encountered such code pit, can avoid early off.

1. Object comparison method

Objects.equals JDK1.7 method provides very easy to achieve the object of the comparison, effectively avoid tedious null pointer checks.

1.1. Symptoms

Before JDK1.7, in judging a short integer, when integer, long integer data type packaging with constant equality, we generally write:

Short shortValue = (short)12345;
System.out.println(shortValue == 12345); // true
System.out.println(12345 == shortValue); // true
Integer intValue = 12345;
System.out.println(intValue == 12345); // true
System.out.println(12345 == intValue); // true
Long longValue = 12345L;
System.out.println(longValue == 12345); // true
System.out.println(12345 == longValue); // true

After from JDK1.7, provided Objects.equals method, and recommend the use of functional programming, to change the code is as follows:

Short shortValue = (short)12345;
System.out.println(Objects.equals(shortValue, 12345)); // false
System.out.println(Objects.equals(12345, shortValue)); // false
Integer intValue = 12345;
System.out.println(Objects.equals(intValue, 12345)); // true
System.out.println(Objects.equals(12345, intValue)); // true
Long longValue = 12345L;
System.out.println(Objects.equals(longValue, 12345)); // false
System.out.println(Objects.equals(12345, longValue)); // false

== Why directly replaced Objects.equals method results in output may look different?

1.2. Analysis

Decompile first code, we obtain the statement "System.out.println (shortValue == 12345);" bytecode instructions as follows:

7   getstatic java.lang.System.out : java.io.PrintStream [22]
10  aload_1 [shortValue]
11  invokevirtual java.lang.Short.shortValue() : short [28]
14  sipush 12345
17  if_icmpne 24
20  iconst_1
21  goto 25
24  iconst_0
25  invokevirtual java.io.PrintStream.println(boolean) : void [32]

Originally, the compiler determines the pack data corresponding to the type of basic data types, and use the basic data types of comparison instructions (such as the above bytecode instruction sipush if_icmpne and the like), the compiler automatically equivalent data constants mandatory type conversion.

Why adopt Objects.equals method, the compiler does not automatically mandatory conversion constants data type? Decompile second segment of code, we obtain the statement "System.out.println (Objects.equals (shortValue, 12345));" bytecode instructions as follows:

7   getstatic java.lang.System.out : java.io.PrintStream [22]
10  aload_1 [shortValue]
11  sipush 12345
14  invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [28]
17  invokestatic java.util.Objects.equals(java.lang.Object, java.lang.Object) : boolean [33]
20  invokevirtual java.io.PrintStream.println(boolean) : void [39]

Originally, the compiler according to the literal meaning, that the constant 12345 is the default base data type int, so the package will be automatically converted into the data type Integer.

In the Java language, the default integer data type is int, the default decimal data type is double.

Let's analyze the code Objects.equals method of implementation:

public static boolean equals(Object a, Object b) {
   return (a == b) || (a != null && a.equals(b));
}

Among them, the statement "a.equals (b)" will be used to Short.equals method.

The method implemented as codes Short.equals:

public boolean equals(Object obj) {
   if (obj instanceof Short) {
       return value == ((Short)obj).shortValue();
  }
   return false;
}

Analysis achieved through the code: the corresponding sentence "System.out.println (Objects.equals (shortValue, 12345));", since the two types of inconsistencies Objects.equals parameter object, a data type packaging Short, other packaging data type Integer, so the final result of the comparison must be false. Likewise, the statement "System.out.println (Objects.equals (intValue, 12345));", because the same type of two parameters Objects.equals objects are packaged the same value and the data type Integer, the final result of the comparison It must be true.

1.3. Avoid pit method

1, to maintain good coding habits, avoid automatic conversion of data type

In order to prevent automatic data type conversion, more scientific wording is declared directly constants corresponding to the basic data types.

First piece of code can be written:

Short shortValue = (short)12345;
System.out.println(shortValue == (short)12345); // true
System.out.println((short)12345 == shortValue); // true
Integer intValue = 12345;
System.out.println(intValue == 12345); // true
System.out.println(12345 == intValue); // true
Long longValue = 12345L;
System.out.println(longValue == 12345L); // true
System.out.println(12345L == longValue); // true

The second paragraph of code can be written like this:

Short shortValue = (short)12345;
System.out.println(Objects.equals(shortValue, (short)12345)); // true
System.out.println(Objects.equals((short)12345, shortValue)); // true
Integer intValue = 12345;
System.out.println(Objects.equals(intValue, 12345)); // true
System.out.println(Objects.equals(12345, intValue)); // true
Long longValue = 12345L;
System.out.println(Objects.equals(longValue, 12345L)); // true
System.out.println(Objects.equals(12345L, longValue)); // true

2, with the development tools or plug-ins, discovered early data type mismatch

Eclipse window in question, we will see this prompt:

Unlikely argument type for equals(): int seems to be unrelated to Short
Unlikely argument type for equals(): Short seems to be unrelated to int
Unlikely argument type for equals(): int seems to be unrelated to Long
Unlikely argument type for equals(): Long seems to be unrelated to int

By FindBugs plug-in scanner, we will see this warning:

Call to Short.equals(Integer) in xxx.Xxx.main(String[]) [Scariest(1), High confidence]
Call to Integer.equals(Short) in xxx.Xxx.main(String[]) [Scariest(1), High confidence]
Call to Long.equals(Integer) in xxx.Xxx.main(String[]) [Scariest(1), High confidence]
Call to Integer.equals(Long) in xxx.Xxx.main(String[]) [Scariest(1), High confidence]

3, a unit testing routine, try to find the problem in the development stage

"Do not forget the little things as," Do not change a small unit does not need to be tested, Bug often appear in their own overconfidence code. Like this problem, as long as a unit test, it is fully discover the problem.

2. unpacking a triplet of expressions


A triplet of expressions is a fixed syntax Java Coding: "conditional expression expression 1:? 2 expression." Logic is a triplet of expressions: "If the conditional expression is true, then execute expressions 1, 2 or expression is executed."

2.1. Symptoms

boolean condition = false;
Double value1 = 1.0D;
Double value2 = 2.0D;
Double value3 = null;
Double result = condition ? value1 * value2 : value3; // 抛出空指针异常

When the conditional expression condition equal to false, directly to the target value3 assigned to Double Double object result, it stands to reason that there is no problem, why would throw a null pointer exception (NullPointerException)?

2.2. Analysis

Decompile the code, we get the statement "Double result = condition value1 * value2:? Value3;" bytecode instructions are as follows:

17  iload_1 [condition]
18  ifeq 33
21  aload_2 [value1]
22  invokevirtual java.lang.Double.doubleValue() : double [24]
25  aload_3 [value2]
26  invokevirtual java.lang.Double.doubleValue() : double [24]
29  dmul
30  goto 38
33  aload 4 [value3]
35  invokevirtual java.lang.Double.doubleValue() : double [24]
38  invokestatic java.lang.Double.valueOf(double) : java.lang.Double [16]
41  astore 5 [result]
43  getstatic java.lang.System.out : java.io.PrintStream [28]
46  aload 5 [result]

In line 33, value3 Double object loaded into the operand stack; in line 35, the method call doubleValue value3 of Double object. This time, due to the value3 is empty object null, calls the method doubleValue inevitably throws throws null pointer exception. But why should empty object value3 into the underlying data type double?

Access to relevant information, to obtain a triplet of expressions of the type conversion rules:

  1. If the same type of two expressions, the type of the return value for that type;

  2. If two different types of expressions, but not the type conversion, return type Object type;

  3. If two different types of expressions, but the type may be transformed into first data type packaging basic data type, and according to the basic data type conversion rules (byte <short (char) <int <long <float <double) used to transform the return value is the highest priority type of data base types.

According to the rules of analysis, the expression 1 (value1 * value2) after return to baseline data calculated type double, the expression 2 (value3) Returns the pack data type Double, depending on the type conversion rules ternary expressions with the judgment, the final data based on the type of return type double. Therefore, when the condition is equal to the conditional expression false, the need to empty object based on the data type conversion value3 double, so he called doubleValue value3 method throws a null pointer exception.

You can verify the type conversion rules of a triplet of expressions with the following cases:

boolean condition = false;
Double value1 = 1.0D;
Double value2 = 2.0D;
Double value3 = null;
Integer value4 = null;
// 返回类型为Double,不抛出空指针异常
Double result1 = condition ? value1 : value3;
// 返回类型为double,会抛出空指针异常
Double result2 = condition ? value1 : value4;
// 返回类型为double,不抛出空指针异常
Double result3 = !condition ? value1 * value2 : value3;
// 返回类型为double,会抛出空指针异常
Double result4 = condition ? value1 * value2 : value3;

2.3. Avoid pit method

1, to avoid a ternary expression, if-else statement can be used in place of

If there is data type arithmetic and packaging a triplet of expressions may be considered using if-else statement instead. Rewrite the code as follows:

boolean condition = false;
Double value1 = 1.0D;
Double value2 = 2.0D;
Double value3 = null;
Double result;
if (condition) {
   result = value1 * value2;
} else {
   result = value3;
}

2, to make use of the basic data types, data type conversion avoid automatic

If there is data type arithmetic and packaging a triplet of expressions may be considered using if-else statement instead. Rewrite the code as follows:

boolean condition = false;
double value1 = 1.0D;
double value2 = 2.0D;
double value3 = 3.0D;
double result = condition ? value1 * value2 : value3;

3, a unit test coverage, try to find the problem in the development stage

Like this problem, just write some unit test cases, covering some testing, it is completely identified in advance.

3. Generic Object assignment


 Java is a new feature JDK1.5 Generics introduced, which is essentially a parameterized type, that data type as a parameter.

3.1. Symptoms

In doing user data paging query, because of a clerical error writing the following code:

1、PageDataVO.java:

/** 分页数据VO类 */
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class PageDataVO<T> {
   /** 总共数量 */
   private Long totalCount;
   /** 数据列表 */
   private List<T> dataList;
}

2、UserDAO.java:

/** 用户DAO接口 */
@Mapper
public interface UserDAO {
   /** 统计用户数量 */
   public Long countUser(@Param("query") UserQueryVO query);
   /** 查询用户信息 */
   public List<UserDO> queryUser(@Param("query") UserQueryVO query);
}

3、UserService.java:

/** 用户服务类 */
@Service
public class UserService {
   /** 用户DAO */
   @Autowired
   private UserDAO userDAO;

   /** 查询用户信息 */
   public PageDataVO<UserVO> queryUser(UserQueryVO query) {
       List<UserDO> dataList = null;
       Long totalCount = userDAO.countUser(query);
       if (Objects.nonNull(totalCount) && totalCount.compareTo(0L) > 0) {
           dataList = userDAO.queryUser(query);
      }
       return new PageDataVO(totalCount, dataList);
  }
}

4、UserController.java:

/** 用户控制器类 */
@Controller
@RequestMapping("/user")
public class UserController {
   /** 用户服务 */
   @Autowired
   private UserService userService;

   /** 查询用户 */
   @ResponseBody
   @RequestMapping(value = "/query", method = RequestMethod.POST)
   public Result<PageDataVO<UserVO>> queryUser(@RequestBody UserQueryVO query) {
       PageDataVO<UserVO> pageData = userService.queryUser(query);
       return ResultBuilder.success(pageData);
  }
}

The above code compiled without any problems, but yet UserDO some secret field back to the front. Careful readers may have found, in the statement queryUser method UserService class "return new PageDataVO (totalCount, dataList);" List, we have the List <UserDO> Object dataList assigned to the PageDataVO <UserVO> of <UserVO> field dataList.

The question is: Why not report development tools compile errors it?

3.2. Analysis

Due to historical reasons, parameterized types and primitive types need to be compatible. Our son ArrayList example, to see how compatible.

Previous wording:

ArrayList list = new ArrayList();

Current wording:

ArrayList<String> list = new ArrayList<String>();

For reasons of compatibility with the previous code, pass a reference value between the various objects, the following situations will inevitably arise:

// 第一种情况
ArrayList list1 = new ArrayList<String>();
// 第二种情况
ArrayList<String> list2 = new ArrayList();

So, Java compiler for the above two types are compatible, compilation errors do not appear, but there will compile warning. However, my real development tools there have been no warning at compile time.

Let's analyze the problems we encountered while in fact hit two situations:

1, the List <UserDO> List assigned to the object, hit the first case;

2, the object is assigned to PageDataVO PageDataVO <UserVO>, hit the second case.

The net effect is this: we magically List <UserDO> object is assigned to the List <UserVO>.

Root of the problem is this: when we initialize PageDataVO objects, there is no requirement to force type checking.

3.3. Avoid pit method

1, when initializing generic objects, it is recommended to use diamond syntax

In the "Ali Baba Java Development Manual", recommended that there is a rule:

When [Recommended] set of generic definitions, JDK7 and above, use diamond syntax or full omitted. Description: diamond generic, i.e. Diamond, directly <> refers to the front-generation type already specified. Positive examples:

// <> diamond 方式
HashMap<String, String> userCache = new HashMap<>(16);
// 全省略方式
ArrayList<User> users = new ArrayList(10);

In fact, when initializing generic objects, full omission is not recommended. This will avoid type checking, resulting in the above problems.

When a generic object is initialized, it is recommended to use diamond syntax, as follows:

return new PageDataVO<>(totalCount, dataList);

Now, the question window Eclipse, we will see this error:

Cannot infer type arguments for PageDataVO<>

So we know that forgot to List <UserDO> object into a List <UserVO> objects a.

2, during the test unit, need to compare the data content

During unit testing, normal operation is an indicator, but the data is correct is more important indicators.

4. Copy Generic Attribute


Spring BeanUtils.copyProperties the method is a good method attribute copy tools used.

4.1. Symptoms

According to specification database development, database tables must contain the id, gmt_create, gmt_modified three fields. Wherein, id this field, depending on the amount of data may be used or long int (Note: Ali specifications must be long type, where for purposes of illustration, to allow or long int).

Therefore, these three fields out to define a base class BaseDO:

/** 基础DO类 */
@Getter
@Setter
@ToString
public class BaseDO<T> {
   private T id;
   private Date gmtCreate;
   private Date gmtModified;
}

For user table, we define a UserDO categories:

/** 用户DO类 */
@Getter
@Setter
@ToString
public class UserDO extends BaseDO<Long>{
   private String name;
   private String description;
}

For query interface that defines a UserVO categories:

/** 用户VO类 */
@Getter
@Setter
@ToString
public static class UserVO {
   private Long id;
   private String name;
   private String description;
}

Implement the query service user interfaces, codes are as follows:

/** 用户服务类 */
@Service
public class UserService {
   /** 用户DAO */
   @Autowired
   private UserDAO userDAO;

   /** 查询用户 */
   public List<UserVO> queryUser(UserQueryVO query) {
       // 查询用户信息
       List<UserDO> userDOList = userDAO.queryUser(query);
       if (CollectionUtils.isEmpty()) {
           return Collections.emptyList();
      }

       // 转化用户列表
       List<UserVO> userVOList = new ArrayList<>(userDOList.size());
       for (UserDO userDO : userDOList) {
           UserVO userVO = new UserVO();
           BeanUtils.copyProperties(userDO, userVO);
           userVOList.add(userVO);
      }

       // 返回用户列表
       return userVOList;
  }
}

Through testing, we will find a problem - call the customer service interface to query the value of the user ID and did not return.

[{"description":"This is a tester.","name":"tester"},...]

4.2. Analysis

id field Logically, UserDO UserVO classes and class types are Long type, not the type conversion does not exist, the assignment should normally. Try manual assignment, as follows:

for (UserDO userDO : userDOList) {
   UserVO userVO = new UserVO();
   userVO.setId(userDO.getId());
   userVO.setName(userDO.getName());
   userVO.setDescription(userDO.getDescription());
   userVOList.add(userVO);
}

After testing, the results of the above code returns to normal, successful return value of the user ID.

So, is the problem of the method BeanUtils.copyProperties tool. Debug mode operation with, BeanUtils.copyProperties tool into the interior of the method, the following data:

The original, getId method UserDO class return type is not Long type, but is generic reduction became Object type. The following methods and tools ClassUtils.isAssignable, determines whether the type Object type assigned to Long, of course, can not cause false returns attribute copy.

Why not consider the authors' first obtain the property value, and then determine whether the assignment "? Recommendations code is as follows:

Object value = readMethod.invoke(source);
if (Objects.nonNull(value) && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], value.getClass())) {
  ... // 赋值相关代码
}

4.3. Avoid pit method

1, do not blindly trust a third-party tool kit, tool kit has any problems that may exist

In Java, there are many third-party toolkits, such as: third-party tools Apache's commons-lang3, commons-collections, Google's guava ...... are very easy to use package. However, do not blindly trust a third-party toolkit, any kit are likely to be problematic.

2, if the property to be copied fewer properties may be hand-coded copy

BeanUtils.copyProperties reflection copy with attributes, the main advantage is to save code size, the main drawback is lowered resulting in program performance. Therefore, if the property to be copied fewer properties may be hand-coded copy.

3, unit test must be sure to compare the data content

After the coding, unit test must be sure to compare the data content. And must not take for granted that: Toolkit very mature, code is also very simple, problems can not arise.

5.Set heavy objects row


In the Java language, Set data structures can be used for heavy objects row, Common Set class has HashSet, LinkedHashSet and so on.

5.1. Symptoms

I wrote a helper class city, the city read data from a CSV file:

/** 城市辅助类 */
@Slf4j
public class CityHelper {
   /** 测试主方法 */
   public static void main(String[] args) {
       Collection<City> cityCollection = readCities2("cities.csv");
       log.info(JSON.toJSONString(cityCollection));
  }

/** 读取城市 */
   public static Collection<City> readCities(String fileName) {
       try (FileInputStream stream = new FileInputStream(fileName);
           InputStreamReader reader = new InputStreamReader(stream, "GBK");
           CSVParser parser = new CSVParser(reader, CSVFormat.DEFAULT.withHeader())) {
           Set<City> citySet = new HashSet<>(1024);
           Iterator<CSVRecord> iterator = parser.iterator();
           while (iterator.hasNext()) {
               citySet.add(parseCity(iterator.next()));
          }
           return citySet;
      } catch (IOException e) {
           log.warn("读取所有城市异常", e);
      }
       return Collections.emptyList();
  }

/** 解析城市 */
   private static City parseCity(CSVRecord record) {
       City city = new City();
       city.setCode(record.get(0));
       city.setName(record.get(1));
       return city;
  }

   /** 城市类 */
   @Getter
   @Setter
   @ToString
   private static class City {
       /** 城市编码 */
       private String code;
       /** 城市名称 */
       private String name;
  }
}

HashSet data structures used in the code, the city in order to avoid duplication of data, the read data of the city forced duplication.

When the input document reads as follows:

Coding, name 
010, Beijing 
020, Canton 
010, Beijing

JSON analysis result as follows:

[{"code":"010","name":"北京"},{"code":"020","name":"广州"},{"code":"010","name":"北京"}]

However, there is no city, "Beijing" in duplication.

5.2. Analysis

When adding objects to the collection Set, the first set of objects is calculated to increase the hashCode, to obtain a value used to store location based on the current object. If there is no existence of an object at that position, then the set Set consider the object does not exist in the collection, increase direct into it. In this position if an object exists, and then added to the prepared set of object methods and object equals the position comparison: If the equals method returns false, then the object set that does not exist in the collection, put the after the object in the object; if the equals method returns true, then that set of objects that already exist, it will not increase the objects in the collection. Therefore, it is determined whether the two elements of the hash table to be used to repeat the hashCode method and the equals method. hashCode method of determining a data storage location in the table, and whether the same data equals method determination table.

The above analysis of the problem, because the hashCode method and the equals method City class does not override, will use the hashCode method and the equals method of the Object class. Its implementation is as follows:

public native int hashCode();
public boolean equals(Object obj) {
   return (this == obj);
}

It can be seen: hashCode method of Object class is a native method, returns the address of the object; equals method of Object class if only the object of comparison is equal. So, for two identical Beijing data, due to the different City initialized object when parsing, resulting hashCode method and the equals method values ​​are not the same, must be considered to be Set different objects, so no duplication.

So, we rewrite the hashCode method and the equals method of the City class, as follows:

/** 城市类 */
@Getter
@Setter
@ToString
private static class City {
   /** 城市编码 */
   private String code;
   /** 城市名称 */
   private String name;

   /** 判断相等 */
   @Override
   public boolean equals(Object obj) {
       if (obj == this) {
           return true;
      }
       if (Objects.isNull(obj)) {
           return false;
      }
       if (obj.getClass() != this.getClass()) {
           return false;
      }
       return Objects.equals(this.code, ((City)obj).code);
  }

   /** 哈希编码 */
   @Override
   public int hashCode() {
       return Objects.hashCode(this.code);
  }
}

Re-test support program, JSON result parsed as follows:

[{"code":"010","name":"北京"},{"code":"020","name":"广州"}]

The result is correct, it has been on the city, "Beijing" in duplication.

5.3. Avoid pit method

1, the only time when the data is determined, may be used instead of Set List

When it is determined when the only data parsing city, there is no need for duplication operation can be directly used to store List.

List<City> citySet = new ArrayList<>(1024);
Iterator<CSVRecord> iterator = parser.iterator();
while (iterator.hasNext()) {
   citySet.add(parseCity(iterator.next()));
}
return citySet;

2, when it is determined that data is not unique, may be used instead of Map Set

When it is determined that the parsed data is not the only city, city names need to be re-discharge operations, can be stored directly Map. Why does not recommend implementing hashCode method City class, and then re-ranked using HashSet to achieve it? First of all, we do not want to model business logic in DO classes; secondly, the duplication code field in the easy to read, understand and maintain the code.

Map<String, City> cityMap = new HashMap<>(1024);
Iterator<CSVRecord> iterator = parser.iterator();
while (iterator.hasNext()) {
   City city = parseCity(iterator.next());
   cityMap.put(city.getCode(), city);
}
return cityMap.values();

3, follow the Java language specification, rewrite hashCode method and the equals method

Equals and hashCode method does not override method custom classes should not be used in the Set.

6. public proxy method


SpringCGLIB agent generates a proxy class is to be inherited class proxy, the proxy is implemented by rewriting method of a non-final class of agents. So, SpringCGLIB agent class can not be final class, proxy methods can not be final method, which is limited by the inheritance mechanism.

6.1. Symptoms

Here, for example a simple example, only the superuser have permission to delete the company, and all service functions are AOP interceptors to handle exceptions. Examples code is as follows:

1、UserService.java:

/** 用户服务类 */
@Service
public class UserService {
   /** 超级用户 */
   private User superUser;

/** 设置超级用户 */
   public void setSuperUser(User superUser) {
       this.superUser = superUser;
  }

   /** 获取超级用户 */
   public final User getSuperUser() {
       return this.superUser;
  }
}

2、CompanyService.java:

/** 公司服务类 */
@Service
public class CompanyService {
   /** 公司DAO */
   @Autowired
   private CompanyDAO companyDAO;
   /** 用户服务 */
   @Autowired
   private UserService userService;

   /** 删除公司 */
   public void deleteCompany(Long companyId, Long operatorId) {
       // 设置超级用户
       userService.setSuperUser(new User(0L, "admin", "超级用户"));

       // 验证超级用户
       if (!Objects.equals(operatorId, userService.getSuperUser().getId())) {
           throw new ExampleException("只有超级用户才能删除公司");
      }

       // 删除公司信息
       companyDAO.delete(companyId, operatorId);
  }
}

3、AopConfiguration.java:

/** AOP配置类 */
@Slf4j
@Aspect
@Configuration
public class AopConfiguration {
   /** 环绕方法 */
   @Around("execution(* org.changyi.springboot.service..*.*(..))")
   public Object around(ProceedingJoinPoint joinPoint) {
       try {
           log.info("开始调用服务方法...");
           return joinPoint.proceed();
      } catch (Throwable e) {
           log.error(e.getMessage(), e);
           throw new ExampleException(e.getMessage(), e);
      }
  }
}

When we call deleteCompany method of CompanyService, it has also throws null pointer exception (NullPointerException), because the call getSuperUser UserService class method of acquiring super user is null. However, we deleteCompany method CompanyService class, every class by setSuperUser method UserService enforce the super user stands to reason acquired by getSuperUser method UserService class of super-users should not be null. In fact, the problem is caused by the AOP proxy.

6.2. Analysis

When using SpringCGLIB proxy class, Spring creates a proxy class named UserService $$ EnhancerBySpringCGLIB $$ ????????'s. Decompile this proxy class, the following major code:

public class UserService$$EnhancerBySpringCGLIB$$a2c3b345 extends UserService implements SpringProxy, Advised, Factory {
  ......
   public final void setSuperUser(User var1) {
       MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
       if (var10000 == null) {
           CGLIB$BIND_CALLBACKS(this);
           var10000 = this.CGLIB$CALLBACK_0;
      }

       if (var10000 != null) {
           var10000.intercept(this, CGLIB$setSuperUser$0$Method, new Object[]{var1}, CGLIB$setSuperUser$0$Proxy);
      } else {
           super.setSuperUser(var1);
      }
  }
  ......
}

It can be seen that the proxy class inherits UserService class, agents setSuperUser method, but no proxy getSuperUser method. So, when we call setSuperUser method, it sets the field value is superUser original object instance; and when we call getSuperUser method to obtain a proxy object instance superUser field value. If the final modifier swap these two methods, there are also acquire root problem is the null.

6.3. Avoid pit method

1, strictly abide by the norms CGLIB proxy, proxy classes and methods are not to add final qualifier

CGLIB strictly follow the agent specification, the class of agents and methods are not to increase the final modifier, to avoid the dynamic agent operating different instances of the object (original object instances and proxy instances), resulting in a null pointer or data inconsistency problems.

2, narrow CGLIB agent class can not be a proxy class will not be proxied

Narrow CGLIB agent class can not be a proxy class, do not be a proxy, which can save memory overhead, but also can improve the efficiency of the function call.

7. Public field agent


In fastjson forced to upgrade to 1.2.60 stepped on a pit, for the development of rapid, defined in the ParseConfig:

public class ParseConfig {
   public final SymbolTable symbolTable = new SymbolTable(4096);
  ......
}

Inherit in our project in the class, while the dynamic AOP proxy, so a line of code caused a "massacre."

7.1. Symptoms


Still using the example of the last chapter, but to get, set, delete methods, the definition of a public field. Examples code is as follows:

1、UserService.java:

/** 用户服务类 */
@Service
public class UserService {
   /** 超级用户 */
   public final User superUser = new User(0L, "admin", "超级用户");
  ......
}

2、CompanyService.java:

/** 公司服务类 */
@Service
public class CompanyService {
   /** 公司DAO */
   @Autowired
   private CompanyDAO companyDAO;
   /** 用户服务 */
   @Autowired
   private UserService userService;

   /** 删除公司 */
   public void deleteCompany(Long companyId, Long operatorId) {
       // 验证超级用户
       if (!Objects.equals(operatorId, userService.superUser.getId())) {
           throw new ExampleException("只有超级用户才能删除公司");
      }

       // 删除公司信息
       companyDAO.delete(companyId, operatorId);
  }
}

3、AopConfiguration.java:

With the last chapter AopConfiguration.java.

When we call deleteCompany method CompanyService, actually throws a null pointer exception (NullPointerException). After debugging print, it found UserService of superUser variable is null. If AopConfiguration deleted, it will not appear null pointer exception, indicating that the problem is caused by the AOP proxy.

7.2. Analysis

When using SpringCGLIB proxy class, Spring creates a proxy class named UserService $$ EnhancerBySpringCGLIB $$ ????????'s. The proxy class inherits UserService class, and covers all public non-final methods UserService class. However, the proxy class does not call the base class method super; on the contrary, it will create a member of userService and point to the original UserService class object instance. Now, there are two object instances in memory: the original is a UserService object instance, another point of the agent object instance UserService. The proxy class is a virtual agent, it inherits UserService class, and has UserService the same field, but it never went to initialize and use them. So, once through this proxy class object instance access to public member variables, a default value will return null.

7.3. Avoid pit method

1, when it is determined immutable field, it can be defined as public static constants

When it is determined immutable field, it can be defined as public static constants, + and accessed with the class name field name. Field name + class name to access public static constant, regardless of the dynamic proxy class instance.

/** 用户服务类 */
@Service
public class UserService {
   /** 超级用户 */
   public static final User SUPER_USER = new User(0L, "admin", "超级用户");
  ......
}

/** 使用代码 */
if (!Objects.equals(operatorId, UserService.SUPER_USER.getId())) {
   throw new ExampleException("只有超级用户才能删除公司");
}

2, when it is determined immutable field can be defined as a private member variable

When it is determined immutable field, can be defined as a member of a private variable, there is provided a method of obtaining a public value of this variable. When a class instance is the dynamic proxy, the proxy is the proxy method calls the method to return the value of a member variable proxy class.

/** 用户服务类 */
@Service
public class UserService {
   /** 超级用户 */
   private User superUser = new User(0L, "admin", "超级用户");
   /** 获取超级用户 */
   public User getSuperUser() {
       return this.superUser;
  }
  ......
}

/** 使用代码 */
if (!Objects.equals(operatorId, userService.getSuperUser().getId())) {
   throw new ExampleException("只有超级用户才能删除公司");
}

3, follow the JavaBean coding standards, do not define a public member variables

JavaBean follow coding standards, do not define a public member variables. JavaBean specifications are as follows:

(1) JavaBean class must be a common type, property and access to public, such as: public class User {......} (2) JavaBean class must have a null constructor: the class must have a public constructor with no arguments (3) should not have a public JavaBean class instance and class variables are private, such as: private Integer id; (4) attributes should be accessed by a set of getter / setter methods.

postscript


Humans benefit from the "analogy" of thinking, giving top priority is human intelligence, whenever I come across new things, people tend to use something similar known as a reference, it can accelerate awareness of new things. The human and subject to the "outside the box" thinking, because it is known things and do not represent a new thing, and people and preconceived notions easy to form, eventually leading to misjudgment of new things.

Published 50 original articles · won praise 1711 · Views 2.24 million +

Guess you like

Origin blog.csdn.net/zl1zl2zl3/article/details/105419338