preface

This article is mainly to record the experience of using lambda expressions to optimize code, the space is not long, I think it is a good tip to share.

Let’s cut to the chase.

The body of the

Let’s start with this code:

@Component
public class ConfigCacheHelper {

    private final RedisHelper redisHelper;

    private final IChannelConfigMapper iChannelConfigMapper;

    @Autowired
    public ConfigCacheHelper(RedisHelper redisHelper, IChannelConfigMapper iChannelConfigMapper) {
        this.redisHelper = redisHelper;
        this.iChannelConfigMapper = iChannelConfigMapper;
    }

    public AaaChannelConfig getAaaChannelConfig(String merchantId){
        if (StringUtils.isEmpty(merchantId)){
            throw new IllegalArgumentException("Merchant number cannot be empty.");
        }
        Object obj = redisHelper.hget(RedisKey.CHANEL_CONFIG, RedisKey.AAA_CHANNEL);
        AaaChannelConfig config;
        if (obj == null){
            config = iChannelConfigMapper.selectAaaChannelConfig(merchantId);
        }
        else {
            Map<String, AaaChannelConfig> map = (Map<String, AaaChannelConfig>)obj;
            config = map.get(merchantId);
        }

        return Objects.requireNonNull(config, "Get Aaa channel configuration is empty");
    }

    public BbbChannelConfig getBbbChannelConfig(String merchantId){
        if (StringUtils.isEmpty(merchantId)){
            throw new IllegalArgumentException("Merchant number cannot be empty.");
        }
        Object obj = redisHelper.hget(RedisKey.CHANEL_CONFIG, RedisKey.BBB_CHANNEL);
        BbbChannelConfig config;
        if (obj == null){
            config = iChannelConfigMapper.selectBbbChannelConfig(merchantId);
        }
        else {
            Map<String, BbbChannelConfig> map = (Map<String, BbbChannelConfig>)obj;
            config = map.get(merchantId);
        }

        return Objects.requireNonNull(config, "Get Bbb channel configuration is empty");
    }

    public CccChannelConfig getCccChannelConfig(String merchantId, String posId, String operatorId){
        if (StringUtils.isEmpty(merchantId)){
            throw new IllegalArgumentException("Merchant number cannot be empty.");
        }
        Object obj = redisHelper.hget(RedisKey.CHANEL_CONFIG, RedisKey.CCC_CHANNEL);
        CccChannelConfig config;
        if (obj == null){
            config = iChannelConfigMapper.selectCccChannelConfig(merchantId, posId, operatorId);
        }
        else {
            Map<String, CccChannelConfig> map = (Map<String, CccChannelConfig>)obj;
            config = map.get(String.format("%s_%s_%s", merchantId, posId, operatorId));
        }

        return Objects.requireNonNull(config, "Get Ccc channel configuration is empty");
    }

    / /... The config of N channels is omitted here

}
Copy the code

I do payment, the logic of this code is very simple, is to obtain the merchant configuration of a payment channel, slow access to the database.

I didn’t see anything wrong with IDEA at first glance, and the code check plugin didn’t report any warning, but when I added a method to get the NTH channel in this class, I realized that the code was not very elegant.

