<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.1.2.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.yamlgroupId>
<artifactId>snakeyamlartifactId>
<version>1.25version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>c3p0groupId>
<artifactId>c3p0artifactId>
<version>0.9.1.2version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.8version>
dependency>
dependencies>
在com.execise包里创建核心配置类AppConfig
@Configuration
@ComponentScan("com.execise")
public class AppConfig {
}
接口
package com.execise.service;
public interface UserService {
void add();
}
实现
package com.execise.service.impl;
import com.execise.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("调用了UserServiceImpl的add方法~!~!");
}
}
package com.execise.test;
import com.execise.config.AppConfig;
import com.execise.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestUserServiceImpl {
//把工厂给注入进来
@Autowired
private ApplicationContext context;
@Test
public void testBeanNames(){
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println("name = " + name);
}
}
}
这个注解的作用就是用来扫描指定包下的所有类。如果哪个类身上打了注解(@Controller | @Service | @Repository | @Component),就被spring给管理起来。默认情况下Spring管理这些对象的时候,**他们的id名字就是类的名字,但是第一个字母小写。**我们是否可用修改这种命名策略呢?
BeanName生成策略
说明
默认的BeanName生成策略:
如果注册bean时指定了id/name,以配置的id/name作为bean的名称
如果没有指定id/name,则以类名首字母小写作为bean的名称
在模块化开发中,多个模块共同组成一个工程。
可能多个模块中,有同名称的类,按照默认的BeanName生成策略,会导致名称冲突。
这个时候可以自定义beanname生成策略解决问题
@ComponentScan的nameGenerator属性,可以配置自定义的BeanName生成策略,步骤:
创建Java类,实现BeanNameGenerator接口,定义BeanName生成策略
在注解@ComponentScan中,使用nameGenerator属性指定生成策略即可
示例
BeanNameGenerator接口,定义BeanName生成策略package com.execise.demo1_componentscan;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
/*
自定义的id生成策略
1. id的生成靠的是generateBeanName的返回值
2. 这个方法的返回值是什么,id就是什么。
*/
public class MyBeanNameGenerator implements BeanNameGenerator {
/**
* 用来创建id值
* @param beanDefinition 表示(包装)现在正在扫描(处理)的类
* @param beanDefinitionRegistry
* @return 对象的id值
*/
public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
//1 得到现在正在扫描到的这个类的全路径地址:
String className = beanDefinition.getBeanClassName();
System.out.println("className = " + className);
//2. 返回的是全路径。
return className;
}
}
@ComponentScan中,使用nameGenerator指定生成策略@Configuration
@ComponentScan(value = "com.execise" , nameGenerator = MyBeanNameGenerator.class) //扫描包 & 命名策略
public class AppConfig {
}
扫描规则过滤器
说明
@ComponentScan默认的扫描规则:
@Component及衍生注解(@Controller,@Service,@Repository)配置的bean@ComponentScan注解也可以自定义扫描规则,来包含或排除指定的bean。步骤:
创建Java类,实现TypeFilter接口,重写match方法
使用@ComponentScan注解的属性,配置过滤规则:
includeFilter:用于包含指定TypeFilter过滤的类,符合过滤规则的类将被扫描
excludeFilter:用于排除指定TypeFilter过滤的类,符合过滤规则的类将被排除
示例1-根据注解过滤
哪个类身上有指定的注解,那么就忽略它 , 不扫描。这是按照注解的名字来忽略的。
@Configuration // 这是核心配置类
@ComponentScan(value = "com.execise" , nameGenerator = MyBeanNameGenerator.class,
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION , classes = Service.class)
public class AppConfig {
}
示例2-根据指定类过滤
@Configuration // 这是核心配置类
@ComponentScan(value = "com.execise" , nameGenerator = MyBeanNameGenerator.class,
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE , classes = UserServiceImpl.class) // 按照类不扫描
public class AppConfig {
}
示例3-自定义过滤
TypeFilter接口,重写match方法package com.execise.demo1_componentscan;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
/*
这是自定扫描(包含|排除)的规则
*/
public class MyTypeFilter implements TypeFilter {
/**
* 用于设置什么样的规则就排除|包含 类
* @param metadataReader
* @param metadataReaderFactory
* @return true: 即表示匹配规则, false: 即表示不匹配规则。
* @throws IOException
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//1. 得到现在正要被检查的类的元数据
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//2. 获取现在的类的全路径名字
String className = classMetadata.getClassName();
//如果哪个类的名字包含了User,那么就返回true
return className.contains("User");
}
}
@ComponentScan,配置过滤规则@ComponentScan(value = "com.execise" , nameGenerator = MyBeanNameGenerator.class,
excludeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM , classes = MyTypeFilter.class) // 按照自定义的规则来忽略类不扫描!
)
public class AppConfig {
}
yml配置文件介绍
以前学习过的常用配置文件有xml和properties两种格式,但是这两种都有一些不足:
properties:
优点:键值对的格式,简单易读
缺点:不方便表示复杂的层级
xml:
优点:层次结构清晰
缺点:配置和解析语法复杂
springboot采用了一种新的配置文件:yaml(或yml),它综合了xml和properties的优点。
yaml are't markup language => yaml
使用空格表示层次关系:相同空格的配置项属于同一级
配置格式是key:空格value,键值对之间用:空格表示
yaml文件示例:
jdbc:
driver: com.mysql.jdbc.Driver # 注意:英文冒号后边必须有一个空格
url: jdbc:mysql:///spring
username: root
password: root
jedis:
host: localhost
port: 6379
使用
@PropertySource加载yml
说明
@PropertySource可以使用factory属性,配置PropertySourceFactory,用于自定义配置文件的解析
步骤:
创建yaml文件:application.yml
导入依赖snakeyaml,它提供了解析yml文件的功能
创建Java类,实现PropertySourceFactory接口,重写createPropertySource方法
使用@PropertySource注解,配置工厂类
示例
resources目录里创建yaml文件:application.ymljdbc:
driver: com.mysql.jdbc.Driver # 注意:英文冒号后边必须有一个空格
url: jdbc:mysql:///spring
username: root
password: root
jedis:
host: localhost
port: 6379
pom.xml增加导入依赖snakeyaml,它提供了解析yml文件的功能<dependency>
<groupId>org.yamlgroupId>
<artifactId>snakeyamlartifactId>
<version>1.25version>
dependency>
PropertySourceFactory接口,重写createPropertySource方法public class YamlSourceFactory implements PropertySourceFactory {
/**
* 解析yaml配置文件
* @param name 名称
* @param resource 配置文件EncodedResource对象
* @return PropertySource
*/
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
//1. 创建yaml解析的工厂
YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
//2. 设置要解析的资源内容
factoryBean.setResources(resource.getResource());
//3. 把资源文件解析成Properties对象
Properties properties = factoryBean.getObject();
//4. 把properties封装成PropertySource对象并返回
return new PropertiesPropertySource("application", properties);
}
}
@PropertySource注解,配置工厂类@Configuration("appConfig")
@PropertySource(value = "application.yml" , factory = YamlSourceFactory.class) //导入yml文件
public class AppConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
public void show(){
System.out.println("driver = " + driver);
System.out.println("url = " + url);
System.out.println("username = " + username);
System.out.println("password = " + password);
}
}
package com.execise.test;
import com.execise.config.AppConfig;
import com.execise.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestAppConfig {
@Autowired
private AppConfig config;
@Test
public void testShow(){
config.show();
}
}
注册bean的方式
如果要注解方式配置一个bean,可以如下方式:
@Component,@Controller, @Service, @Repository:只能用于自己编写的类上,jar包里的类不能使用(比如ComboPooledDataSource)@Bean:把方法返回值配置注册成bean到IoC容器,通常用于注册第三方jar里的bean@Import:
@Import(类名.class),注册的bean的id是全限定类名@Import(自定义ImportSelector.class):把自定义ImportSelector返回的类名数组,全部注册bean@Import(自定义ImportBeanDefinitionRegister.class):在自定义ImportBeanDefinitionRegister里手动注册beanImportSelector导入器
示例1-直接导入注册bean
使用@import来到一个类
@Configuration
@ComponentScan("com.execise")
@Import(Teacher.class)
public class AppConfig {
}
示例2-使用ImportSelector注册bean
导入器
package com.execise.demo3_import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
//自定义导入选择器
public class MyImportSelector implements ImportSelector {
/**
* 用于控制到底导入那个类到spring的容器|工厂里面
* @param annotationMetadata
* @return 数组,数组里面包含类的全路径地址。
*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{Teacher.class.getName() , Student.class.getName()};
}
}
示例
@Configuration
@ComponentScan("com.execise")
@Import(MyImportSelector.class)
public class AppConfig {
...
}
示例3-ImportSelector的高级使用
说明
pringboot框架里有大量的@EnableXXX注解,底层就是使用了ImportSelector解决了模块化开发中,如何启动某一模块功能的问题
例如:
我们开发了一个工程,要引入其它的模块,并启动这个模块的功能:把这个模块的bean进行扫描装载

步骤:
创建一个Module:module_a
在包com.a里创建几个Bean
创建核心配置文件AConfig,扫描com.a
定义一个ImportSelector:AImportSelector,导入AConfig类
定义一个注解@EnableModuleA
在我们的Module的核心配置文件AppConfig上增加注解:@EnableModuleA
引入module_a的坐标
测试能否获取到Module a里的bean
第一步:创建新Module:module_a
<groupId>com.execisegroupId>
<artifactId>spring_module_aartifactId>
<version>1.0-SNAPSHOTversion>

com.a.beans里创建类 DemoBeanA @Component
public class DemoBeanA {
}
AConfig@Configuration
@ComponentScan("com.a")
public class AConfig {
}
ImportSelector接口,重写selectImports方法public class AImportSelector implements ImportSelector {
/**
* @param importingClassMetadata。被@Import注解标注的类上所有注解的信息
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{AConfig.class.getName()};
}
}
@EnableModuleA@Import(AImportSelector.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableModuleA {
}
第二步:引入module_a,启动模块a
<dependency>
<groupId>com.execisegroupId>
<artifactId>spring_module_aartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
@Configuration
@EnableModuleA
public class AppConfig {
}
@Test
public void testBean(){
//在我们自己的工程里,可以获取到module_a里的bean
DemoBeanA demoBeanA = app.getBean(DemoBeanA.class);
System.out.println(demoBeanA);
}
ImportBeanDefinitionRegister注册器
说明
ImportBeanDefinitionRegister提供了更灵活的注册bean的方式
AOP里的@EnableAspectJAutoProxy就使用了这种注册器,用于注册不同类型的代理对象
步骤:
创建注册器:
创建Java类,实现ImportBeanDefinitionRegister接口,重写registerBeanDefinitions方法
在核心配置类上,使用@Import配置注册器
示例
com.other.Otherpublic class Other {
public void show(){
System.out.println("Other.show....");
}
}
public class CustomImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 当前类的注解信息
* @param registry 用于注册bean的注册器
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//获取bean定义信息
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition("com.other.Other").getBeanDefinition();
//注册bean,方法参数:bean的id,bean的定义信息
registry.registerBeanDefinition("other", beanDefinition);
}
}
@Import配置注册器@Configuration
@Import({CustomImportBeanDefinitionRegister.class})
public class AppConfig {
}
@Test
public void testImportRegister(){
//获取扫描范围外,使用ImportBeanDefinitionRegister注册的bean
Other other = app.getBean("other",Other.class);
other.showOther();
}
说明
@Conditional 一般是搭配@Bean注解使用,用于表示设定一个条件,如果条件满足,spring就会管理方法的返回值!
符合Condition条件的,Spring会生成bean对象 存储容器中
不符合Condition条件的,不会生成bean对象
示例:
有一个类Person(姓名name,年龄age)
如果当前操作系统是Linux:就创建Person(linus, 62)对象,并注册bean
如果当前操作系统是Windows:就创建Person(BillGates, 67)对象,并注册bean
步骤
创建Person类
创建两个Java类,都实现Condition接口:
WindowsCondition:如果当前操作系统是Windows,就返回true
LinuxCondition:如果当前操作系统是Linux,就返回true
在核心配置类里创建两个bean
一个bean名称为bill,加上@Conditional(WindowsCondition.class)
一个bean名称为linus,加上@Conditional(LinuxCondition.class)
示例
package com.execise.demo4_conditional;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private int age;
}
Condition接口package com.execise.demo4_conditional;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/*
判断当前的操作系统是不是windows
*/
public class WindowsCondition implements Condition {
/*
返回true: 即表明是windows操作系统
返回false : 即表示不是windows系统
*/
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//1. 得到当前的环境对象
Environment environment = conditionContext.getEnvironment();
//2. 得到当前系统的名字
String osName = environment.getProperty("os.name");
return osName.contains("Windows");
}
}
package com.execise.demo4_conditional;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/*
判断当前的操作系统是不是Linux
*/
public class LinuxCondition implements Condition {
/*
返回true: 即表明是Linux操作系统
返回false : 即表示不是Linux系统
*/
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//1. 得到当前的环境对象
Environment environment = conditionContext.getEnvironment();
//2. 得到当前系统的名字
String osName = environment.getProperty("os.name");
return osName.contains("Linux");
}
}
@Configuration
@ComponentScan("com.execise")
public class AppConfig {
...
//=========================以下代码使用@Conditional来判断创建对象============================
@Bean
@Conditional(WindowsCondition.class)
public Person windows(){
System.out.println("创建了windows对象!~");
return new Person("比尔", 75);
}
@Bean
@Conditional(LinuxCondition.class)
public Person linux(){
System.out.println("创建了linux对象!~");
return new Person("林纳斯", 62);
}
...
}
package com.execise.test;
import com.execise.config.AppConfig;
import com.execise.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestUserServiceImpl {
//把工厂给注入进来
@Autowired
private ApplicationContext context;
@Test
public void testBeanNames(){
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println("name = " + name);
}
}
}
执行一次单元测试方法之后,按照以下方式,可以通过JVM参数的方式,设置os.name的参数值。
设置之后,再次执行单元测试方法


