我们都知道,Spring4在配置方式上有一个重要的变革点。在Spring4.x之前,通常使用XML配置来完成应用的基本配置,而在业务逻辑中则推荐使用注解方式。但是自从Spring4.x开始,官方推荐使用基于Java的编程配置来代替XML配置,这是一个重要的转变。
XML配置的优点在于对于老一辈程序员来说非常熟悉和简单,易于扩展,不需要重新编译就能修改应用配置参数。
XML配置的缺点是读取和解析配置文件需要耗费时间,当配置文件过多时会显得臃肿,管理起来不方便。
基于Java的编程配置的优点是结构更清晰,可读性更高,同时也节省了解析XML的时间。
基于Java的编程配置的缺点是修改应用配置参数需要重新编译。然而,在实际的生产环境中,应用配置完成后很少会随意修改,因此这并不是一个大问题。
配置类(通常命名为AppConfig),并在类名上添加@Configuration
注解,这样告诉Spring这是一个配置类,类似于XML文件。
如果有外部配置文件,可以在类名上添加@PropertySource
注解,并指定properties文件的路径。这样可以加载外部配置文件中的属性值。
在需要获取应用配置属性值的地方,可以在对应的变量上添加@Value
注解,通过${}
表达式来获取配置文件中的参数。
如果需要进行依赖注入,可以在相应的方法上添加@Bean
注解,并返回对应的Bean对象。也可以使用FactoryBean
来创建对应的Bean。
在这个例子中,我们声明了一个配置类DataSourceConfiguration
。它使用@Configuration
注解标识为配置类,@PropertySource
注解引入了一个名为application.properties
的外部文件,@ComponentScan
配置了自动扫描的包路径。
/**
* Spring 配置类
* 配置数据源,事务管理,bean,自动扫描包
*/
@Configuration // 声明该类为配置类
@PropertySource({"classpath:application.properties"}) // 引入外部文件
@ComponentScan("com.example") // 配置自动扫描包的路径
public class DataSourceConfiguration {
private String DB_USERNAME;
@Value("${database.password}")
private String DB_PASSWORD;
@Value("${database.driver}")
private String DB_DRIVER;
@Value("${database.jdbcUrl}")
private String DB_JDBC_URL;
@Bean // 数据源
public DataSource dataSource() {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(DB_USERNAME);
dataSource.setPassword(DB_PASSWORD);
dataSource.setDriverClass(DB_DRIVER);
dataSource.setJdbcUrl(DB_JDBC_URL);
return dataSource;
}
}
在配置类中,我们使用@Value
注解来注入外部属性文件中的值。具体来说,我们注入了数据库用户名、密码、驱动和JDBC URL四个属性。
我们使用@Bean
注解来定义了一个dataSource()
方法,该方法返回一个数据源对象ComboPooledDataSource
。在方法中,我们根据注入的属性值设置了数据源的相关属性,最终将数据源返回。
Bean注解:Bean注解类似于XML文件中的标签,用于将一个方法或类标记为Spring容器中的一个组件。被Bean注解修饰的方法名对应标签中的id,也可以通过Bean注解的value属性设置id的值。在Spring Boot的底层代码中广泛使用。
默认单实例:在Spring中,默认情况下,被注解为Bean的对象是单例的。即容器启动后会创建对象,并将其保存在容器中,以后每次使用时从容器中获取。
懒加载:如果希望在需要使用时才创建对象,并将其保存在容器中以供下次使用,可以使用Lazy注解修饰对象。当使用到该对象时,容器会创建并保存,以后每次使用时都从容器中获取。
多实例:如果希望每次使用时都创建一个新对象而不是使用先前创建的对象,则可以使用Scope注解。将其参数值设置为prototype,即@Scope(“prototype”)。
条件注入:如果希望根据条件选择需要注入的Bean,可以使用注解Conditional进行条件判断。Spring Boot的底层代码中广泛使用该注解。
在这个例子中,我们使用@Bean
注解来配置一个名为sampleBeanName
的Bean。我们在方法名SampleBean ()
上使用了@Bean
注解,并将其value值设置为sampleBeanName
,表示该Bean的id为sampleBeanName
。
@Bean(value="sampleBeanName")
public SampleBean sampleBean() {
return new SampleBean ();
}
在方法内部,我们返回一个SampleBean
对象。这样,在容器启动时,会创建一个名为sampleBeanName
的Bean,并将其保存在容器中,供其他组件使用。
通过这个例子,我们可以学习到如何使用@Bean
注解来配置Bean,并设置Bean的id。这种方式相当于在XML文件中使用标签来配置Bean。
当使用FilterType.ASSIGNABLE_TYPE时,你可以根据给定的类型及其子类和实现类来进行过滤。下面是一个使用FilterType.ASSIGNABLE_TYPE的过滤实现的案例:
interface Animal {
void makeSound();
}
class Dog implements Animal {
public void makeSound() {
System.out.println("Dog barks...");
}
}
class Cat implements Animal {
public void makeSound() {
System.out.println("Cat meows...");
}
}
现在,我们定义一个Filter来只扫描Animal及其子类:
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.filter.AbstractClassTestingTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
public class AssignableTypeFilter implements TypeFilter {
private final Class<?> targetType;
public AssignableTypeFilter(Class<?> targetType) {
this.targetType = targetType;
}
@Override
public boolean match(ClassMetadata metadata) {
try {
Class<?> clazz = Class.forName(metadata.getClassName());
return targetType.isAssignableFrom(clazz);
} catch (ClassNotFoundException e) {
return false;
}
}
}
然后,在@ComponentScan注解中使用FilterType.ASSIGNABLE_TYPE过滤器:
@ComponentScan(basePackages = "com.example",
includeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Animal.class)
})
public class MyApplication {
// ...
}
在上述示例中,我们定义了一个AssignableTypeFilter类,实现了TypeFilter接口,并在match()方法中通过isAssignableFrom()方法判断待扫描的类是否是给定类型或其子类或实现类。
在@ComponentScan注解中使用@ComponentScan.Filter指定type为FilterType.ASSIGNABLE_TYPE,并将Animal类作为参数传入,这样在扫描时会过滤出Animal及其子类。
@ComponentScan(value = "com.example",
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,
classes = {Controller.class, Service.class, Repository.class}),
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,
classes = {CustomAnnotation.class}),
useDefaultFilters = false)
在这段代码中,我们使用@ComponentScan注解来配置自动扫描的包路径和规则。
我们使用@ComponentScan注解的value属性来设置自动扫描的包路径,这里设置为"com.example"。
我们通过设置@ComponentScan注解的excludeFilters属性来排除某些类型的组件。使用@ComponentScan.Filter注解来指定需要排除的类型,其中type=ANNOTATION表示按照注解进行排除。具体的注解类如Controller、Service、Repository。
我们通过设置@ComponentScan注解的useDefaultFilters=false来关闭Spring默认扫描全部的功能,使includeFilters生效。
上面案例已经介绍了对应的type=ANNOTATION的案例,当使用FilterType.CUSTOM来进行过滤时,你需要自定义一个类来实现TypeFilter接口,然后根据自己的规则进行过滤。
以下是一个示例:
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.filter.AbstractClassTestingTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
public class CustomFilter implements TypeFilter {
@Override
public boolean match(ClassMetadata metadata) {
// 在这里实现自定义的过滤逻辑
// 返回 true 表示匹配成功,类将被扫描
// 返回 false 表示匹配失败,类将被排除
// 例如,只扫描带有"Service"字符串的类
String className = metadata.getClassName();
return className.contains("Service");
}
}
然后,在@ComponentScan注解中使用自定义的过滤器:
@ComponentScan(basePackages = "com.example",
excludeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = CustomFilter.class)
})
public class MyApplication {
// ...
}
在上述示例中,我们定义了一个CustomFilter类,实现了TypeFilter接口,并在match()方法中实现了自定义的过滤逻辑。在这个示例中,我们只会扫描带有"Service"字符串的类,排除其他类。在@ComponentScan注解中使用@ComponentScan.Filter指定type为FilterType.CUSTOM,并将CustomFilter类作为参数传入,这样在扫描时会应用我们定义的自定义过滤器来进行过滤。