Two points are summarized:

  1. Redundant StringUtils. IsEmpty (merchantId)

            if (StringUtils.isEmpty(merchantId)){
                throw new IllegalArgumentException("Merchant number cannot be empty.");
            }
    Copy the code

    There are two reasons:

    • It should be the caller’s responsibility to determine that the string is empty
    • The external business logic has long ensured that merchantId cannot be an empty string, so there is no need to judge
  2. The getXXXChannelConfig logic can be extracted as follows

        public AaaChannelConfig getAaaChannelConfig(String merchantId){
            // if (StringUtils.isEmpty(merchantId)){
            // throw new IllegalArgumentException(" Merchant number cannot be empty ");
            // }
            / / 1 ️ ⃣Object obj = redishelper. hget(rediskey. CHANEL_CONFIG, {channel key}); AaaChannelConfig config;if (obj == null) {/ / 2 ️ ⃣
                // selectBbbChannelConfig()
                // selectCccChannelConfig(merchantId, posId, operatorId)Config = iChannelConfigMapper.{obtain a channel configuration method}(...) ; }else {
                Map<String, AaaChannelConfig> map = (Map<String, AaaChannelConfig>)obj;
                / / 3 ️ ⃣
                // config = map.get(String.format("%s_%s_%s", merchantId, posId, operatorId));
                // config = map.get(merchantId);Config = map.get({channel config map key}); }return Objects.requireNonNull(config, "Get Aaa channel configuration is empty");
        }
    Copy the code

The first point is simple enough. The second point is mainly discussed. Three variables can be extracted from the above analysis:

  • Channel is the key value
  • Go to the database and get the configured function – the provider of the configuration
  • The channel configures the Map key

This way we can change our code to look like this:

    
    private <T> T getChannelConfig(String configKey, String configMapInnerKey, Supplier
       
         daoSupplier)
        {
        Object obj = redisHelper.hget(RedisKey.CHANNELPROXY_HKEY, configKey);
        T config;
        if (obj == null){
            config = daoSupplier.get();
        } else {
            Map<String, T> map = (Map<String, T>)obj;
            config = map.get(configMapInnerKey);
        }
        return Objects.requireNonNull(config, "Get channel configuration is empty, channel value:" + configKey);
    }

    public AaaChannelConfig getAaaChannelConfig(String merchantId){
        return getChannelConfig(
            RedisKey.AAA_CHANNEL, merchantId,
            () -> iChannelConfigMapper.selectAaaChannelConfig(merchantId)
        );
    }

    public BbbChannelConfig getBbbChannelConfig(String merchantId){
        return getChannelConfig(
            RedisKey.BBB_CHANNEL, merchantId,
            () -> iChannelConfigMapper.selectBbbChannelConfig(merchantId)
        );
    }

    public CccChannelConfig getCccChannelConfig(String merchantId, String posId, String operatorId){
        return getChannelConfig(
            RedisKey.CCC_CHANNEL, String.format("%s_%s_%s", merchantId, posId, operatorId),
            () -> iChannelConfigMapper.selectCccChannelConfig(merchantId, posId, operatorId)
        );
    }
Copy the code

A brief mention of Supplier, which is a functional interface provided in java.util.function to support functional programming in Java. It can be understood semantically as “provider of T”. For example, in the context above, it is the provider of the corresponding channel configuration. Similar common interfaces include:

interface parameter The return type
Predicate<T> T boolean
Consumer<T> T void
Function<T,R> T R
Supplier<T> None T
UnaryOperator<T> T T

The optimization results

After this optimization, the readability of the code is improved, and the amount of code is reduced by half under the premise of realizing the same function. (233 -> 137)

How do I throw the exception I want?

One more scenario I want to mention, because I’ve seen it before, is how do you get your Function to throw an exception?

Again, taking the code above as an example,

    private <T> T getChannelConfig(String configKey, String configMapInnerKey, Supplier
       
         daoSupplier)
        {
        Object obj = redisHelper.hget(RedisKey.CHANNELPROXY_HKEY, configKey);
        T config;
        if (obj == null) {What if I wanted this method to throw a custom BizException business exception?
            config = daoSupplier.get();
        } else {
            Map<String, T> map = (Map<String, T>)obj;
            config = map.get(configMapInnerKey);
        }
        return Objects.requireNonNull(config, "Get channel configuration is empty, channel value:" + configKey);
    }
Copy the code

I started thinking about it for a while, but it turns out that this is actually a basic knowledge of Java.

The daoSuppier argument we pass in is really nothing more than an anonymous internal implementation class of the functional interface Supplier (although the underlying implementation is different and in some ways much better than the anonymous internal class in terms of performance, readability, and usage trends)

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get(a);
}
Copy the code

An interface may declare to throw an exception, but its implementation may not. Conversely, if its implementation does throw a checked exception, the interface must explicitly declare to throw the exception.

In this case, we declare a Functional Interface if we want our Supplier to throw the exception we want.

    public interface DaoSupplier<T> {

        /**
         * Gets a result.
         *
         * @return a result
         */
        T get(a) throws BizException;
    }
Copy the code

Code modified again:


    private <T> T getChannelConfig(String configKey, String configMapInnerKey, DaoSupplier
       
         daoSupplier)
        throws BizException {
        Object obj = redisHelper.hget(RedisKey.CHANNELPROXY_HKEY, configKey);
        T config;
        if (obj == null){
            config = daoSupplier.get();
        } else {
            Map<String, T> map = (Map<String, T>)obj;
            config = map.get(configMapInnerKey);
        }
        return Objects.requireNonNull(config, "Get channel configuration is empty, channel value:" + configKey);
    }
    
    public AaaChannelConfig getAaaPayChannelConfig(String merchantId) throws BizException {
        return getChannelConfig(
                RedisKey.AAA_CHANNEL, merchantId,
                () -> Optional.ofNullable(iChannelConfigMapper.selectAliPayChannelConfig(appId))
                        .orElseThrow(BizException::new)); }Copy the code

conclusion

This article does not introduce a lot of introduction to the rationale and API of Java Lambda, because it is a personal development note, focusing on practice, introducing too much knowledge but deviating from the original intention. Hope that students unfamiliar with Java Lambda can learn the relevant materials.

If this article is helpful to you, please give me a thumbs up. This is my biggest motivation 🤝🤝🤗🤗.