在配置客户端信息的时候,打算采用configure方法配置,代码如下:
//自定义模式
JdbcClientDetailsServiceBuilder jdbcClientDetailsServiceBuilder = new JdbcClientDetailsServiceBuilder().dataSource(dataSource).passwordEncoder(passwordEncoder);
clients.configure(jdbcClientDetailsServiceBuilder);
代码测试发现会报错,Cannot build client services (maybe use inMemory() or jdbc()).,然后查看父类,发现这个是来自父类。
通过jdbc类找到父类ClientDetailsServiceBuilder,然后找到ClientDetailsServiceConfiguration和ClientDetailsServiceConfigurer
下面代码是ClientDetailsServiceConfiguration
默认配置了ClientDetailsServiceBuilder,也就是jdbc的父类
这里最重要的就是客户业务服务bean,INTERFACES代表要使用JDK的动态代理来创建代理对象。
所以上面代码并没有影响到这里的对象实例。
@Configuration
public class ClientDetailsServiceConfiguration {
@SuppressWarnings("rawtypes")
private ClientDetailsServiceConfigurer configurer = new ClientDetailsServiceConfigurer(new ClientDetailsServiceBuilder());
@Bean
public ClientDetailsServiceConfigurer clientDetailsServiceConfigurer() {
return configurer;
}
@Bean
@Lazy
@Scope(proxyMode=ScopedProxyMode.INTERFACES)
public ClientDetailsService clientDetailsService() throws Exception {
return configurer.and().build();
}
}
下面这个ClientDetailsServiceConfigurer类,可以发现setBuilder和this.and()就是上面动态配置中使用到的方法。
此类持有ClientDetailsServiceBuilder ,ClientDetailsServiceBuilder的子类负责通过ClientBuilder创建Client
public class ClientDetailsServiceConfigurer extends
SecurityConfigurerAdapter> {
public ClientDetailsServiceConfigurer(ClientDetailsServiceBuilder> builder) {
setBuilder(builder);
}
public ClientDetailsServiceBuilder> withClientDetails(ClientDetailsService clientDetailsService) throws Exception {
setBuilder(getBuilder().clients(clientDetailsService));
return this.and();
}
public InMemoryClientDetailsServiceBuilder inMemory() throws Exception {
InMemoryClientDetailsServiceBuilder next = getBuilder().inMemory();
setBuilder(next);
return next;
}
public JdbcClientDetailsServiceBuilder jdbc(DataSource dataSource) throws Exception {
JdbcClientDetailsServiceBuilder next = getBuilder().jdbc().dataSource(dataSource);
setBuilder(next);
return next;
}
@Override
public void init(ClientDetailsServiceBuilder> builder) throws Exception {
}
@Override
public void configure(ClientDetailsServiceBuilder> builder) throws Exception {
}
}
不管是InMemoryClientDetailsServiceBuilder 还是JdbcClientDetailsServiceBuilder都继承ClientDetailsServiceBuilder,ClientDetailsServiceBuilder持有private List clientBuilders = new ArrayList(); 保存了所有客户端信息配置。
然后看看ClientBuilder,是一个内部类,路径是org.springframework.security.oauth2.config.annotation.builders。
回到一开始说的ClientDetailsServiceConfiguration,生成的客户端业务是configurer.and().build()。点开and方法
public B and() {
return getBuilder();
}
继续看getBuilder,这里是拿到父类中的securityBuilder
protected final B getBuilder() {
if (securityBuilder == null) {
throw new IllegalStateException("securityBuilder cannot be null");
}
return securityBuilder;
}
父类的securityBuilder是什么呢?是一个继承SecurityBuilder的泛型
public abstract class SecurityConfigurerAdapter>
implements SecurityConfigurer {
private B securityBuilder;
再看看客户端client的几个方法
public ClientDetailsServiceConfigurer(ClientDetailsServiceBuilder> builder) {
setBuilder(builder);
}
public ClientDetailsServiceBuilder> withClientDetails(ClientDetailsService clientDetailsService) throws Exception {
setBuilder(getBuilder().clients(clientDetailsService));
return this.and();
}
public InMemoryClientDetailsServiceBuilder inMemory() throws Exception {
InMemoryClientDetailsServiceBuilder next = getBuilder().inMemory();
setBuilder(next);
return next;
}
public JdbcClientDetailsServiceBuilder jdbc(DataSource dataSource) throws Exception {
JdbcClientDetailsServiceBuilder next = getBuilder().jdbc().dataSource(dataSource);
setBuilder(next);
return next;
}
可以发现都存在setBuilder方法,这是一个父类SecurityConfigurerAdapter的方法,大概可以猜到点了,就是替换数据。
建造者模式梳理:
configuration配置生成一个配置类ClientDetailsServiceConfigurer,配置类继承了适配器SecurityConfigurerAdapter抽象类,适配器中包括了securityBuilder,这是来父类接口SecurityConfigurer中的SecurityBuilder(接口只是规定方法),初始化的时候存入ClientDetailsServiceBuilder。
public class ClientDetailsServiceConfigurer extends
SecurityConfigurerAdapter>
public class ClientDetailsServiceBuilder> extends
SecurityConfigurerAdapter implements SecurityBuilder
这里为什么生成ClientDetailsServiceConfigurer的bean,而不是直接用适配器呢,因为这个类要进行配置替换适配器的内容,适配器只做适配器该做的事情。
下面是适配器,private B securityBuilder就是适配器的私有内容,这个是一个builder,用于生成O,这个用于生成O是什么时候用呢,就是build方法的时候使用,build方法也就是生成bean的地方进行使用的。那么就很清楚了,先生成一个配置实例,配置实例可以实现配置类中的B,而这个B就是为了生成业务对象,所以最终获取这个B,然后生成业务对象bean就完成了最终的功能。
public abstract class SecurityConfigurerAdapter>
implements SecurityConfigurer {
private B securityBuilder;
然后回到ClientDetailsServiceBuilder,是实现SecurityBuilder。
看下面的方法,不就是跟配置类的方法一一对应的吗?客户端有内存模式,jdbc模式,业务service模式,看下面方法就很清楚了,最终都是生成了builder,然后在配置类中去替换B,而生成业务方法也在下面build方法,
public class ClientDetailsServiceBuilder> extends
SecurityConfigurerAdapter implements SecurityBuilder {
private List clientBuilders = new ArrayList();
public InMemoryClientDetailsServiceBuilder inMemory() throws Exception {
return new InMemoryClientDetailsServiceBuilder();
}
public JdbcClientDetailsServiceBuilder jdbc() throws Exception {
return new JdbcClientDetailsServiceBuilder();
}
@SuppressWarnings("rawtypes")
public ClientDetailsServiceBuilder> clients(final ClientDetailsService clientDetailsService) throws Exception {
return new ClientDetailsServiceBuilder() {
@Override
public ClientDetailsService build() throws Exception {
return clientDetailsService;
}
};
}
public ClientBuilder withClient(String clientId) {
ClientBuilder clientBuilder = new ClientBuilder(clientId);
this.clientBuilders.add(clientBuilder);
return clientBuilder;
}
@Override
public ClientDetailsService build() throws Exception {
for (ClientBuilder clientDetailsBldr : clientBuilders) {
addClient(clientDetailsBldr.clientId, clientDetailsBldr.build());
}
return performBuild();
}
从上面代码可以看出,对于入参是业务service,就直接重写了build方法,然后将业务service直接返回即可。
而对于内存模式和jdbc模式,先addClient,然后performBuild,而这个客户的信息是在withClient的时候加入的。
对于内存模式来说,通过withClient加入客户信息,然后addClient方法加入到父类builder内存,performBuild的时候保存到了内存的builder中。
对于jdbc来说,withClient和addClient是在jdbc的基础上额外加入的客户端,也就是说如果是想在数据库的基础上固定一些客户端,可以采用withClient方法加入,然后配置jdbc。
建造者模式的组成:适配器,适配器配置,父类生成器
地基:生成适配器配置类(包含了生成对应子生成器方法和替换适配器的方法)
建造:生成子生成器并替换原来的生成器,最终替换了生成的业务service
回到问题,为什么configure没有效果呢,因为这个方法是空的,并没有影响到原来的生成器,所以需要重写方法。但是ClientDetailsServiceConfiguration配置的是ClientDetailsServiceConfigurer,所以就需要剔除这个配置类,然后手写一个替换上去。个人角色这样不妥,剔除后就不会加载到这个配置,如果oauther升级后实现了这个方法,那就体会不到升级的好处了。