|
|
|
@ -5873,6 +5873,665 @@ MethodInterceptor是AOP项目中的拦截器,它拦截的目标是方法,即
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## FactoryBean
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class UserFactoryBean implements FactoryBean<User> {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public User getObject() throws Exception {
|
|
|
|
|
User user = new User();
|
|
|
|
|
System.out.println("调用 UserFactoryBean 的 getObject 方法生成 Bean:" + user);
|
|
|
|
|
return user;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Class<?> getObjectType() {
|
|
|
|
|
// 这个 FactoryBean 返回的Bean的类型
|
|
|
|
|
return User.class;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**FactoryBean在Mybatis中的实践**
|
|
|
|
|
|
|
|
|
|
Mybatis的Mapper接口的动态代理也是通过FactoryBean注入到Spring中的。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class MapperFactoryBean<T> extends SqlSessionDaoSupportimplements FactoryBean<T> {
|
|
|
|
|
|
|
|
|
|
// mapper的接口类型
|
|
|
|
|
private Class<T> mapperInterface;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public T getObject() throws Exception {
|
|
|
|
|
// 通过SqlSession获取接口的动态搭理对象
|
|
|
|
|
return getSqlSession().getMapper(this.mapperInterface);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Class<T> getObjectType() {
|
|
|
|
|
return this.mapperInterface;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**FactoryBean在OpenFeign中的实践**
|
|
|
|
|
|
|
|
|
|
FeignClient接口的动态代理也是通过FactoryBean注入到Spring中的。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class FeignClientFactoryBean implements
|
|
|
|
|
FactoryBean<Object>, InitializingBean, ApplicationContextAware {
|
|
|
|
|
|
|
|
|
|
// FeignClient接口类型
|
|
|
|
|
private Class<?> type;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object getObject() throws Exception {
|
|
|
|
|
return getTarget();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Class<?> getObjectType() {
|
|
|
|
|
return type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## @Import
|
|
|
|
|
|
|
|
|
|
@Import的核心作用就是导入配置类,并且还可以根据配合(比如@EnableXXX)使用的注解的属性来决定应该往Spring中注入什么样的Bean。
|
|
|
|
|
|
|
|
|
|
### 实现ImportSelector接口
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class UserImportSelector implements ImportSelector {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
|
|
|
|
|
System.out.println("调用 UserImportSelector 的 selectImports 方法获取一批类限定名");
|
|
|
|
|
return new String[]{"com.sanyou.spring.extension.User"};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Import(UserImportSelector.class)
|
|
|
|
|
public class Application {
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
AnnotationConfigApplicationContext applicationContext =
|
|
|
|
|
new AnnotationConfigApplicationContext();
|
|
|
|
|
//将 Application 注册到容器中
|
|
|
|
|
applicationContext.register(Application.class);
|
|
|
|
|
applicationContext.refresh();
|
|
|
|
|
System.out.println("获取到的Bean为" + applicationContext.getBean(User.class));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 运行结果如下:
|
|
|
|
|
// 调用 UserImportSelector 的 selectImports 方法获取一批类限定名
|
|
|
|
|
// 获取到的Bean为com.sanyou.spring.extension.User@282003e1
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 实现ImportBeanDefinitionRegistrar接口
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class UserImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
|
|
|
|
|
//构建一个 BeanDefinition , Bean的类型为 User
|
|
|
|
|
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class)
|
|
|
|
|
// 设置 User 这个Bean的属性username的值为张小凡
|
|
|
|
|
.addPropertyValue("username", "张小凡")
|
|
|
|
|
.getBeanDefinition();
|
|
|
|
|
|
|
|
|
|
System.out.println("往Spring容器中注入User");
|
|
|
|
|
//把 User 这个Bean的定义注册到容器中
|
|
|
|
|
registry.registerBeanDefinition("user", beanDefinition);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 导入 UserImportBeanDefinitionRegistrar
|
|
|
|
|
@Import(UserImportBeanDefinitionRegistrar.class)
|
|
|
|
|
public class Application {
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
|
|
|
|
|
//将 Application 注册到容器中
|
|
|
|
|
applicationContext.register(Application.class);
|
|
|
|
|
applicationContext.refresh();
|
|
|
|
|
|
|
|
|
|
User user = applicationContext.getBean(User.class);
|
|
|
|
|
System.out.println("获取到的Bean为" + user + ",属性username值为:" + user.getUsername());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 运行结果如下:
|
|
|
|
|
// 往Spring容器中注入User
|
|
|
|
|
// 获取到的Bean为com.sanyou.spring.extension.User@6385cb26,属性username值为:张小凡
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## BeanPostProcessor
|
|
|
|
|
|
|
|
|
|
Bean的后置处理器,在Bean创建的过程中起作用。BeanPostProcessor是Bean在创建过程中一个非常重要的扩展点,因为每个Bean在创建的各个阶段,都会回调BeanPostProcessor及其子接口的方法,传入正在创建的Bean对象,这样如果想对Bean创建过程中某个阶段进行自定义扩展,那么就可以自定义BeanPostProcessor来完成。常用的两个子接口:
|
|
|
|
|
|
|
|
|
|
- InstantiationAwareBeanPostProcessor
|
|
|
|
|
- DestructionAwareBeanPostProcessor
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class UserBeanPostProcessor implements BeanPostProcessor {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
|
|
|
|
if (bean instanceof User) {
|
|
|
|
|
//如果当前的Bean的类型是 User ,就把这个对象 username 的属性赋值为 张小凡
|
|
|
|
|
((User) bean).setUsername("张小凡");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class Application {
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
|
|
|
|
|
//将 UserBeanPostProcessor 和 User 注册到容器中
|
|
|
|
|
applicationContext.register(UserBeanPostProcessor.class);
|
|
|
|
|
applicationContext.register(User.class);
|
|
|
|
|
applicationContext.refresh();
|
|
|
|
|
|
|
|
|
|
User user = applicationContext.getBean(User.class);
|
|
|
|
|
System.out.println("获取到的Bean为" + user + ",属性username值为:" + user.getUsername());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 运行结果如下:
|
|
|
|
|
// 获取到的Bean为com.sanyou.spring.extension.User@21a947fe,属性username值为:三友的java日记
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Spring内置的BeanPostProcessor**
|
|
|
|
|
|
|
|
|
|
| BeanPostProcessor | 作用 |
|
|
|
|
|
| :------------------------------------- | :--------------------------------------------- |
|
|
|
|
|
| AutowiredAnnotationBeanPostProcessor | 处理@Autowired、@Value注解 |
|
|
|
|
|
| CommonAnnotationBeanPostProcessor | 处理@Resource、@PostConstruct、@PreDestroy注解 |
|
|
|
|
|
| AnnotationAwareAspectJAutoProxyCreator | 处理一些注解或者是AOP切面的动态代理 |
|
|
|
|
|
| ApplicationContextAwareProcessor | 处理Aware接口注入的 |
|
|
|
|
|
| AsyncAnnotationBeanPostProcessor | 处理@Async注解 |
|
|
|
|
|
| ScheduledAnnotationBeanPostProcessor | 处理@Scheduled注解 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**BeanPostProcessor在Dubbo中的应用**
|
|
|
|
|
|
|
|
|
|
在Dubbo中可以通过@DubboReference(@Reference)来引用生产者提供的接口,这个注解的处理也是依靠ReferenceAnnotationBeanPostProcessor,也就是 BeanPostProcessor 的扩展来实现的。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class ReferenceAnnotationBeanPostProcessor
|
|
|
|
|
extends AbstractAnnotationBeanPostProcessor
|
|
|
|
|
implements ApplicationContextAware, BeanFactoryPostProcessor {
|
|
|
|
|
// 忽略
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## BeanFactoryPostProcessor
|
|
|
|
|
|
|
|
|
|
BeanFactoryPostProcessor是可以对Spring容器做处理的,方法入参就是Spring容器,通过这个接口,就对容器进行为所欲为的操作。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
|
|
|
|
// 禁止循环依赖
|
|
|
|
|
((DefaultListableBeanFactory) beanFactory).setAllowCircularReferences(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Spring SPI机制
|
|
|
|
|
|
|
|
|
|
SPI全称为 (Service Provider Interface),是一种动态替换发现的机制,一种解耦非常优秀的思想,SPI可以很灵活的让接口和实现分离, 让API提供者只提供接口,第三方来实现,然后可使用配置文件的方式来实现替换或者扩展,在框架中比较常见,提高框架的可扩展性。
|
|
|
|
|
|
|
|
|
|
- JDK:ServiceLoader
|
|
|
|
|
- Dubbo:ExtensionLoader
|
|
|
|
|
- Spring:SpringFactoriesLoader
|
|
|
|
|
|
|
|
|
|
Spring的SPI机制规定,配置文件必须在classpath路径下的META-INF文件夹内,文件名必须为spring.factories,文件内容为键值对,一个键可以有多个值,只需要用逗号分割就行,同时键值都需要是类的全限定名。但是键和值可以没有任何关系,当然想有也可以有。
|
|
|
|
|
|
|
|
|
|
### 自动装配
|
|
|
|
|
|
|
|
|
|
### PropertySourceLoader
|
|
|
|
|
|
|
|
|
|
在SpringBoot环境下,外部化的配置文件支持properties和yaml两种格式。但现在不想使用properties和yaml格式的文件,想使用json格式的配置文件。则可通过PropertySourceLoader来实现。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public interface PropertySourceLoader {
|
|
|
|
|
|
|
|
|
|
// 可以支持哪种文件格式的解析
|
|
|
|
|
String[] getFileExtensions();
|
|
|
|
|
|
|
|
|
|
// 解析配置文件,读出内容,封装成一个PropertySource<?>结合返回回去
|
|
|
|
|
List<PropertySource<?>> load(String name, Resource resource) throws IOException;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
对于PropertySourceLoader的实现,SpringBoot两个实现:
|
|
|
|
|
|
|
|
|
|
- **PropertiesPropertySourceLoader**:可以解析properties或者xml结尾的配置文件
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class PropertiesPropertySourceLoader implements PropertySourceLoader {
|
|
|
|
|
private static final String XML_FILE_EXTENSION = ".xml";
|
|
|
|
|
|
|
|
|
|
public PropertiesPropertySourceLoader() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String[] getFileExtensions() {
|
|
|
|
|
return new String[]{"properties", "xml"};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
|
|
|
|
|
Map<String, ?> properties = this.loadProperties(resource);
|
|
|
|
|
return properties.isEmpty() ? Collections.emptyList() : Collections.singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Map<String, ?> loadProperties(Resource resource) throws IOException {
|
|
|
|
|
String filename = resource.getFilename();
|
|
|
|
|
return (Map)(filename != null && filename.endsWith(".xml") ? PropertiesLoaderUtils.loadProperties(resource) : (new OriginTrackedPropertiesLoader(resource)).load());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- **YamlPropertySourceLoader**:解析以yml或者yaml结尾的配置文件
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class YamlPropertySourceLoader implements PropertySourceLoader {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String[] getFileExtensions() {
|
|
|
|
|
return new String[] { "yml", "yaml" };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
|
|
|
|
|
if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
|
|
|
|
|
throw new IllegalStateException(
|
|
|
|
|
"Attempted to load " + name + " but snakeyaml was not found on the classpath");
|
|
|
|
|
}
|
|
|
|
|
List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();
|
|
|
|
|
if (loaded.isEmpty()) {
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());
|
|
|
|
|
for (int i = 0; i < loaded.size(); i++) {
|
|
|
|
|
String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";
|
|
|
|
|
propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber,
|
|
|
|
|
Collections.unmodifiableMap(loaded.get(i)), true));
|
|
|
|
|
}
|
|
|
|
|
return propertySources;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
所以可以看出,要想实现json格式的支持,只需要自己实现可以用来解析json格式的配置文件的PropertySourceLoader就可以了。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**实现可以读取json格式的配置文件**
|
|
|
|
|
|
|
|
|
|
**第一步:自定义一个JsonPropertySourceLoader,实现PropertySourceLoader接口**
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class JsonPropertySourceLoader implements PropertySourceLoader {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String[] getFileExtensions() {
|
|
|
|
|
//这个方法表明这个类支持解析以json结尾的配置文件
|
|
|
|
|
return new String[]{"json"};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
|
|
|
|
|
|
|
|
|
|
ReadableByteChannel readableByteChannel = resource.readableChannel();
|
|
|
|
|
|
|
|
|
|
ByteBuffer byteBuffer = ByteBuffer.allocate((int) resource.contentLength());
|
|
|
|
|
|
|
|
|
|
//将文件内容读到 ByteBuffer 中
|
|
|
|
|
readableByteChannel.read(byteBuffer);
|
|
|
|
|
//将读出来的字节转换成字符串
|
|
|
|
|
String content = new String(byteBuffer.array());
|
|
|
|
|
// 将字符串转换成 JSONObject
|
|
|
|
|
JSONObject jsonObject = JSON.parseObject(content);
|
|
|
|
|
|
|
|
|
|
Map<String, Object> map = new HashMap<>(jsonObject.size());
|
|
|
|
|
//将 json 的键值对读出来,放入到 map 中
|
|
|
|
|
for (String key : jsonObject.keySet()) {
|
|
|
|
|
map.put(key, jsonObject.getString(key));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Collections.singletonList(new MapPropertySource("jsonPropertySource", map));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**第二步:配置PropertySourceLoader**
|
|
|
|
|
|
|
|
|
|
基于SPI机制加载PropertySourceLoader实现。因此在spring.factories文件中配置如下:
|
|
|
|
|
|
|
|
|
|
```properties
|
|
|
|
|
org.springframework.boot.env.PropertySourceLoader=\
|
|
|
|
|
cn.test.JsonPropertySourceLoader
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**第三步:测试验证**
|
|
|
|
|
|
|
|
|
|
在application.json中配置如下:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
{
|
|
|
|
|
"test.userName": "张小凡"
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
定义配置类:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class User {
|
|
|
|
|
// 注入配置文件的属性
|
|
|
|
|
@Value("${test.userName:}")
|
|
|
|
|
private String userName;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
启动项目
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@SpringBootApplication
|
|
|
|
|
public class Application {
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class);
|
|
|
|
|
User user = applicationContext.getBean(User.class);
|
|
|
|
|
System.out.println("获取到的Bean为" + user + ",属性userName值为:" + user.getUserName());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Bean
|
|
|
|
|
public User user() {
|
|
|
|
|
return new User();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 运行结果如下:
|
|
|
|
|
// 获取到的Bean为com.sanyou.spring.extension.User@481ba2cf,属性username值为:张小凡
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Nacos对于PropertySourceLoader的实现**
|
|
|
|
|
|
|
|
|
|
```properties
|
|
|
|
|
org.springframework.boot.env.PropertySourceLoader=\
|
|
|
|
|
com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\
|
|
|
|
|
com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Nacos作为配置中心,不仅支持properties和yaml格式的文件,还支持json格式的配置文件,那么客户端拿到这些配置就需要解析,SpringBoot已经支持了properties和yaml格式的文件的解析,那么Nacos只需要实现SpringBoot不支持的就可以了。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### ApplicationContextInitializer
|
|
|
|
|
|
|
|
|
|
ApplicationContextInitializer也是SpringBoot启动过程的一个扩展点。在SpringBoot启动过程,会回调这个类的实现initialize方法,传入ConfigurableApplicationContext。
|
|
|
|
|
|
|
|
|
|
```properties
|
|
|
|
|
org.springframework.context.ApplicationContextInitializer=\
|
|
|
|
|
org.springframework.boot.context.ConfigurationWarningApplicationContextInitializer,\
|
|
|
|
|
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
|
|
|
|
|
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
|
|
|
|
|
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
|
|
|
|
|
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
注意:这里传入的ConfigurableApplicationContext并没有调用过refresh方法,也就是里面是没有Bean对象的,一般这个接口是用来配置ConfigurableApplicationContext,而不是用来获取Bean的。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### EnvironmentPostProcessor
|
|
|
|
|
|
|
|
|
|
EnvironmentPostProcessor在SpringBoot启动过程中,也会调用,也是通过SPI机制来加载扩展的。EnvironmentPostProcessor是用来处理ConfigurableEnvironment的,也就是一些配置信息,SpringBoot所有的配置都是存在这个对象的。
|
|
|
|
|
|
|
|
|
|
这个类的作用就是用来处理外部化配置文件的,也就是这个类是用来处理配置文件的,通过前面提到的PropertySourceLoader解析配置文件,放到ConfigurableEnvironment里面。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### ApplicationRunner和CommandLineRunner
|
|
|
|
|
|
|
|
|
|
ApplicationRunner和CommandLineRunner都是在SpringBoot成功启动之后会调用,可以拿到启动时的参数。这两个其实不是通过SPI机制来扩展,而是直接从容器中获取的。因为调用ApplicationRunner和CommandLineRunner时,SpringBoot已经启动成功了,Spring容器都准备好了,需要什么Bean直接从容器中查找多方便。
|
|
|
|
|
|
|
|
|
|
所以要想扩展这个点,只需要实现接口,添加到Spring容器。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Spring Event 事件
|
|
|
|
|
|
|
|
|
|
什么是Spring Event 事件,就是Spring实现了这种事件模型,你只需要基于Spring提供的API进行扩展,就可以完成事件的发布订阅。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
// 火灾事件
|
|
|
|
|
public class FireEvent extends ApplicationEvent {
|
|
|
|
|
public FireEvent(String source) {
|
|
|
|
|
super(source);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 打119的火灾事件的监听器
|
|
|
|
|
public class Call119FireEventListener implements ApplicationListener<FireEvent> {
|
|
|
|
|
@Override
|
|
|
|
|
public void onApplicationEvent(FireEvent event) {
|
|
|
|
|
System.out.println("打119");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 救人的火灾事件的监听器
|
|
|
|
|
public class SavePersonFireEventListener implements ApplicationListener<FireEvent> {
|
|
|
|
|
@Override
|
|
|
|
|
public void onApplicationEvent(FireEvent event) {
|
|
|
|
|
System.out.println("救人");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class Application {
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
|
|
|
|
|
//将 事件监听器 注册到容器中
|
|
|
|
|
applicationContext.register(Call119FireEventListener.class);
|
|
|
|
|
applicationContext.register(SavePersonFireEventListener.class);
|
|
|
|
|
applicationContext.refresh();
|
|
|
|
|
|
|
|
|
|
// 发布着火的事件,触发监听
|
|
|
|
|
applicationContext.publishEvent(new FireEvent("着火了"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Spring内置的事件**
|
|
|
|
|
|
|
|
|
|
Spring内置的事件很多,这里我罗列几个
|
|
|
|
|
|
|
|
|
|
| 事件类型 | 触发时机 |
|
|
|
|
|
| :-------------------- | :----------------------------------------------------------- |
|
|
|
|
|
| ContextRefreshedEvent | 在调用ConfigurableApplicationContext 接口中的refresh()方法时触发 |
|
|
|
|
|
| ContextStartedEvent | 在调用ConfigurableApplicationContext的start()方法时触发 |
|
|
|
|
|
| ContextStoppedEvent | 在调用ConfigurableApplicationContext的stop()方法时触发 |
|
|
|
|
|
| ContextClosedEvent | 当ApplicationContext被关闭时触发该事件,也就是调用close()方法触发 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### ApplicationEvent
|
|
|
|
|
|
|
|
|
|
事件的父类,所有具体的事件都得继承这个类,构造方法的参数是这个事件携带的参数,监听器就可以通过这个参数来进行一些业务操作。
|
|
|
|
|
|
|
|
|
|
![图片](images/Middleware/ApplicationEvent.png)
|
|
|
|
|
|
|
|
|
|
### ApplicationListener
|
|
|
|
|
|
|
|
|
|
事件监听的接口,泛型是子类需要监听的事件类型,子类需要实现onApplicationEvent,参数就是事件类型,onApplicationEvent方法的实现就代表了对事件的处理,当事件发生时,Spring会回调onApplicationEvent方法的实现,传入发布的事件。
|
|
|
|
|
|
|
|
|
|
![图片](images/Middleware/ApplicationListener.png)
|
|
|
|
|
|
|
|
|
|
### ApplicationEventPublisher
|
|
|
|
|
|
|
|
|
|
事件发布器,通过publishEvent方法就可以发布一个事件,然后就可以触发监听这个事件的监听器的回调。
|
|
|
|
|
|
|
|
|
|
ApplicationContext实现了ApplicationEventPublisher接口,所以通过ApplicationContext就可以发布事件。
|
|
|
|
|
|
|
|
|
|
![图片](images/Middleware/ApplicationEventPublisher.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 命名空间
|
|
|
|
|
|
|
|
|
|
**第一步:定义一个xsd文件**
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
|
|
|
<!-- xmlns 和 targetNamespace 需要定义,结尾为sanyou,前面都一样的-->
|
|
|
|
|
<xsd:schema xmlns="http://sanyou.com/schema/sanyou"
|
|
|
|
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
|
|
|
targetNamespace="http://sanyou.com/schema/sanyou">
|
|
|
|
|
|
|
|
|
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
|
|
|
|
|
|
|
|
|
<xsd:complexType name="Bean">
|
|
|
|
|
<xsd:attribute name="class" type="xsd:string" use="required"/>
|
|
|
|
|
</xsd:complexType>
|
|
|
|
|
|
|
|
|
|
<!-- sanyou 便签的子标签,类型是Bean ,就会找到上面的complexType=Bean类型,然后处理属性 -->
|
|
|
|
|
<xsd:element name="mybean" type="Bean"/>
|
|
|
|
|
</xsd:schema>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这个xsd文件来指明sanyou这个命名空间下有哪些标签和属性。这里我只指定了一个标签 mybean,mybean标签里面有个class的属性,然后这个标签的目的就是将class属性指定的Bean的类型,注入到Spring容器中,作用跟spring的标签的作用是一样的。xsd文件没有需要放的固定的位置,这里我放到 META-INF 目录下。
|
|
|
|
|
|
|
|
|
|
**第二步:解析这个命名空间**
|
|
|
|
|
|
|
|
|
|
解析命名空间很简单,Spring都有配套的东西--NamespaceHandler接口,只要实现这个接口就行了。但一般我们不直接实现 NamespaceHandler 接口,我们可以继承 NamespaceHandlerSupport 类,这个类实现了 NamespaceHandler 接口。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class SanYouNameSpaceHandler extends NamespaceHandlerSupport {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void init() {
|
|
|
|
|
//注册解析 mybean 标签的解析器
|
|
|
|
|
registerBeanDefinitionParser("mybean", new SanYouBeanDefinitionParser());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static class SanYouBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
|
|
|
|
|
@Override
|
|
|
|
|
protected boolean shouldGenerateId() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected String getBeanClassName(Element element) {
|
|
|
|
|
return element.getAttribute("class");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
SanYouNameSpaceHandler的作用就是将sanyou命名空间中的mybean这个标签读出来,拿到class的属性,然后将这个class属性指定的class类型注入到Spring容器中,至于注册这个环节的代码,都交给了SanYouBeanDefinitionParser的父类来做了。
|
|
|
|
|
|
|
|
|
|
**第三步:创建并配置spring.handlers和spring.schemas文件**
|
|
|
|
|
|
|
|
|
|
先创建spring.handlers和spring.schemas文件。spring.handlers文件内容:
|
|
|
|
|
|
|
|
|
|
```properties
|
|
|
|
|
http\://sanyou.com/schema/sanyou=com.sanyou.spring.extension.namespace.SanYouNameSpaceHandler
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
通过spring.handlers配置文件,就知道sanyou命名空间应该找SanYouNameSpaceHandler进行解析。spring.schemas文内容:
|
|
|
|
|
|
|
|
|
|
```properties
|
|
|
|
|
http\://sanyou.com/schema/sanyou.xsd=META-INF/sanyou.xsd
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
spring.schemas配置xsd文件的路径。文件都有了,只需要放到classpath下的META-INF文件夹就行了。
|
|
|
|
|
|
|
|
|
|
![图片](images/Middleware/sanyou-xsd.png)
|
|
|
|
|
|
|
|
|
|
**第四步:测试**
|
|
|
|
|
|
|
|
|
|
先构建一个applicationContext.xml文件,放到resources目录下
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
|
|
|
xmlns="http://www.springframework.org/schema/beans"
|
|
|
|
|
xmlns:sanyou="http://sanyou.com/schema/sanyou"
|
|
|
|
|
xsi:schemaLocation="
|
|
|
|
|
http://www.springframework.org/schema/beans
|
|
|
|
|
http://www.springframework.org/schema/beans/spring-beans.xsd
|
|
|
|
|
http://sanyou.com/schema/sanyou
|
|
|
|
|
http://sanyou.com/schema/sanyou.xsd
|
|
|
|
|
">
|
|
|
|
|
|
|
|
|
|
<!--使用 sanyou 标签,配置一个 User Bean-->
|
|
|
|
|
<sanyou:mybean class="com.sanyou.spring.extension.User"/>
|
|
|
|
|
|
|
|
|
|
</beans>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
再写个测试类:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class Application {
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
|
|
|
|
|
applicationContext.refresh();
|
|
|
|
|
User user = applicationContext.getBean(User.class);
|
|
|
|
|
System.out.println(user);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 运行结果
|
|
|
|
|
// com.sanyou.spring.extension.User@27fe3806
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# SpringCloud
|
|
|
|
|