Conditional的扩展注解
@Conditional在springboot里应用非常多,以下列出了一些@Conditional的扩展注解:
@ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
@ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
@ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
@ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。
@ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
@ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
@ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
@ConditionalOnExpression:基于SpEL表达式的条件判断。
@ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
@ConditionalOnResource:当类路径下有指定的资源时触发实例化。
@ConditionalOnJndi:在JNDI存在的条件下触发实例化。
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。
说明
在开发中,我们编写的工程通常要部署不同的环境,比如:开发环境、测试环境、生产环境。不同环境的配置信息是不同的,比如:数据库配置信息;如果每次切换环境,都重新修改配置的话,会非常麻烦,且容易出错
针对这种情况,Spring提供了@Profile注解:可以根据不同环境配置不同的bean,激活不同的配置
@Profile注解的底层就是@Conditional例如:
定义三个数据源:
开发环境一个DataSource,使用@Profile配置环境名称为dev
测试环境一个DataSource,使用@Profile配置环境名称为test
生产环境一个DataSource,使用@Profile配置环境名称release
在测试类上,使用@ActiveProfiles激活哪个环境,哪个环境的数据源会生效
示例
pom.xml中增加导入依赖mysql驱动, c3p0<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>c3p0groupId>
<artifactId>c3p0artifactId>
<version>0.9.1.2version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
@Profile@Configuration
public class AppConfig {
//生成三个数据源
@Bean
@Profile("dev")
public DataSource devDataSource() throws PropertyVetoException {
System.out.println("dev 开发环境的");
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql:///devdb");
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setMaxPoolSize(20);
return dataSource;
}
@Bean
@Profile("test")
public DataSource testDataSource() throws PropertyVetoException {
System.out.println("test 测试环境的");
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql:///testdb");
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setMaxPoolSize(20);
return dataSource;
}
@Bean
@Profile("release")
public DataSource releaseDataSource() throws PropertyVetoException {
System.out.println("release 生产环境的");
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql:///releasedb");
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setMaxPoolSize(20);
return dataSource;
}
}
@ActiveProfiles激活哪个环境,哪个环境的数据源会生效或者使用JVM参数
-Dspring.profiles.active=dev
package com.execise.test;
import com.execise.config.AppConfig;
import com.execise.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ActiveProfiles("dev")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestUserServiceImpl {
//把工厂给注入进来
@Autowired
private ApplicationContext context;
@Test
public void testBeanNames(){
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println("name = " + name);
}
}
}