原来屎代码也有它的道理

@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);
    }
}

当你看到上面的代码时,是不是会露出下面的表情?

8869e691e6b02c230ab9d2b9aa400488.jpg

你肯定会说“这是什么屎代码?”。

其实,这段代码的本意是想定义一个静态的工具类,但是这个工具类需要注入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

e9ba151f8252cbb54daded89f7eb6a3c.jpg

这下我们明白了,无论构造方法还是实例方法, 都是不建议来设置静态成员的——道理都是一样,都是为了防止某个实例的行为,影响全部其他实例。

 回过头我们再来看看最开始的代码:

@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);
    }
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值