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
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 thisHere 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.
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
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
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 ofdistributed-lock
: you can use@ComponentScan
or@Autoconfiguration
.