前言
在我写的Spring前一篇文章,具体介绍了Bean的生命周期和Bean的创建过程,内容并不是很多,真正深入Spring的入门课程还需要这篇博客,同时这篇博客也是为了下面理解Spring源码必备的。接下来让我们进入今天的学习。
注意:这篇主要是介绍每个功能的作用,所以会有很多的API调用而不会解释原理,原理学到了会讲的。
BeanDefinition
BeanDefinition可以看作是一个Bean的定义,也可以看作是Bean的定义,BeanDefinition中存了很多Bean的属性,用来描述一个Bean的特点。 下面列举几个BeanDefinition表示的属性
class: 表示Bean的类型
scope: 表示Bean的作用域,单例Bean或者原型Bean等
lazyinit: 表示Bean是否是懒加载
initMethodName: 表示Bean初始化时要执行的方法,这和我们上一期写的Bean初始化阶段要执行的方法相对应
destoryMethodName: 表示Bean销毁时要执行的方法
拓展:什么是懒加载?
懒加载,明了的说,就是当Spring要用到这个Bean的时候才进行创建这个Bean。可以大大提高性能,Spring在启动时只需要创建非懒加载的Bean。
拓展:Bean什么时候进行销毁
单例Bean:这种类型的Bean在Spring容器销毁时进行销毁。
原型Bean:这种类型的Bean,Spring不负责销毁,由JVM的垃圾回收机制进行销毁。
大家都知道下面几种定义Bean的方法: 在XML文件中: 在注解中:@Bean @Component @Service @Controller 上面这些都是声明式定义Bean,就是说是告诉Spring我需要这几个Bean,我们还可以通过BeanDefinition进行编程式定义Bean:
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// 生成一个 BeanDefinition 对象,并设置 beanClass 为 User.class,并注册到 ApplicationContext 中
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition()
.getBeanDefinition();
beanDefinition.setBeanClass(User.class);
context.registerBeanDefinition("user", beanDefinition);
System.out.println(context.getBean("user"));
这是注册一个Bean到BeanDefinition中,还可以使用BeanDefition来设置一个Bean的属性:
beanDefinition.setScope("prototype"); // 设置作用域
beanDefinition.setInitMethodName("init"); // 设置初始化方法
beanDefinition.setLazyInit(true); // 设置懒加载
通过声明式定义的Bean,都是由Spring来执行代码之后来设置Bean,就是说Spring来进行编程式定义了。
BeanDefinitionReader
就和工厂思想一样,光有工厂的建造不行,还需要运输才能将产品送到目的地,BeanDefinition同样需要读取器BeanDefinitionReader。这些我们平常使用Spring开发时根本用不到,但是Spring自己会用到,相当于Spring的基础设施。
AnnotatedBeanDefinitionReader
这个读取器可以直接把某个类转换为BeanDefition,而且名字有Annotated,表示会解析该类上的注解 ,实例:
// 创建基于注解配置的Spring应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 创建AnnotatedBeanDefinitionReader,用于解析基于注解的Bean定义
AnnotatedBeanDefinitionReader annotatedBeanDefinitionReader = new AnnotatedBeanDefinitionReader(context);
// 将User.class解析为BeanDefinition并注册
annotatedBeanDefinitionReader.register(User.class);
// 从上下文中获取名为"user"的Bean并打印
System.out.println(context.getBean("user"));
**注意:它能解析的注解为:@Condition,@Scope,@Lazy,@Primary,@DependsOn,@Role,@Description
XmlBeanDefinitionReader
这个简单,可以解析用XML定义的Bean(标签)
// 创建基于注解配置的 Spring 应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 创建 XmlBeanDefinitionReader 用于从 XML 文件加载 Bean 定义
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(context);
// 从 spring.xml 文件加载 Bean 定义,并返回加载的 Bean 定义数量
int i = xmlBeanDefinitionReader.loadBeanDefinitions("spring.xml");
// 从应用上下文中获取名为 "user" 的 Bean 并打印
System.out.println(context.getBean("user"));
ClassPathBeanDefinitionScanner
这个是Spring扫描Bean的扫描器,它可以进行对一个包路径下的类进行扫描,并且进行解析,比如:扫描到一个类上的注解为@Component,那么就会把这个类解析为一个BeanDefinition。
// 创建一个基于注解配置的 Spring 应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 刷新应用上下文,触发 Bean 定义的加载和 Bean 的创建等操作
context.refresh();
// 创建一个类路径 Bean 定义扫描器,用于扫描指定包下的 Bean 定义
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
// 扫描 com.zhouyu 包下的类,将带有特定注解的类注册为 Bean
scanner.scan("com.zhouyu");
// 从应用上下文中获取名为 userService 的 Bean 并打印
System.out.println(context.getBean("userService"));
BeanFactory
BeanFactory就是Bean工厂,类型是接口,负责创建Bean,并且提供Bean的API。
public interface ApplicationContext
extends EnvironmentCapable,
ListableBeanFactory,
HierarchicalBeanFactory,
MessageSource,
ApplicationEventPublisher,
ResourcePatternResolver {
// 接口方法及其他内容省略
...
}
既然是接口,那么就肯定有很多类来实现接口的方法。其中ListableBeanFactory和HierarchicalBeanFactory都是实现了BeanFactory接口。说明他们都是Bean工厂,但是我们平常不用他们,在使用Spring的时候都是来用ApplicationContext来实现的,因为这个继承了刚刚说的两个类,所以ApplicationContext也是Bean工厂。
那有人要问了,为什么不直接实现BeanFactory呢?这因为BeanFactory是接口,不能被实例化,而且,一层一层下来,ApplicationContext肯定比BeanFactory功能多了许多,可以说是一个万能的Bean工厂,所以我们在没有特别需求的时候都是用的ApplicationContext。
当我们new出一个ApplicationContext时,Spring实际上是new出了一个BeanFactory。在调用例如getBean()方法时实际上是在调用BeanFactory的getBean()方法。
DefaultListableBeanFactory
这下有人就要问了,为什么非要用ApplicationContext呢?他只是继承了其他类,说明他的父类也有它能实现的功能。其实没错,在Spring中有个类非常核心,叫做DefaultListableBeanFactory,看到最后一个单词,其实他也是Bean工厂,所以他也有创建Bean的功能。
// 创建 DefaultListableBeanFactory 实例
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 使用 BeanDefinitionBuilder 构建 AbstractBeanDefinition 对象
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
// 设置 BeanDefinition 的 beanClass 为 User.class
beanDefinition.setBeanClass(User.class);
// 向 beanFactory 注册名为 "user" 的 BeanDefinition
beanFactory.registerBeanDefinition("user", beanDefinition);
// 从 beanFactory 中获取名为 "user" 的 Bean 并打印
System.out.println(beanFactory.getBean("user"));
大家可以看到,这和我们上面提到的编程式定义Bean是差不多的。也是创建了AbstractBeanDefinition对象,最后设置Bean的属性。
DefaultListableBeanFactory是十分强大的,强大的功能基于Spring的接口式编程,实现的接口让很多类都必须实现很多功能,其中DefaultListableBeanFactory就是一个典型的类,接下来看类图:
接下来我给大家介绍一下实现的功能:
0. AliasRegistry: 支持别名功能,一个名字可以对应多个别名
1. BeanDefinitionRegistry: 可以注册、保存、移除、获取某个BeanDefinition
2. BeanFactory: Bean工厂,可以根据某个bean的名字、或类型、或别名获取某个Bean对象
3. SingletonBeanRegistry: 可以直接注册、获取某个单例Bean
4.SimpleAliasRegistry: 它是一个类,实现了AliasRegistry接口中所定义的功能,支持别名功能
5. ListableBeanFactory: 在BeanFactory的基础上,增加了其他功能,可以获取所有 BeanDefinition的beanNames,可以根据某个类型获取对应的beanNames,可以根据某个类 型获取{类型:对应的Bean}的映射关系
6. HierarchicalBeanFactory: 在BeanFactory的基础上,添加了获取父BeanFactory的功能
7. DefaultSingletonBeanRegistry: 它是一个类,实现了SingletonBeanRegistry接口,拥有了直 接注册、获取某个单例Bean的功能
8. ConfigurableBeanFactory: 在HierarchicalBeanFactory和SingletonBeanRegistry的基础上, 添加了设置父BeanFactory、类加载器(表示可以指定某个类加载器进行类的加载)、设置 Spring EL表达式解析器(表示该BeanFactory可以解析EL表达式)、设置类型转化服务(表示 该BeanFactory可以进行类型转化)、可以添加BeanPostProcessor(表示该BeanFactory支持 Bean的后置处理器),可以合并BeanDefinition,可以销毁某个Bean等等功能
9. FactoryBeanRegistrySupport: 支持了FactoryBean的功能
10. AutowireCapableBeanFactory: 是直接继承了BeanFactory,在BeanFactory的基础上,支持 在创建Bean的过程中能对Bean进行自动装配
11. AbstractBeanFactory: 实现了ConfigurableBeanFactory接口,继承了 FactoryBeanRegistrySupport,这个BeanFactory的功能已经很全面了,但是不能自动装配和 获取beanNames
12. ConfigurableListableBeanFactory: 继承了ListableBeanFactory、 AutowireCapableBeanFactory、ConfigurableBeanFactory
13. AbstractAutowireCapableBeanFactory: 继承了AbstractBeanFactory,实现了 AutowireCapableBeanFactory,拥有了自动装配的功能
14. DefaultListableBeanFactory: 继承了AbstractAutowireCapableBeanFactory,实现了 ConfigurableListableBeanFactory接口和BeanDefinitionRegistry接口,所以 DefaultListableBeanFactory的功能很强大
ApplicationContext
大家在启动Spring的时候都需要创建一个ApplicationContext实例对象,之后调用run方法,方法中传入这个类的class文件。其实,ApplicationContext是一个BeanFactory,但是有人要问了,为什么就用这个呢,BeanFactory那么多。其实不难想为什么优先用这个,因为这个的功能更多,比BeanFactory更加强大,比如:
-
HierarchicalBeanFactory:
- 拥有获取父
BeanFactory的功能。
- 拥有获取父
-
ListableBeanFactory:
- 拥有获取
beanNames的功能。
- 拥有获取
-
ResourcePatternResolver:
- 资源加载器,可以一次性获取多个资源(如文件资源等)。
-
EnvironmentCapable:
- 可以获取运行时环境,但没有设置运行时环境功能。
-
ApplicationEventPublisher:
- 拥有广播事件的功能,但没有添加事件监听器的功能。
-
MessageSource:
- 拥有国际化功能。
演示在后面。
我们先首先来看看他的两个重要实现类:
AnnotationConfigApplicationContext
ClassPathXmlApplicationContext
AnnotationConfigApplicationContext
-
ConfigurableApplicationContext:
-
继承自
ApplicationContext接口。 -
新增功能如下:
- 添加事件监听器。
- 添加
BeanFactoryPostProcessor。 - 设置
Environment。 - 获取
ConfigurableListableBeanFactory。
-
-
AbstractApplicationContext:
- 实现了
ConfigurableApplicationContext接口。
- 实现了
-
GenericApplicationContext:
- 继承自
AbstractApplicationContext。 - 实现了
BeanDefinitionRegistry接口。 - 具备所有
ApplicationContext的功能,并且可以注册BeanDefinition。 - 注意:该类中有一个属性
DefaultListableBeanFactory beanFactory。
- 继承自
-
AnnotationConfigRegistry:
-
可以单独注册某个类为
BeanDefinition。- 能够处理该类上的
@Configuration注解。 - 能够处理
@Bean注解。
- 能够处理该类上的
-
同时具备扫描功能。
-
-
AnnotationConfigApplicationContext:
- 继承自
GenericApplicationContext。 - 实现了
AnnotationConfigRegistry接口。 - 拥有以上所有功能。
- 继承自
ClassPathXmlApplicationContext
两个都是继承AbstractApplicationContext,但是对于上面那个而言,功能没有它强大,比如不能注册Bean
国际化
什么是国际化,就是说可以把相对的语言转换为指定的语言,如果Spring没有整合这个功能,那么我们就要自己实现,可以说是很麻烦的。但是Spring只需要创建一个Bean,之后就可以使用了,可以说是十分方便。
定义MessageSource
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
return messageSource;
}
有了这个Bean,我们就可以把任何语言转换为指定的语言了。这个功能不是那么重要,所以就不细讲了。
资源加载
资源加载可以说是每个框架都必备的功能了,在Spring中也是有的。
我们可以直接使用ApplicationContext来获取某个文件中的内容。
// 创建基于注解配置的Spring应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 通过应用上下文获取指定路径的资源
Resource resource = context.getResource("file://D:\IdeaProjects\springframework\luban\src\main\java\com\luban\entity\User.java");
// 打印资源的长度
System.out.println(resource.contentLength());
这是Spring帮我们写好了的,当然,我们自己也可以完成。
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 获取本地文件资源
Resource resource = context.getResource("file://D:\IdeaProjects\spring‐framework5.3.10\tuling\src\main\java\com\zhouyu\service\UserService.java");
System.out.println(resource.contentLength());
System.out.println(resource.getFilename());
// 获取网络资源
Resource resource1 = context.getResource("https://www.baidu.com");
System.out.println(resource1.contentLength());
System.out.println(resource1.getURL());
// 获取类路径下的资源
Resource resource2 = context.getResource("classpath:spring.xml");
System.out.println(resource2.contentLength());
System.out.println(resource2.getURL());
// 一次性获取类路径下指定目录的多个资源
Resource[] resources = context.getResources("classpath:com/zhouyu/*.class");
for (Resource res : resources) {
System.out.println(res.contentLength());
System.out.println(res.getFilename());
}
获取运行时环境
// 创建基于注解配置的Spring应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 获取系统环境变量
Map<String, Object> systemEnvironment = context.getEnvironment().getSystemEnvironment();
System.out.println(systemEnvironment);
System.out.println("=======");
// 获取系统属性
Map<String, Object> systemProperties = context.getEnvironment().getSystemProperties();
System.out.println(systemProperties);
System.out.println("=======");
// 获取属性源
MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
System.out.println(propertySources);
System.out.println("=======");
// 获取特定属性值
System.out.println(context.getEnvironment().getProperty("NO_PROXY"));
System.out.println(context.getEnvironment().getProperty("sun.jnu.encoding"));
System.out.println(context.getEnvironment().getProperty("zhouyu"));
getSystemEnvironment()和getSystemProperties()名字很相似,但是功能大不相同,前者是获取环境变量,比如使用的JDK,操作系统的各种属性。后者实则是获得系统的属性,比如CPU,GPU等等硬件。 当然,Spring给我们提供了更简便的方法
@PropertySource("classpath:spring.properties")
可以使得某个properties文件中的配置运行到环境中。
事件发布
大家对事件发布应该不陌生,当有一个event活动时,可以执行我们自己写的函数。
@Bean
public ApplicationListener applicationListener() {
return new ApplicationListener() {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("接收到了一个事件");
// 发布一个事件
applicationEventPublisher.publishEvent("kkk");
}
};
之后就可以发布一个事件
context.publishEvent("kkk");
类型转换
大家都能遇到很多类型转换的问题,比如把String转换为其他类型。Spring中提供了更见方便的类型转换的工具。
PropertyEditor
这是JDK提供的类型转换类
// 定义 User 类,用于表示用户信息
class User {
private String name;
// 设置用户姓名的方法
public void setName(String name) {
this.name = name;
}
// 重写 toString 方法,方便打印用户信息
@Override
public String toString() {
return "User{name='" + name + "'}";
}
}
import java.beans.PropertyEditor;
import java.beans.PropertyEditorSupport;
// 自定义属性编辑器,将字符串转换为 User 对象
public class StringToUserPropertyEditor extends PropertyEditorSupport implements PropertyEditor {
// 重写 setAsText 方法,将输入的字符串转换为 User 对象
@Override
public void setAsText(String text) throws IllegalArgumentException {
User user = new User();
user.setName(text);
this.setValue(user);
}
}
public class Main {
public static void main(String[] args) {
// 创建自定义属性编辑器实例
StringToUserPropertyEditor propertyEditor = new StringToUserPropertyEditor();
// 调用 setAsText 方法,传入字符串 "1"
propertyEditor.setAsText("1");
// 获取转换后的 User 对象
User value = (User) propertyEditor.getValue();
// 打印 User 对象信息
System.out.println(value);
}
}
在Spring中注册类型转换器(PropertyEditor),转换为Bean
import org.springframework.beans.factory.config.CustomEditorConfigurer;
import java.beans.PropertyEditor;
import java.util.HashMap;
import java.util.Map;
// 使用 @Bean 注解将方法返回的对象注册到 Spring 容器中
@Bean
public CustomEditorConfigurer customEditorConfigurer() {
// 创建 CustomEditorConfigurer 实例
CustomEditorConfigurer customEditorConfigurer = new CustomEditorConfigurer();
// 创建一个用于存储属性编辑器映射的 Map
Map<Class<?>, Class<? extends PropertyEditor>> propertyEditorMap = new HashMap<>();
// 这里可以向 propertyEditorMap 中添加具体的映射关系,例如:
// propertyEditorMap.put(SomeClass.class, SomePropertyEditor.class);
// 将属性编辑器映射设置到 CustomEditorConfigurer 中
customEditorConfigurer.setCustomEditors(propertyEditorMap);
// 返回配置好的 CustomEditorConfigurer 实例
return customEditorConfigurer;
}
注册完之后就会让那些用String类型来初始化的类来进行实例化
ConversionService
这个是Spring提供的类型转换服务,比上一个更加强大,废话不多说,直接上代码
// 自定义转换器,将字符串转换为 User 对象
public class StringToUserConverter implements ConditionalGenericConverter {
// 判断是否可以进行转换
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return sourceType.getType().equals(String.class) && targetType.getType().equals(User.class);
}
// 获取可转换的类型对
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, User.class));
}
// 执行转换操作
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
User user = new User();
user.setName((String) source);
return user;
}
}
TypeCoverter
这个类整合了上面两个的内容,但是是Spring内部使用的
// 自定义属性编辑器,用于将字符串转换为 User 对象
class StringToUserPropertyEditor extends PropertyEditorSupport implements PropertyEditor {
@Override
public void setAsText(String text) throws IllegalArgumentException {
User user = new User();
user.setName(text);
this.setValue(user);
}
}
OrderComparator
在Spring是一个比较器,可以根据@Order注解或者Ordered接口来实现对某个值的比较,从而进行排序
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import java.util.ArrayList;
import java.util.List;
// 类 A 实现 Ordered 接口
class A implements Ordered {
// 实现 getOrder 方法,返回排序顺序值为 3
@Override
public int getOrder() {
return 3;
}
// 重写 toString 方法,返回类的简单名称
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
// 类 B 实现 Ordered 接口
class B implements Ordered {
// 实现 getOrder 方法,返回排序顺序值为 2
@Override
public int getOrder() {
return 2;
}
// 重写 toString 方法,返回类的简单名称
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
// 主类,包含程序入口 main 方法
public class Main {
public static void main(String[] args) {
// 创建 A 类的实例,其 order 值为 3
A a = new A();
// 创建 B 类的实例,其 order 值为 2
B b = new B();
// 创建 OrderComparator 实例,用于比较实现 Ordered 接口的对象
OrderComparator comparator = new OrderComparator();
// 使用 comparator 比较 a 和 b,并输出比较结果
System.out.println(comparator.compare(a, b));
// 创建一个 ArrayList 列表
List<Object> list = new ArrayList<>();
// 将 A 类实例添加到列表中
list.add(a);
// 将 B 类实例添加到列表中
list.add(b);
// 按 order 值升序对列表进行排序
list.sort(comparator);
// 输出排序后的列表
System.out.println(list);
}
}
另外Spring还实现了OrderComparator的子类AnnotationAwareOrderComparator,它支持同@Order来指定order的值
BeanPostProcessor
表示对于Bean的后置处理器,我们自己可以定义一个或者多个BeanPostProcessor,每个有不同的方法
@Component
public class ZhouyuBeanPostProcessor implements BeanPostProcessor {
/**
* 在 Bean 初始化之前执行的后置处理方法
* @param bean 当前正在处理的 Bean 实例
* @param beanName 当前 Bean 的名称
* @return 处理后的 Bean 实例
* @throws BeansException 如果处理过程中出现异常
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 检查 Bean 名称是否为 "userService"
if ("userService".equals(beanName)) {
System.out.println("初始化前");
}
return bean;
}
/**
* 在 Bean 初始化之后执行的后置处理方法
* @param bean 当前正在处理的 Bean 实例
* @param beanName 当前 Bean 的名称
* @return 处理后的 Bean 实例
* @throws BeansException 如果处理过程中出现异常
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 检查 Bean 名称是否为 "userService"
if ("userService".equals(beanName)) {
System.out.println("初始化后");
}
return bean;
}
}
第一个方法代表初始化前的方法,第二个代表初始化后的方法,两个方法在Bean的生命周期中特定时间段发挥特定作用。
我们可以通过针对特定的Bean名字来做特定的名字,比如上面方法"userService".equals(beanName);就是在判断是否这个Bean的名字是userService,如果是的话就执行if里面的逻辑。
那么我们可以得出一个结论,我们可以通过BeanPostProcessor来干涉Spring创建Bean的过程。
BeanFactoryPostProcessor
我们刚才讲的是BeanPostProcessor,叫做Bean的后置处理器,那么这个是BeanFactory,那么就叫做Bean工厂的后置处理器,干涉一个Bean工厂的构建过程。
@Component
public class ZhouyuBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
/**
* 该方法会在 BeanFactory 加载完所有 Bean 定义后被调用,可在此对 BeanFactory 进行自定义配置。
*
* @param beanFactory 可配置的列表式 BeanFactory,允许对 Bean 定义进行修改和查询。
* @throws BeansException 如果在处理 BeanFactory 过程中发生错误。
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("加工beanFactory");
}
}
FactoryBean
上面是说干涉BeanPostProcessor来干涉一个Bean的生成,但我们如果想要自己创建一个Bean呢,也是有办法的。
@Component public class ZhouyuFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
UserService userService = new UserService();
return userService;
}
}
@Override public Class getObjectType() {
return UserService.class;
}
这是我们自己简单实现的Bean工厂,实现了FactoryBean接口,重写了getObject()接口和getObjectType(),第一个方法创建了一个Bean对象,并且返回。第二个则是返回这个对象的类型。
这一篇的学习到此为止,谢谢大家!