@Component
public class UserHelper implements InitializingBean {
private UserService userService;
private static UserService userService2;
public UserHelper(UserService userService) {
this.userService = userService;
}
@Override
public void afterPropertiesSet() {
setUserService2(userService);
}
private static void setUserService2(UserService userService) {
userService2 = userService;
}
public static String getUserNameById(Integer id) {
return userService2.getUserNameById(id);
}
}
当你看到上面的代码时,是不是会露出下面的表情?
你肯定会说“这是什么屎代码?”。
其实,这段代码的本意是想定义一个静态的工具类,但是这个工具类需要注入bean对象。
我相信,你肯定在工作中也遇到过类似的需求:工具类里面需要注入bean。
代码之所以演变成上面这个样子,也是有一个过程的。
最初的代码长下面这样:
@Component
public class UserHelper{
private static UserService userService;
public UserHelper(UserService userService) {
UserHelper.userService = userService;
}
public static String getUserNameById(Integer id) {
return userService.getUserNameById(id);
}
}
我相信,这才是大家熟悉的味道。
因为我们这个工具类要提供的是静态方法,因此我们需要把userService声明成类的静态属性。
而userService本身是一个bean,所以我需要把它注入进来。这里,我选的的是构造注入,并且在类上使用@Component注解。
的确,这段代码也能正常工作,可是为啥要改它呢?
如果你在idea里面安装了SonarLint,你就会发现,SonarLint认为这种写法有坏味道。
Assigning a value to a static field in a constructor could cause unreliable behavior at runtime since it will change the value for all instances of the class.
Instead remove the field’s static modifier, or initialize it statically.
意思就是不要在构造函数中对静态成员初始化,因为静态成员是属于类的,在构造方法中对静态成员初始化,可能会影响所有实例。
它建议删除静态修饰符,或者选择进行静态初始化(比如静态初始化块,或者静态方法)。
首先,我们没办法删除静态修饰符,否则静态方法就访问不到了。
那么,我们就尝试用第二种方法:我们可以定义一个setUserService的静态方法,然后在构造方法方法中调用这个方法给userService赋值。
@Component
public class UserHelper{
private static UserService userService;
public UserHelper(UserService userService) {
setUserService(userService);
}
public static void setUserService(UserService userService) {
UserHelper.userService = userService;
}
public static String getUserNameById(Integer id) {
return userService.getUserNameById(id);
}
}
正当我满心欢喜的认为问题解决了时,SonarLint又给我当头棒喝——我们还有一个问题没有解决。
Utility classes should not have public constructors
意思就是工具类不应该public的构造方法。
的确,它是对的,工具类确实不需要实例化成员,因此也就不需要声明一个public的构造方法。
但是我们又确实需要这个public构造方法,进行构造注入,咋办呢?
换成setter注入行不行呢?
@Component
public class UserHelper{
private static UserService userService;
@Autowired
private void setUserService(UserService userService) {
UserHelper.userService = userService;
}
public static String getUserNameById(Integer id) {
return userService.getUserNameById(id);
}
}
不幸的是有来一个新的问题。
Instance methods should not write to "static" fields
这下我们明白了,无论构造方法还是实例方法, 都是不建议来设置静态成员的——道理都是一样,都是为了防止某个实例的行为,影响全部其他实例。
回过头我们再来看看最开始的代码:
@Component
public class UserHelper {
private UserService userService;
public UserHelper(UserService userService) {
this.userService = userService;
}
我们把userService改成了实例方法,用构造注入。但是这个userService没办法被静态方法访问到。咋办呢?我们再定义一个静态的userService2。只需要把userService赋值给userService2就行了。所以这里我们实现了InitializingBean接口。
但是我们并不能直接在afterPropertiesSet方法里面给userService2赋值,因为afterPropertiesSet是实例方法,不要再实例方法里面设置静态成员。
所以我们需要定义一个静态方法setUserService2,通过它给userService2复制。
@Component
public class UserHelper implements InitializingBean {
private UserService userService;
private static UserService userService2;
public UserHelper(UserService userService) {
this.userService = userService;
}
@Override
public void afterPropertiesSet() {
setUserService2(userService);
}
private static void setUserService2(UserService userService) {
userService2 = userService;
}
public static String getUserNameById(Integer id) {
return userService2.getUserNameById(id);
}
}
其实本质上,setUserService2还是在实例方法里面给静态成员赋值。只是这样能绕过sonar的检测而已。
虽然上面的这段代码,sonar没有检测出问题,但是显然它不是好代码——它实在是太绕了。
我们应该反问自己,为什么我一定要使用静态方法?
在没有IOC容器之前,使用静态方法很方便,因为不需要创建实例。但是spring会帮我们创建bean对象,因此我们没有必要固执于静态方法或者说工具类。
我们应该在定义这个UserHelper之初就把它定义成一个常规的bean:
@Component
public class UserHelper {
private UserService userService;
public UserHelper(UserService userService) {
this.userService = userService;
}
public String getUserNameById(Integer id) {
return userService.getUserNameById(id);
}
}
如果你说,我的代码里面已经用了静态方法了,我不想改了,那么你可以这样。
public class UserHelper {
private UserHelper() {}
private static UserService userService;
private static UserService getUserService() {
if (userService == null) {
userService = Objects.requireNonNull(ContextLoader.getCurrentWebApplicationContext())
.getBean(UserService.class);
}
return userService;
}
public static String getUserNameById(Integer id) {
return getUserService().getUserNameById(id);
}
}