一、 需要动态实现bean注册原因
- 在普通spring项目升级为springboot项目,由于需要针对springmvc中的过滤器(Filter)需要控制调用顺序;
- 找了一些资料添加@Order注解,实现Ordered接口发现定义的顺序号不生效。
- 通过手动注册的 org.springframework.boot.web.servlet.FilterRegistrationBean 去设置过滤器,发现里面设置的顺序号,能够控制过滤器调用执行顺序;
@Bean
public FilterRegistrationBean filterRegistrationBean1(){
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(过滤器2);
registration.setOrder(1);
return registration;
}
@Bean
public FilterRegistrationBean filterRegistrationBean2(){
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(过滤器2);
registration.setOrder(2);
return registration;
}
- 上述代码注册进去后可以保证过滤器的执行顺序,但是存在一个问题没加一个过滤器都要写一个Bean(FilterRegistrationBean ),比较麻烦;
- 所有考虑通过注解动态注入Bean(FilterRegistrationBean ),只需要写一个类配置一下即可。
- 实现依赖spring中两个接口(org.springframework.context.annotation.ImportBeanDefinitionRegistrar、org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor)
- 这两个接口实现一个都能实现动态注册,会有一点区别,想要写成一个starter,使用时自动装配。
二、 通过实现org.springframework.context.annotation.ImportBeanDefinitionRegistrar
- 自定义实现工厂bean(FactoryBean),生成bean对应使用,主要使用里面的getObject方法
- 自定义类实现ImportBeanDefinitionRegistrar,实现registerBeanDefinitions方法:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false, this.environment){
@Override
protected boolean isCandidateComponent(
AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent()) {
if (!beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
}
return isCandidate;
}
};
scanner.setResourceLoader(this.resourceLoader);
AnnotationTypeFilter filter = new AnnotationTypeFilter(YsxnFilter.class);
scanner.addIncludeFilter(filter);
Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(FilterConstant.DEFAULT_PACKAGE);
for (BeanDefinition beanDefinition : beanDefinitions) {
ScannedGenericBeanDefinition annotatedBeanDefinition = (ScannedGenericBeanDefinition) beanDefinition;
AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
registerWebFilter(registry, annotationMetadata);
}
}
private void registerWebFilter(BeanDefinitionRegistry registry, AnnotationMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(YsxnFilter.class.getCanonicalName());
String className = metadata.getClassName();
try {
Object o = Class.forName(className).newInstance();
if (!(o instanceof Filter)){
return;
}
BeanDefinitionBuilder bdf = BeanDefinitionBuilder.genericBeanDefinition(WebFilterFactoryBean.class);
Object initParams = attributes.get("initParams");
List<WebInitParam> initParamList = new ArrayList<>();
if (initParams != null){
AnnotationAttributes[] attrs = (AnnotationAttributes[])initParams;
for (AnnotationAttributes attr : attrs) {
initParamList.add(new WebInitParam(attr.getString("name"),
attr.getString("value"), attr.getString("description")));
}
}
bdf.addPropertyValue("filter", o)
.addPropertyValue("order", attributes.get("order"))
.addPropertyValue("displayName", attributes.get("displayName"))
.addPropertyValue("initParams", initParamList)
.addPropertyValue("filterName", attributes.get("filterName"))
.addPropertyValue("servletNames", attributes.get("servletNames"))
.addPropertyValue("urlPatterns", attributes.get("urlPatterns"))
.addPropertyValue("dispatcherTypes", attributes.get("dispatcherTypes"))
.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
AbstractBeanDefinition beanDefinition = bdf.getBeanDefinition();
registry.registerBeanDefinition(className, beanDefinition);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
三、 实现接口org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
- 同理也需要定义类实现FactoryBean
- 定义类实现接口BeanDefinitionRegistryPostProcessor实现方法postProcessBeanDefinitionRegistry,此时去调用动态注入的逻辑;
四、实现代码:springboot中Filter顺序控制
- 定义Filter注解,后面扫描到添加这个注解的类才进行注入
import javax.servlet.DispatcherType;
import javax.servlet.annotation.WebInitParam;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YsxnFilter {
int order() default 0;
String description() default "";
String displayName() default "";
WebInitParam[] initParams() default {};
String filterName() default "";
String smallIcon() default "";
String largeIcon() default "";
String[] servletNames() default {};
String[] value() default {};
String[] urlPatterns() default {};
DispatcherType[] dispatcherTypes() default {DispatcherType.REQUEST};
boolean asyncSupported() default false;
}
- 配置类,自动装配使用
import com.filter.manager.WebFilterAutoRegistryManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
@Import({FilterProperties.class})
public class EnableFilterAutoRegistryAutoConfiguration {
@Bean
public WebFilterAutoRegistryManager webFilterAutoRegistryManager(FilterProperties properties){
return new WebFilterAutoRegistryManager(properties);
}
}
- 常量
public interface FilterConstant {
String FILTER_PREFIX = "my.filter";
String DEFAULT_PACKAGE = "com.example.filter";
}
- 配置类
@Component
@ConfigurationProperties(prefix = FilterConstant.FILTER_PREFIX)
public class FilterProperties {
private List<String> scanPackages;
private boolean enabled;
public List<String> getScanPackages() {
return scanPackages;
}
public void setScanPackages(List<String> scanPackages) {
this.scanPackages = scanPackages;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
- 使用的数据封装类
public class WebInitParam {
private String name;
private String value;
private String description="";
public WebInitParam(String name, String value, String description) {
this.name = name;
this.value = value;
if (description != null){
this.description = description;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
if (description != null){
this.description = description;
}
}
}
- 注册BeanDefinition
import com.ysxn.filter.annotation.YsxnFilter;
import com.ysxn.filter.config.FilterConstant;
import com.ysxn.filter.config.FilterProperties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class WebFilterAutoRegistryManager implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, EnvironmentAware {
private FilterProperties properties;
private ResourceLoader resourceLoader;
private Environment environment;
private ApplicationContext applicationContext;
public WebFilterAutoRegistryManager(FilterProperties properties) {
this.properties = properties;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
Bindable<FilterProperties> bindable = Bindable.ofInstance(properties);
Binder binder = Binder.get(environment);
binder.bind(FilterConstant.FILTER_PREFIX, bindable);
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
registerBeanDefinitions( beanDefinitionRegistry);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
System.out.println("");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println(applicationContext);
}
public void registerBeanDefinitions(BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false, this.environment){
@Override
protected boolean isCandidateComponent(
AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent()) {
if (!beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
}
return isCandidate;
}
};
scanner.setResourceLoader(this.resourceLoader);
AnnotationTypeFilter filter = new AnnotationTypeFilter(YsxnFilter.class);
scanner.addIncludeFilter(filter);
List<String> scanPackages = properties.getScanPackages();
Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(FilterConstant.DEFAULT_PACKAGE);
if (scanPackages != null && scanPackages.size()>0){
for (String scanPackage : scanPackages) {
beanDefinitions.addAll(scanner.findCandidateComponents(scanPackage));
}
}
for (BeanDefinition beanDefinition : beanDefinitions) {
ScannedGenericBeanDefinition annotatedBeanDefinition = (ScannedGenericBeanDefinition) beanDefinition;
AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
registerWebFilter(registry, annotationMetadata);
}
}
private void registerWebFilter(BeanDefinitionRegistry registry, AnnotationMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(YsxnFilter.class.getCanonicalName());
String className = metadata.getClassName();
try {
Object o = Class.forName(className).newInstance();
if (!(o instanceof Filter)){
return;
}
BeanDefinitionBuilder bdf = BeanDefinitionBuilder.genericBeanDefinition(WebFilterFactoryBean.class);
Object initParams = attributes.get("initParams");
List<WebInitParam> initParamList = new ArrayList<>();
if (initParams != null){
AnnotationAttributes[] attrs = (AnnotationAttributes[])initParams;
for (AnnotationAttributes attr : attrs) {
initParamList.add(new WebInitParam(attr.getString("name"),
attr.getString("value"), attr.getString("description")));
}
}
bdf.addPropertyValue("filter", o)
.addPropertyValue("order", attributes.get("order"))
.addPropertyValue("displayName", attributes.get("displayName"))
.addPropertyValue("initParams", initParamList)
.addPropertyValue("filterName", attributes.get("filterName"))
.addPropertyValue("servletNames", attributes.get("servletNames"))
.addPropertyValue("urlPatterns", attributes.get("urlPatterns"))
.addPropertyValue("dispatcherTypes", attributes.get("dispatcherTypes"))
.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
AbstractBeanDefinition beanDefinition = bdf.getBeanDefinition();
registry.registerBeanDefinition(className, beanDefinition);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
- FactoryBean实现
import org.springframework.beans.factory.FactoryBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WebFilterFactoryBean implements FactoryBean<FilterRegistrationBean> {
private Filter filter;
private Integer order=0;
private String description;
private String displayName = "";
private List<WebInitParam> initParams;
private String filterName = "";
private String smallIcon = "";
private String largeIcon = "";
private List<String> servletNames;
private List<String> value;
private List<String> urlPatterns;
private List<DispatcherType> dispatcherTypes;
private Boolean asyncSupported = false;
@Override
public FilterRegistrationBean getObject() throws Exception {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(filter);
bean.setOrder(order==null?0:order);
if (urlPatterns != null){
urlPatterns.forEach(p->{
bean.addUrlPatterns(p);
});
}
if (initParams != null){
Map<String, String> params = new HashMap<>();
for (WebInitParam initParam : initParams) {
params.put(initParam.getName(), initParam.getValue());
}
bean.setInitParameters(params);
}
if (filterName != null && !"".equals(filterName)){
bean.setName(filterName);
}
if (servletNames != null){
bean.setServletNames(servletNames);
}
if (dispatcherTypes != null ){
int size = dispatcherTypes.size();
if (size>1){
DispatcherType[] dis = new DispatcherType[size-1];
for (int i = 1; i < size; i++) {
dis[i] = dispatcherTypes.get(i);
}
bean.setDispatcherTypes(dispatcherTypes.get(0), dis);
}else {
bean.setDispatcherTypes(dispatcherTypes.get(0));
}
}else {
bean.setDispatcherTypes(DispatcherType.REQUEST);
}
bean.setAsyncSupported(asyncSupported == null ? false:asyncSupported);
return bean;
}
@Override
public Class<?> getObjectType() {
return FilterRegistrationBean.class;
}
public Filter getFilter() {
return filter;
}
public void setFilter(Filter filter) {
this.filter = filter;
}
public Integer getOrder() {
return order;
}
public void setOrder(Integer order) {
this.order = order;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public List<WebInitParam> getInitParams() {
return initParams;
}
public void setInitParams(List<WebInitParam> initParams) {
this.initParams = initParams;
}
public String getFilterName() {
return filterName;
}
public void setFilterName(String filterName) {
this.filterName = filterName;
}
public String getSmallIcon() {
return smallIcon;
}
public void setSmallIcon(String smallIcon) {
this.smallIcon = smallIcon;
}
public String getLargeIcon() {
return largeIcon;
}
public void setLargeIcon(String largeIcon) {
this.largeIcon = largeIcon;
}
public List<String> getServletNames() {
return servletNames;
}
public void setServletNames(List<String> servletNames) {
this.servletNames = servletNames;
}
public List<String> getValue() {
return value;
}
public void setValue(List<String> value) {
this.value = value;
}
public List<String> getUrlPatterns() {
return urlPatterns;
}
public void setUrlPatterns(List<String> urlPatterns) {
this.urlPatterns = urlPatterns;
}
public List<DispatcherType> getDispatcherTypes() {
return dispatcherTypes;
}
public void setDispatcherTypes(List<DispatcherType> dispatcherTypes) {
this.dispatcherTypes = dispatcherTypes;
}
public boolean isAsyncSupported() {
return asyncSupported;
}
public void setAsyncSupported(boolean asyncSupported) {
this.asyncSupported = asyncSupported;
}
}
- 最后在spring.factories中添加配置,springboot自动装配灵魂配置(META-INF下创建的文件)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.filter.config.EnableFilterAutoRegistryAutoConfiguration
- application.yml 中配置需要扫描的包路径(scanPackages)
my:
filter:
enabled: true
scanPackages:
- com.core.filter
- com.core.filterd