skip to Main Content

I want to start with an example to show my real problem, here is


This is a small project of distributed-lock, its purpose is to configure distributed-lock through properties file, and when using it, you don’t need to considered its impl

You can config distributed-lock properties file like this

dlock:
  list: # this is a Map List
    - namespace: redis-dlock-1
      type: redis
      config: # the config includes specific impl properties, here is redisson
        ...
    - namespace: db-dlock-1
      type: database
      config: # here is database config
        ...
    - namespace: zk-dlock-1
      type: zookeeper
      config: # here is curator config
        ...
    ...

When using distributed-lock in project, you just need import the dependency, create a config file, and using Annotation or API in project like this

// this is a user method
@DistributedLock(namespace="redis-dlock-1", tryWait=3000, fair=false)
public void invoke(Object[] params) {
  ......
}

public void invoke(Object[] params) {
  DLock lock = DLockSupport.getDLock(/* namespace = */ "db-dlock-1", /* fair = */false);
  ......
  if (lock.tryLock(3000)) {
    ...... // do bussiness
    lock.unlock(); 
  }
  ......
}

It’s easy to make and use, but as I mentioned, there are 3 impl of distributed-lock: database, zookeeper, redis, and to avoid user actively choose its impl, they are in the same project com.example.dlock:distributed-lock

distributed-lock
     |
    impl
       |
      redis
      zookeeper
      database

But I found a question, even though my project almost only use redis impl, importing distributed-lock will also lead to import database and zookeeper dependencies, that’s I don’t want to see

In another word, I want to make the lib’s impl effective only when its basic dependencies has been imported by user project. That’s mean, if there is only redisson(redis), then zookeeper and database is invalid, if there have redisson(redis) and database, then using zookeeper is invalid, if env has all impl dependencies, then you can use any impl of distributed-lock

My configuration design is like this:

@Configuration
@Import(RedisDLockConfiguration.class, 
        ZookeeperDLockConfiguration.class, 
        DatabaseDLockConfiguration.class)
public class DLockAutoConfiguration { }

@ConditionalOnClass({RedisTemplate.class, RedissonClient.class})
@EnableConfigurationProperties(RedisDLockProperties.class)
public class RedisDLockConfiguration {
    ......
}

@ConditionalOnClass({Zookeeper.class, CuratorFramework.class})
@EnableConfigurationProperties(ZookeeperDLockProperties.class) 
public class ZookeeperDLockConfiguration {
    ......
}

@ConditionalOnClass({JdbcTemplate.class, Connection.class})
@EnableConfigurationProperties(DatabaseDLockProperties.class)
public class DatabaseDLockConfiguration {
    ......
}

So in my assign, if user classpath contains RedisTemplate.class, RedissonClient.class, then redis-dlock will be enabled, same as database and zookeeper, instead if user lack some class, and it should be ignored and ok, but actually i got missing class error


My real problem just like this example, i succeed an old spring-cloud lib system and ordered to upgrade to 2021.0.X, the system source code in a ordinary module contains a ZuulFilter, so I replaced it to GlobalFilter of springcloud-gateway, I thought it would enabled at gateway-imported enviroment and disable in ordinary env, but I was wrong, it imported a lot of classes and beans of gateway, that was too heavy to accept

Is there any solution to make the goal?

2

Answers


  1. Chosen as BEST ANSWER

    Finally I got the answer!

    It's really amazing for me when I lack the knowledge, please look ConditionalOnClass

    This document said A Class value can be safely specified on @Configuration classes as the annotation metadata is parsed by using ASM before the class is loaded, that's means I can dynamicly declare beans wrapped by an Configuration Class, like this

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
      <optional>true</optional>
    </dependency>
    

    Here I declare the spring-cloud-gateway's starter as a dependency, cause it's really heavy, and my lib only have one Class that implement GlobalFilter as an optional component, which means only user's project depends on spring-cloud-gateway, it will do work.

    public class MyGlobalFilter implements GlobalFilter {
        // ......
    }
    public class MyLibAutoConfiguration {
        // omit other beans
    
        // this will cause error when lacks of spring-cloud-gateway
        @Bean
        @ConditionalOnClass(GlobalFilter.class)
        public MyGlobalFilter myGlobalFilter() {
            return new MyGlobalFilter();
        }
    }
    

    The code above will cause error when user's project depends on this lib but exclude spring-cloud-gateway. Cause Spring scaned this method and cannot found the class GlobalFitler implemented by MyGlobalFilter

    public class MyGlobalFilter implements GlobalFilter {
        // ......
    }
    public class MyLibAutoConfiguration {
        // omit other beans
    
        // this will work well whether depends on spring-cloud-gateway or not
        @Configuration
        @ConditionalOnClass(GlobalFilter.class)
        public class MyLibGatewayAutoConfiguration {
            @Bean
            public MyGlobalFilter myGlobalFilter() {
                return new MyGlobalFilter();
            }
        }
    }
    

    So the conclusion comes out, if a project is a big mess, Classes where should belong to different modules were placed in the same one, now you can use the Configuration(class and @annotation) to wrap the beans, and then they can be dynamically loaded by users' dependency enviroment


  2. Move each implementation of distributed-lock to a separated Maven module so that applications can declare a dependency only on the modules they really want to use. In the application avoid that explicit import of the configuration classes of the 3 implementations of distributed-lock: you can use @ComponentScan or @Autoconfiguration.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search