1. spring概述
可以扫描下面二维码访问我的小程序来打开,随时随地通过微信访问。
1.使用Spring框架的好处是什么?
(1)轻量级开发,方便引入,易于开发
(2)控制反转(IOC)/依赖注入:实现了松散耦合,各个bean相互独立,不需要通过new的方式去手工建立需要使用的bean,通过Spring将bean之间的关联起来,如果bean A依赖bean B,则Spring将B注入到A中,不需要再手工新建bean。
(3)面向切面AOP:可以独立于业务层代码之外,单独编写切面逻辑,例如常见的在过滤器中打印一些特殊的日志输出,校验一些权限验证等操作。
(4)方便集成各种框架,hibernate,MyBatis、Quartz、Junit等。
(5)容器对bean的生命周期进行了统一管理,不再需要手动新建或者销毁。
(6)后期实现了多种新框架,Springboot,SpringCloud,SpringMVC等。
(7)可以统一进行异常管理,设置全局的异常捕获即可将所有的异常统一处理,返回。
2.Spring由哪些模块组成?
(1)Spring Core,包含IoC容器、事件(设定Event以及监听,通过applicationContext.publishEvent触发)、资源(支持从url,xml等文件引入一些资源)、i18n、验证、数据绑定(将用户输入的信息绑定到对应的bean中)、类型转换、SpEL、AOP。
(2)Spring Testing,测试框架,单元测试
(3)Spring Data Access,DAO,JDBC,数据库集成,xml解析
(4)Spring Web Servlet,包括Spring MVC, WebSocket, 跨域cors,集成前端开发模版 Thymeleaf、FreeMarker
(5)Spring Web Reactive,Spring WebFlux, WebClient, WebSocket(响应式的), RSocket.
(6)Spring Integration,远程调用,JMS,消息处理,邮件,任务调度定时,缓存(Cacheable一类注解实现)
3.解释AOP模块
(1)主要用来面向切面的开发
(2)常用注解@Aspect@Pointcut
(3)方便在指定的Pointcut前后切面,进行一些日志输出,权限验证,以及某些获取token的操作(比如token有效期过了,对方返回401,程序在这个切面截获401错误,自动触发获取新token,再次重新调用,这样就不会直接把异常返回)
4.解释WEB模块
(1)Web是在历史版本时一个,现在实际上有两个,分别是Web Servlet和Web Reactive
(2)Web Servlet包括Spring MVC, WebSocket(可以在用户的浏览器和服务器之间打开交互式通信会话。ws://127.0.0.1:8888这种url), 跨域cors,集成前端开发模版 Thymeleaf、FreeMarker
(3)Web Reactive包括Spring WebFlux, WebClient(Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具), WebSocket(响应式的), RSocket(RSocket是一种二进制字节流传输协议,没有实际使用过).
5.核心容器(应用上下文) 模块。
(1)提供spring框架的基础功能
(2)Spring整体框架都是建立在此模块之上,它使Spring成为一个容器,个人理解成工厂,主要用来生产装配各种bean,也就是说咱们使用的各种bean几乎都是Spring Core帮忙创建生成的。
(3)不光是创建,实际上负责了对象整个的生命周期的管理——创建、装配、销毁。
(4)应用上下文包括BeanFactory和ApplicationContext,BeanFactory是任何以spring为基础的应用的核心。
(5)ApplicationContext更像一个为我们提供一个维护Bean定义以及对象之间协作关系的高级接口,通过ApplicationContext实体context.getBean(AAA.class)方法获取到AAA这个bean。
6.什么是Spring IOC 容器?
(1)控制反转(IOC)/依赖注入,负责创建对象、管理对象(通过依赖注入(DI)、装配对象、配置对象,并且管理这些对象的整个生命周期。
(2)实现了松散耦合,各个bean相互独立,不需要通过new的方式去手工建立需要使用的bean,通过Spring将bean之间的关联起来,如果bean A依赖bean B,则Spring将B注入到A中,不需要再手工新建bean。
(3)实际上形成了拿来即用的一种方便的模式,需要哪个bean直接注入就可以了,很方便,没有那么强的依赖关系了。
7.IOC的优点是什么?
(1)把很多bean拆散,解偶,容易设计开发
(2)开发成本低,不用考虑各个bean如何实现,分别依赖谁,拿来注入需要使用的bean即可,拿来即用。
8.ApplicationContext通常的实现是什么?
(1)FileSystemXmlApplicationContext,从⼀个XML⽂件中加载beans的定义,从文件系统中读取。
//读取单个xml
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
//或者多个xml
String[] xmls = {"bean1.xml", "bean2.xml", "bean3.xml"};
ctx = new ClassPathXmlApplicationContext(xmls);
(2)ClassPathXmlApplicationContext,从class路径读取XML⽂件中加载beans的定义。
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
(3)XmlWebApplicationContext,专门针对Web项目。
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
(4)AnnotationConfigApplicationContext,基于java配置文件,通过注解引入bean,这个才是我们最常用的,因为咱们几乎已经不再使用xml来读取bean了。
(5)AnnotationConfigWebApplicationContext,同样基于java配置文件,通过注解引入bean,但是这个主要针对Web项目,与XmlWebApplicationContext对应。
9.Bean工厂和ApplicationContexts有什么区别?
(1)BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
(2)BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
(3)ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:继承MessageSource,因此支持国际化;统一的资源文件访问方式;提供在监听器中注册bean的事件;同时加载多个配置文件;载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
(4)BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
(5)ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。因此ApplicationContext占用内存空间,当应用程序配置Bean较多时,程序启动较慢。
2. spring依赖注入
10. 什么是Spring的依赖注入?
(1)依赖注入,实现了松散耦合,各个bean相互独立,不需要通过new的方式去手工建立需要使用的bean,通过Spring将bean之间的关联起来,如果bean A依赖bean B,则Spring将B注入到A中,不需要再手工新建bean。
(2)每次说这个都会想到控制反转(IOC),也就是把控制权给了使用者,谁使用谁只要在代码里讲被使用的bean注入即可。
11.有哪些不同类型的IOC(依赖注入)方式?
(1)构造器依赖注入,通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
public class BeanA {
private String columnA;
private String columnB;
//需要注入的BeanB
private BeanB b;
//构造函数注入BeanB
public BeanA(BeanB b){
this.b = b;
}
}
(2)Setter方法注入,容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
public class BeanA {
private String columnA;
private String columnB;
//需要注入的BeanB
private BeanB b;
//setter注入BeanB
public void setBeanB(BeanB b){
this.b = b;
}
}
(3)静态工厂的方法注入,直接通过static方法注入,由于是静态方法直接指定类的具体静态注入方法即可
public class StaticBeanFactory {
private String columnA;
private String columnB;
//需要注入的BeanB
private BeanB b;
//静态方法工厂注入
public static BeanB createBeanB(){
BeanB b = new BeanB();
return b;
}
}
xml
<bean id="beanB" class="com.test.StaticBeanFactory" factory-method="createBeanB"></bean>
(4)实例工厂的方法注入,由于非静态,需要先通过xml配置BeanFactory,再通过beanFactory的createBeanB方法即可注入
public class BeanFactory {
private String columnA;
private String columnB;
//需要注入的BeanB
private BeanB b;
//实例方法工厂注入
public BeanB createBeanB(){
BeanB b = new BeanB();
return b;
}
}
xml
<bean id="beanFactory" class="com.test.BeanFactory"></bean>
<bean id="beanB" factory-bean="beanFactory" factory-method="createBeanB"></bean>
12.什么是Spring beans?
(1)表示受到Spring管理的对象,是被Spring框架容器初始化、配置和管理的对象。
(2)被SpringIOC容器初始化,装配,和管理。
(3)过去通过xml配置beans,现在主要可以通过注解配置。像@Configuration,@Bean等。
13.一个 Spring Bean 定义 包含什么?
(1)应该问的BeanDefinition,是定义Bean的配置元信息接口。
(2)Spring根据BeanDefinition来创建Bean对象。
(3)包含:Bean的类名,BeanB.class
(4)包含:设置父bean名称、是否为primary(例如多个database数据源的优先级);
(5)包含:Bean行为配置信息,作用域(scope等于singleton,该bean就是一个单例Bean; scope等于prototype,该bean就是一个原型Bean)、自动绑定模式、生命周期回调、延迟加载、初始方法、销毁方法等;
(6)包含:Bean之间的依赖设置,dependencies;构造参数、属性设置。
public class BeanB {
private String columnA;
private String columnB;
public BeanB() {
}
public BeanB(String columnA, String columnB) {
this.columnA = columnA;
this.columnB = columnB;
}
public String getColumnA() {
return columnA;
}
public void setColumnA(String columnA) {
this.columnA = columnA;
}
public String getColumnB() {
return columnB;
}
public void setColumnB(String columnB) {
this.columnB = columnB;
}
@Override
public String toString() {
return "BeanB{" +
"columnA='" + columnA + '\'' +
", columnB='" + columnB + '\'' +
'}';
}
}
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
public class TestBeanDefinition {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// BeanDefinitionBuilder 构建 BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(BeanB.class)
.addPropertyValue("columnA", "123") //属性设置
.addPropertyValue("columnB", "beanb");
beanFactory.registerBeanDefinition("beanb", builder.getBeanDefinition());
BeanB user = beanFactory.getBean("beanb", BeanB.class);
System.out.println(user);
//直接 new 的方式,创建 BeanDefinitionBuilder 对象
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanB.class);
//构造参数
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, "456");
constructorArgumentValues.addIndexedArgumentValue(1, "beanb2");
beanDefinition.setConstructorArgumentValues(constructorArgumentValues);
beanFactory.registerBeanDefinition("beanb2", beanDefinition);
BeanB beanb2 = beanFactory.getBean("beanb2", BeanB.class);
System.out.println(beanb2);
}
}
//输出
17:18:26.594 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanb'
BeanB{columnA='123', columnB='beanb'}
17:18:26.687 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanb2'
BeanB{columnA='456', columnB='beanb2'}
14.解释Spring支持的几种bean的作用域。
(1)Spring框架支持以下五种bean的作用域singleton,prototype,request,session,global-session。
(2)singleton(单例模式),bean在每个Spring ioc容器中只有一个实例,每次bean的请求,都是同一个bean。
(3)prototype(原型模式),一个bean的定义可以有多个实例,实际上是每次bean的请求,都生成一个bean,和单例模式的对应。
(4)request(HTTP请求),每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
(5)session(会话),在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
(6)global-session(全局会话),在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。Portlet的Web应用使用的,不常用。
15.Spring框架中的单例bean是线程安全的吗?
不安全,由于是单例,那么当并发请求时,多个线程同时请求到该单例的bean,如果bean中有针对变量的修改,第一个线程修改完毕后,打印该变量,但是打印该变量之前,第二个线程修改了该变量,则第一个线程打印的变量为第二个线程修改后的值。
16.解释Spring框架中bean的生命周期
(1)首先需要注意的是,这个是Spring框架中的bean的生命周期,不要从咱们使用的方式来理解
(2)Spring对bean进行实例化
(3)Spring将值和bean的引用注入到bean对应的属性中
(4)如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBeanName()方法
import org.springframework.beans.factory.BeanNameAware;
public class BeanNameAwareTest implements BeanNameAware {
//来自BeanNameAware
@Override
public void setBeanName(String name) {
System.out.println("invoke setBeanName " + name);
}
}
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@Configurable
public class SpringConfig {
//实际上这个注解Bean中的name对应上面的setBeanName方法的入参
@Bean(name ="beanNameAwareTest1")
public BeanNameAwareTest beanNameAwareTest() {
BeanNameAwareTest bean = new BeanNameAwareTest();
return bean;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BeanNameAwareTest bean = ctx.getBean(BeanNameAwareTest.class);
ctx.close();
}
}
//输出
invoke setBeanName beanNameAwareTest1
(5)如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
public class BeanFactoryAwareTest implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public void getMyBeanName() {
BeanNameAwareTest beanNameAwareTest = beanFactory.getBean(BeanNameAwareTest.class);
System.out.println(beanNameAwareTest);
System.out.println(beanFactory.isSingleton("beanNameAwareTest1"));
}
}
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@Configurable
public class SpringConfigTest {
@Bean(name ="beanNameAwareTest1")
public BeanNameAwareTest beanNameAwareTest() {
BeanNameAwareTest bean = new BeanNameAwareTest();
return bean;
}
@Bean(name ="beanFactoryAwareTest")
public BeanFactoryAwareTest beanFactoryAwareTest() {
BeanFactoryAwareTest bean = new BeanFactoryAwareTest();
return bean;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfigTest.class);
BeanFactoryAwareTest myBeanFactory = context.getBean(BeanFactoryAwareTest.class);
myBeanFactory.getMyBeanName();
}
}
//输出
invoke setBeanName beanNameAwareTest1
com.buxuesong.account.api.config.BeanNameAwareTest@c8e4bb0
true
(6)如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来,主要是用来为bean处理以及操作各种bean提供条件
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class ApplicationContextAwareTest implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("invoke setApplicationContext === " + applicationContext);
}
}
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@Configurable
public class SpringConfigTest {
@Bean(name ="applicationContextAwareTest")
public ApplicationContextAwareTest applicationContextAwareTest() {
ApplicationContextAwareTest bean = new ApplicationContextAwareTest();
return bean;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfigTest.class);
ApplicationContextAwareTest applicationContextAwareTest = context.getBean(ApplicationContextAwareTest.class);
}
}
//输出
invoke setApplicationContext === org.springframework.context.annotation.AnnotationConfigApplicationContext@1ff8b8f, started on Tue May 17 16:37:48 CST 2022
(7)如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization(),表示在Initialization之前执行。作用是在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前(注意按照步骤,现在是前)后(这个在postProcessAfterInitialization部分写)添加我们自己的逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class BeanPostProcessorTest implements BeanPostProcessor {
//来自BeanPostProcessor
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("invoke postProcessBeforeInitialization === " + beanName);
return bean;
}
//来自BeanPostProcessor
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("invoke postProcessAfterInitialization === " + beanName);
return bean;
}
public String test(){
System.out.println("test is called");
return "test";
}
}
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
@Configurable
public class SpringConfigTest {
//注意下面的Scope为prototype的模式,只有这种模式下才会每次调用postProcessBeforeInitialization
//和postProcessAfterInitialization,默认的单例不会每次都创建也就不会执行该方法
@Bean(name ="beanPostProcessorTest")
@Scope("prototype")
public BeanPostProcessorTest beanPostProcessorTest() {
BeanPostProcessorTest bean = new BeanPostProcessorTest();
return bean;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfigTest.class);
BeanPostProcessorTest beanPostProcessorTest = context.getBean(BeanPostProcessorTest.class);
System.out.println(beanPostProcessorTest.test());
beanPostProcessorTest = context.getBean(BeanPostProcessorTest.class);
System.out.println(beanPostProcessorTest.test());
}
}
//输出
invoke postProcessBeforeInitialization === beanPostProcessorTest
invoke postProcessAfterInitialization === beanPostProcessorTest
test is called
test
invoke postProcessBeforeInitialization === beanPostProcessorTest
invoke postProcessAfterInitialization === beanPostProcessorTest
test is called
test
(8)如果bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()方法。另外,如果bean使用init-method(xml中)声明了初始化方法,该方法也会被调用
//xml配置了init-method,则会在调用testInit方法之后在调用afterPropertiesSet()方法
<bean id = "initializingBeanTest" class = "com.init.InitializingBeanTest" init-method="testInit">
<property name="id" value="#{1111111}" />
</bean>
//另一种直接初始化的方式
import org.springframework.beans.factory.InitializingBean;
public class InitializingBeanTest implements InitializingBean {
//来自InitializingBean
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("invoke afterPropertiesSet");
}
}
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@Configurable
public class SpringConfigTest {
@Bean(name ="initializingBeanTest")
public InitializingBeanTest initializingBeanTest() {
InitializingBeanTest bean = new InitializingBeanTest();
return bean;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfigTest.class);
InitializingBeanTest initializingBeanTest = context.getBean(InitializingBeanTest.class);
}
}
//输出
invoke afterPropertiesSet
(9)如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessAfterInitialization()方法,表示在Initialization之后执行。具体已经在上面postProcessBeforeInitialization部分写了。
(10)此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
(11)如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。
import org.springframework.beans.factory.DisposableBean;
public class DisposableBeanTest implements DisposableBean {
//来自DisposableBean
@Override
public void destroy() throws Exception {
System.out.println("invoke destroy");
}
}
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@Configurable
public class SpringConfigTest {
@Bean(name ="disposableBeanTest")
public DisposableBeanTest disposableBeanTest() {
DisposableBeanTest bean = new DisposableBeanTest();
return bean;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfigTest.class);
DisposableBeanTest disposableBeanTest = context.getBean(DisposableBeanTest.class);
context.close();
}
}
//输出
invoke destroy
(12)最后写一个总结的吧
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class SpringBeanLife implements BeanNameAware, BeanClassLoaderAware, ApplicationContextAware,
InitializingBean, BeanPostProcessor, DisposableBean {
private String id;
/******************* 下面是方法按时间执行的先后顺序 *************************/
//初始化
SpringBeanLife() {
System.out.println("bean init");
}
//参数注入
public void setId(String id) {
this.id = id;
System.out.println("invoke set method");
}
//来自BeanNameAware
@Override
public void setBeanName(String name) {
System.out.println("invoke setBeanName" + name);
}
//来自BeanClassLoaderAware
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("invoke setBeanClassLoader");
}
//来自ApplicationContextAware
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("invoke setApplicationContext");
}
//来自注解@PostConstruct
@PostConstruct
public void postConstructMethod() {
System.out.println("invoke postConstructMethod");
}
//来自InitializingBean
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("invoke afterPropertiesSet");
}
//来自配置initMethod = "initMethod"
public void initMethod() {
System.out.println("invoke init-method");
}
//来自BeanPostProcessor
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("invoke postProcessBeforeInitialization");
return bean;
}
//来自BeanPostProcessor
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("invoke postProcessAfterInitialization");
return bean;
}
//bean使用(如此时调用了下面的use方法)
public void use() {
System.out.println("use bean");
}
//来自注解@PreDestroy
@PreDestroy
public void preDestroyMethod() {
System.out.println("invoke preDestroyMethod");
}
//来自bean定义中的配置destroyMethod = "destoryMethod"
public void destoryMethod() {
System.out.println("invoke destory-method");
}
//来自DisposableBean
@Override
public void destroy() throws Exception {
System.out.println("invoke destroy");
}
}
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@Configurable
public class SpringConfig {
@Bean(name ="testSpringBeanLife", initMethod = "initMethod", destroyMethod = "destoryMethod")
public SpringBeanLife springBeanLife() {
SpringBeanLife bean = new SpringBeanLife();
bean.setId("001");
return bean;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
SpringBeanLife bean = ctx.getBean(SpringBeanLife.class);
bean.use();
ctx.close();
}
}
//输出
bean init
invoke set method
invoke setBeanNametestSpringBeanLife
invoke setBeanClassLoader
invoke setApplicationContext
invoke postConstructMethod
invoke afterPropertiesSet
invoke init-method
use bean
17:38:58.541 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@50cbc42f, started on Tue May 17 17:38:58 CST 2022
invoke preDestroyMethod
invoke destroy
invoke destory-method
17.什么是bean装配?
(1)创建应用对象之间协作关系的行为称为装配(wiring),在类中表现为组合,所以依赖的装配也就是DI的目的。说人话就是将BeanB注入到BeanA的这个注入的过程,将两个bean关联起来,促使BeanA可以直接使用BeanB。
(2)分别为基于XML的装配,基于JavaConfig装配,自动装配。
(3)基于XML的装配,Spring提供了2种基于XML的装配方式,设值注入(Setter Injection)和构造注入(Constructor Injection),这种历史比较悠久了,现在少有人这么做了,因此我就忽略了。
(4)基于JavaConfig装配,@Configuration就创建了一个配置类,在方法上使用@Bean注解来声明一个bean,即可将需要使用的bean装配。
//@Configuration指明是一个JavaConfig配置类
@Configuration
public class TestConfig {
//@Bean修饰的方法用来声明一个bean
@Bean
public TestService testService() {
TestService testService = new TestService();
//组装这个bean,对testService进行操作
testService.setTest("111");
//返回组装好的bean对象
return testService;
}
}
(5)自动装配,实际上就是咱们现在经常使用的autowired注解,使用哪一个bean只需要@Autowired注解引入就可以了。过去是通过xml中配置的autowire类型有byName,byType,constructor三种。所以实际上是四种,@Autowired注解,byName,byType,constructor。
//byName方式
<bean id="testBeanA" class="com.yiibai.common.TestBeanA" />
<bean id="testBeanB" class="com.yiibai.common.TestBeanB" autowire="byName" />
public class TestBeanA {
private TestBeanB testBeanB;
public void query() {
TestBeanB.query();
}
//注意通过byName时候,set方法的属性名称要与xml中的id的保持一致
public void setTestBeanB1(TestBeanB testBeanB) {
this.testBeanB = testBeanB;
}
}
//byType方式
<bean id="testBeanA" class="com.yiibai.common.TestBeanA" />
<bean id="testBeanB" class="com.yiibai.common.TestBeanB" autowire="byType" />
public class TestBeanA {
private TestBeanB testBeanB;
public void query() {
testBeanB.query();
}
//注意通过byName时候,set方法的属性类型要与xml中的类型的保持一致
public void setTestBeanB1(TestBeanB testBeanB) {
this.testBeanB = testBeanB;
}
}
//constructor方式
<bean id="testBeanA" class="com.yiibai.common.TestBeanA" />
<bean id="testBeanB" class="com.yiibai.common.TestBeanB" autowire="constructor" />
public class TestBeanA {
private TestBeanB testBeanB;
public void query() {
testBeanB.query();
}
public TestBeanA(TestBeanB testBeanB) {
this.testBeanB = testBeanB;
}
}
//当前常用的Autowired方式
public class TestBeanA {
@Autowired
private TestBeanB testBeanB;
public void query() {
testBeanB.query();
}
}
18.什么是bean的自动装配?
自动装配,实际上就是咱们现在经常使用的autowired注解,使用哪一个bean只需要@Autowired注解引入就可以了。过去是通过xml中配置的autowire类型有byName,byType,constructor三种。所以实际上是四种,@Autowired注解,byName,byType,constructor。
19.解释不同方式的自动装配
//byName方式
<bean id="testBeanA" class="com.yiibai.common.TestBeanA" />
<bean id="testBeanB" class="com.yiibai.common.TestBeanB" autowire="byName" />
public class TestBeanA {
private TestBeanB testBeanB;
public void query() {
TestBeanB.query();
}
//注意通过byName时候,set方法的属性名称要与xml中的id的保持一致
public void setTestBeanB1(TestBeanB testBeanB) {
this.testBeanB = testBeanB;
}
}
//byType方式
<bean id="testBeanA" class="com.yiibai.common.TestBeanA" />
<bean id="testBeanB" class="com.yiibai.common.TestBeanB" autowire="byType" />
public class TestBeanA {
private TestBeanB testBeanB;
public void query() {
testBeanB.query();
}
//注意通过byName时候,set方法的属性类型要与xml中的类型的保持一致
public void setTestBeanB1(TestBeanB testBeanB) {
this.testBeanB = testBeanB;
}
}
//constructor方式
<bean id="testBeanA" class="com.yiibai.common.TestBeanA" />
<bean id="testBeanB" class="com.yiibai.common.TestBeanB" autowire="constructor" />
public class TestBeanA {
private TestBeanB testBeanB;
public void query() {
testBeanB.query();
}
public TestBeanA(TestBeanB testBeanB) {
this.testBeanB = testBeanB;
}
}
//当前常用的Autowired方式
public class TestBeanA {
@Autowired
private TestBeanB testBeanB;
public void query() {
testBeanB.query();
}
}
20. 自动装配有哪些局限性?
(1)你不能自动装配简单的属性,如原生类型、字符串和类。
(2)自动装配不如显式装配精确。
(3)建议使用@Autowired注解,非常方便,注入精确。
3. spring 注解
21.怎样开启注解装配?
(1)要使用 @Autowired,需要注册 AutowiredAnnotationBeanPostProcessor,可以有以下两种xml的方式来实现
(2)引入配置文件中的
<beans>
<context:annotation-config />
</beans>
(3)在bean配置文件中直接引入AutowiredAnnotationBeanPostProcessor
<beans>
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
</beans>
(4)现在咱们通过springboot可以在启动类直接通过@SpringBootApplication注解实现。这个注解是个组合注解,其中包含@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解组成的。其中@EnableAutoConfiguration即为启动注解。
22.谈谈@Required、 @Autowired、 @Qualifier注解。
(1)@Required,只能用于method的注解,适用于bean属性的setter方法。
import org.springframework.beans.factory.annotation.Required;
public class BeanA {
private BeanB beanB;
@Required
public void setBeanB(BeanB beanB){
this.beanB = beanB;
}
}
(2)@Autowired,相当于替换@Required注解,可以在方法,变量,构造器上使用,均可注入。
import org.springframework.beans.factory.annotation.Autowired;
public class BeanA {
//可以在beanB属性上写@Autowired,即可省略set方法
@Autowired
private BeanB beanB;
//也可以在构造器使用
@Autowired
public BeanA (BeanB beanB){
this.beanB = beanB;
}
//可以在set方法使用
@Autowired
public void setBeanB(BeanB beanB){
this.beanB = beanB;
}
}
(3)@Qualifier,按类型自动装配可能多个 bean 实例的情况,可以使用 @Qualifier 注解缩小范围(或指定唯一),也可以用于指定单独的构造器参数或方法参数。常见的方式就是咱们有多个datasource数据源,也就是一个项目要访问两个以上的数据库时,需要通过@Qualifier指定具体的Datasource。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class BeanA {
@Autowired
//可能会有多个BeanB类型的实例,因此可以指定具体的BeanB类型的名字来区分
@Qualifier("beanB")
private BeanB beanB;
}
4. spring 数据访问
23.在Spring框架中如何更有效地使用JDBC?
(1)这个问题我先吐槽下,不咋地。
(2)个人认为这个就是在问JDBCTemplate,通过JDBCTemplate,可以使开发者免去考虑数据连接的关闭等一些其他操作。
(3)直接通过jdbcTemplate.queryForList(),jdbcTemplate.update(),jdbcTemplate.queryForObject(),jdbcTemplate.batchUpdate()使用。
(4)jdbcTemplate不像jpa那么重量级嵌入,jpa封装了很多实际没有使用方法,jdbcTemplate就比较简单,只负责具体sql的执行,不做额外的操作,效率上高于jpa。具体来说,jpa的save方法,如果有id会调用一次查询,没有该id才会进行insert操作,而jdbcTemplate则直接insert,如果有存在数据直接报异常,因此jpa实际上是2次sql交互,而jdbcTemplate只有一次操作。
24.Spring框架的事务管理有哪些优点?
(1)为不同的数据库中间件(如JPA,JDBC,MyBatis等)提供统一的编程模型,依然是注入到具体Service中统一调用。
(2)为编程式事务提供了一个简单的API而非一系列的复杂的事务API。例如PlatformTransactionManager,所有事务代理类都是基于PlatformTransactionManager接口的实现。
(3)支持声明式事务管理。现在通过@Transactional放在具体需要实现事物的方法或者类上就可以统一方便实现。
25. 使用 Spring 通过什么方式访问 Hibernate ?
(1)这道题我感觉问的有些奇怪,通过相关资料找到以下的解答
(2)Spring控制反转 Hibernate Template 和 Callback
(3)继承 HibernateDAOSupport 提供一个 AOP 拦截器。
5. Spring面向切面编程(AOP)
26. 解释 AOP
(1)面向切面编程,可独立于业务层代码之外,单独编写切面逻辑,例如常见的在过滤器中打印一些特殊的日志输出,校验一些权限验证等操作。
(2)通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
(3)说白了AOP就是把程序中公共的代码片段抽取出来,然后动态代理出来一个对象。就像一个函数一样,里面封装了我们的公共代码,这样就省去了每次都要在业务代码中调用公共部分,在某一个环节通过动态代理来执行该部分公共代码。
27. Aspect 切面
Aspect 切⾯,切⼊系统的⼀个切⾯。⽐如事务管理是⼀个切⾯,权限管理也是⼀个切⾯,日志输出也是一个切面,Spring的异常统一处理也是⼀个切⾯。
28. Join point 连接点
Join point 连接点,也就是可以进⾏横向切⼊的位置
29. Advice 通知
(1)Advice 通知,切⾯在特定接⼊点的执⾏动作,包括 around(切入在接入点前后,能够在执行前执行后均有操作),before(切入在接入点执行前操作),after(切入在接入点执行完成后操作)等多种类型。
(2)包含 Spring 在内的许多 AOP 框架,通常会使⽤拦截器来实现Advice,围绕着接⼊点维护着⼀个拦截器链。
30. Pointcut 切点
(1)Pointcut 切点,⽤来匹配特定接⼊点的谓词(表达式),增强将会与切点表达式产⽣关联,并运⾏在任何切点匹配到的接⼊点上。
(2)通过切点表达式匹配接⼊点是AOP的核⼼,Spring默认使⽤AspectJ的切点表达式。
31. 什么是织入Weaving。什么是织入应用的不同点?
(1)Weaving织⼊,将⼀个或多个切⾯与类或对象链接在⼀起创建⼀个被增强对象。
(2)Spring AOP 框架仅支持有限的几个 AspectJ 切入点的类型,它允许将切面运用到在 IoC 容器中声明的 bean 上。如果你想使用额外的切入点类型或者将切面应用到在 Spring IoC 容器外部创建的类,那么你必须在你的 Spring 程序中使用 AspectJ 框架,并且使用它的织入特性。(3)织⼊能发⽣在编译时(compile time)(使⽤AspectJ编译器),加载时(load time),或运⾏时(runtime)。Spring AOP默认就是运⾏时织⼊,可以通过来设枚举AdviceMode置。
(4)个人理解就是咱们常用的AOP都是在Spring的生命周期内的切面,但是还是会存在一些切面,在Spring生命周期之外,这些时期也可以通过一些其他方式来使用AOP,也就是通过AspectJ框架,在初始化Spring前也能够使用AOP。
32.在Spring AOP 中,关注点和横切关注的区别是什么?(JoinPoint和PointCut有什么区别?)
(1)JoinPoint可以理解为可以进行切面的位置,但是我们可以选择切入或者不切入,具体是否切入需要通过PointCut来指定。
(2)PointCut是通过正则表达式标识出所有需要进行切面的具体位置,PointCut设定好后,AOP才能成功切入。
33. 有几种不同类型的自动代理?
(1)AOP有三种自动代理类BeanNameAutoProxyCreator(通过BeanName名字来自动切入的代理类),DefaultAdvisorAutoProxyCreator(默认的自动切入代理类,通过Advisor中的规则来切入,例如需要切入的方法的正则来过滤),Metadata autoproxying(元数据自动代理)
(2)其中BeanNameAutoProxyCreator具体实现如下
//具体Service接口以及实现类
public interface TestAopService {
void testMothod();
}
public class TestAopServiceImpl implements TestAopService {
public void testMothod(){
System.out.println(getClass()+" testMothod");
}
}
//测试AOP调用方法类,执行方法前后打印日志
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class TestMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println(getClass()+"调用方法前");
Object ret=invocation.proceed();
System.out.println(getClass()+"调用方法后");
return ret;
}
}
//具体Configuration
import org.aopalliance.aop.Advice;
import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
//要创建代理的目标Bean
@Bean
public TestAopService testAopService(){
return new TestAopServiceImpl();
}
//创建Advice或Advisor
@Bean
public Advice testMethodInterceptor(){
return new TestMethodInterceptor();
}
//使用BeanNameAutoProxyCreator来创建代理
@Bean
public BeanNameAutoProxyCreator beanNameAutoProxyCreator(){
BeanNameAutoProxyCreator beanNameAutoProxyCreator=new BeanNameAutoProxyCreator();
//设置要创建代理的那些Bean的名字
beanNameAutoProxyCreator.setBeanNames("testAopSer*");
//设置拦截链名字(这些拦截器是有先后顺序的)
beanNameAutoProxyCreator.setInterceptorNames("testMethodInterceptor");
return beanNameAutoProxyCreator;
}
public static void main(String[] args) {
ApplicationContext applicationContext=new AnnotationConfigApplicationContext(AppConfig.class);
TestAopService testAopService = applicationContext.getBean(TestAopService.class);
testAopService.testMothod();
}
}
//输出,可以看出来,已经在Beanname为testAopSer开头的类方法执行前后切入成功了
class com.buxuesong.account.api.aop.TestMethodInterceptor调用方法前
class com.buxuesong.account.api.aop.TestAopServiceImpl testMothod
class com.buxuesong.account.api.aop.TestMethodInterceptor调用方法后
(3)DefaultAdvisorAutoProxyCreator具体实现如下,具体service以及interceptor不变,只需要修改Configuration类
import org.aopalliance.aop.Advice;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
//要创建代理的目标Bean
@Bean
public TestAopService testAopService(){
return new TestAopServiceImpl();
}
//创建Advice或Advisor
@Bean
public Advice testMethodInterceptor(){
return new TestMethodInterceptor();
}
//使用Advice创建Advisor,对应到具体的执行方法,通过正则过滤需要切入的方法,通过setMappedname方式实现。
@Bean
public NameMatchMethodPointcutAdvisor nameMatchMethodPointcutAdvisor(){
NameMatchMethodPointcutAdvisor nameMatchMethodPointcutAdvisor=new NameMatchMethodPointcutAdvisor();
nameMatchMethodPointcutAdvisor.setMappedName("testMothod*");
nameMatchMethodPointcutAdvisor.setAdvice(testMethodInterceptor());
return nameMatchMethodPointcutAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
return new DefaultAdvisorAutoProxyCreator();
}
public static void main(String[] args) {
ApplicationContext applicationContext=new AnnotationConfigApplicationContext(AppConfig.class);
TestAopService testAopService = applicationContext.getBean(TestAopService.class);
testAopService.testMothod();
}
}
//输出
class com.buxuesong.account.api.aop.TestMethodInterceptor调用方法前
class com.buxuesong.account.api.aop.TestAopServiceImpl testMothod
class com.buxuesong.account.api.aop.TestMethodInterceptor调用方法后
(4)Metadata autoproxying
6. springMVC
34.什么是 Spring 的 MVC 框架?
(1)SpringMvc 是一种web层mvc框架,它是spring的一个模块,拥有spring的特性。SpringMvc分离了控制器、模型对象、分派器以及处理程序对象的角色。
(2)Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。可以选择是使用内置的 Spring Web 框架还是 Struts 这样的 Web 框架。
(3)SpringMVC运行原理:客户端请求提交到DispatcherServlet;由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller;DispatcherServlet将请求提交到Controller;Controller调用业务逻辑处理后,返回ModelAndView;DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图;视图负责将结果显示到客户端
35. DispatcherServlet
(1)DispatcherServlet是前置控制器,配置在web.xml文件当中的,拦截匹配的请求,Servlet拦截匹配规则要自己定义,将拦截下来的请求按照相应的规则分发到目标Controller来处理,是配置spring MVC的第一步。
(2)DispatcherServlet是前端控制器设计模式的实现。
(3)提供Spring Web MVC的集中访问点,负责职责的分派,和Spring IoC容器无缝集成,从而可以获得Spring的所有好处。
(4)DispatcherServlet的主要职责:
文件上传解析,假如,请求类型是multipart将通过MultipartResolver进行文件上传解析
通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器)
通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器)
通过ViewResolver解析逻辑视图名到具体视图实现
本地化解析
渲染具体的视图等
如果执行过程中遇到异常将交给HandlerExceptionResolver来解析
(5)DispatcherServlet 类中的属性 beans:
HandlerMapping:用于 handlers 映射请求和一系列的对于拦截器的前处理和后处理,大部分用 @Controller 注解
HandlerAdapter:帮助 DispatcherServlet 处理映射请求处理程序的适配器,而不用考虑实际调用的是哪个处理程序
ViewResolver:根据配置解析实际的 View 类型
ThemeResolver:解决 Web 应用程序可以使用的主题,例如提供个性化布局
MultipartResolver:解析多部分请求,以支持从 HTML 表单上传文件
FlashMapManager:存储并检索可用于将一个请求属性传递到另一个请求的 input 和 output 的 FlashMap,通常用于重定向
(6)DispatcherServlet默认使用WebApplicationContext作为上下文
36. WebApplicationContext
(1)WebApplicationContext 专门为Web应用准备的,它允许从Web根目录的路径中装载配置文件并完成初始化工作。
(2)因为WebApplicationContext是专门为Web应用设计的,所以WebApplicationContext需要ServletContext实例,也就是必须需要Web容器才可以完成初始化。
(3)WebApplicationContext 还为Bean提供了三个新的作用域,request、session和globalsession。其中两个参数HttpServletRequest:服务器从客户端拿去数据,HttpServletResponse:服务器向前台传送数据。
37. 什么是Spring MVC框架的控制器?
(1)Spring MVC框架的控制器提供一个访问应用程序的行为,此行为通常通过服务接口实现。
(2)控制器解析用户输入并将其转换为一个由视图呈现给用户的模型。
(3)Spring用一个非常抽象的方式实现了一个控制层,允许用户创建多种用途的控制器。
(4)具体可以通过@Controller 注解和@RequestMapping 注解来具体实现不同的控制器。
38. @Controller 注解
(1)以前在编写Controller方法的时候,需要开发者自定义一个Controller类实现Controller接口,实现handleRequest方法返回ModelAndView。并且需要在Spring配置文件中配置Handle,将某个接口与自定义Controller类做映射。引入@Controller 注解和@RequestMapping注解配合使用后,可以省去配置xml配置。
(2)@Controller 注解用于标注控制层组件,标识该类就成了一个控制器类。
39. @RequestMapping 注解
(1)@RequestMapping 注解将请求和处理请求的控制器⽅法关联起来,建⽴映射关系。
(2)@RequestMapping表示共享映射,如果没有指定请求方式,将接收GET、POST、HEAD、OPTIONS、PUT、PATCH、DELETE、TRACE、CONNECT所有的HTTP请求方式。@GetMapping、@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping 都是HTTP方法特有的快捷方式@RequestMapping的变体,分别对应具体的HTTP请求方式的映射注解。
@RequestMapping(value = "/removeAccount", method = RequestMethod.POST)
//与下面的方式意思相同
@PostMapping(value = "/removeAccount")
(3)可以配置多个路径
@RequestMapping(value = {
"",
"/page",
"page*",
"view/*,**/msg"
})
40. @PathVaraible 注解
@PathVaraible 注解和@RequestMapping或者具体的GetMapping配合使用,在path中通过{}中指定在url中传递的参数
@GetMapping(value = "/test/{id}")
public String test(@PathVariable String id)
41. @RequestParam 注解
@RequestParam 注解同样和@RequestMapping或者具体的@GetMapping @PostMapping配合使用,以下表示在url/test?id=123的路径下,直接取出id值
@GetMapping(value = "/test")
public String test(@RequestParam String id)
42. @RequestBody 注解
@RequestParam 注解同样和@RequestMapping或者具体的@PostMapping @PutMapping等配合使用,注意不能够和@GetMapping使用,因为我也没见过Get的method下通过RequestBody传递参数的。如下代码表示传递的RequestBody结构体为BeanA的json参数
@PostMapping(value = "/addValue")
public String test(@RequestBody BeanA beanA)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。