1、spring是什么?
spring是一个轻量级IOC和AOP容器框架,是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用的开发,开发者只需要关注业务需求即可:
- core container 容器组件
- spring context,容器上下文
- spring core 核心类库,所有功能都依赖该类库,提供IOC和DI服务
- spring beans 进行实例的管理
- spring AOP 提供AOP服务
- spring web -- 实际就是MVC框架,spring提供了面向web的综合特性,提供了对常见web框架的支持,spring可以管理这些框架,并将spring的资源注入给框架,也可以在框架的前后插入拦截器。
- data acess --- spring的数据访问组件,帮助完成事务管理及持久化操作(没有mybatis也可以完成持久化操作)
- test --- 单元测试组件
2 spring 的优点?
- 基于POJO的轻量级和最小侵入性编程
- 通过依赖注入和面向接口,减低了组件的耦合性
- 提供了AOP技术,支持将一些通用的任务,如安全,日志,事务等进行集中管理,从而实现更好的代码复用
- spring对主流的应用框架提供了集成支持
3、spring框架中都用到了哪些涉及模式?
- 工厂模式: BeanFactory 简单工厂模式,用来创建对象
- 单例模式:spring创建出来的bean默认就是单例模式
- 代理模式:spring的aop功能用到了动态代理技术(jdk、CGLib)
- 模板方法:解决代码重复问题,比如 RestTemplate, JmsTemplate, JpaTemplate
- 观察者模式:定义对象键,一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖他的对象都会得到通知被动刷新,如spring中listener的实现 ---ApplicationListener
4、spring框架中有哪些不同类型的事件?
spring提供了一些5种标准的事件:
- 上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
- 上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
- 上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
- 上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
- 请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。
5 、spring 的IOC?
5.1、什么是IOC?
IOC,inversion of control,控制反转,将对象的控制权转移给spring框架进行管理,由spring来控制对象的生命周期(创建,销毁)和对象之间的依赖关系。
也就是说,以前创建对象的时机和主动权是由开发者把握的,如果在一个对象中使用外面的对象,就需要new去创建对象,用完后还涉及到对象的销毁,这种情况下,当前对象就会和其他的接口或者类耦合起来。而IOC则是由专门的容器去创建对象,我们只需要将所有的类在spring中进行登记,当需要某个对象时,只需要告诉spring容器,然后spring就会在适当的时机,把想要的对象主动给我们。也就是说,IOC中,所有的容器都被spring控制,spring容器帮我们创建,查找,注入依赖对象。
5.2 、什么是DI
DI ,dependency injection 依赖注入,在spring完成对象创建的同时,完成对象属性的赋值。这个行为是动态的。
容器全权负责依赖的查询,受管组件只需要暴露JavaBean的setter方法或者带参数构造器,容器就可以在初始化的时候组装对象的依赖关系。
依赖注入的实现方式?
- 接口注入(不用)
- setter方法注入 --- bean标签中通过property标签的value属性进行赋值(实际就是通过反射调用set方法完成属性输入)
- 构造器注入 --- Java类中定义带参数构造器,<constructor-arg index="0" value="10001"/> ,index是参数的位置
两种依赖方式都可以使用,一般来说,自己定义的类,用setter注入比较多,第三方的技术整合时,构造器注入用的比较多。
5.3 、IOC的原理
spring ioc的实现原理就是工厂模式加反射。简单实例代码如下
Class.forName(ClassName).newInstance();
interface Fruit {
public abstract void eat();
}
class Apple implements Fruit {
public void eat(){
System.out.println("Apple");
}
}
class Orange implements Fruit {
public void eat(){
System.out.println("Orange");
}
}
class Factory {
public static Fruit getInstance(String ClassName) {
Fruit f=null;
try {
f=(Fruit)Class.forName(ClassName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
class Client {
public static void main(String[] a) {
Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple");
if(f!=null){
f.eat();
}
}
}
5.4、 IOC的作用?
- 管理对象的创建和依赖的维护
- 解耦,由容器去维护具体的对象
- 托管类的生产过程,如通过代理在类的生产过程中做一些处理,交给容器去做。
5.5 、spring的IoC支持哪些功能?
- 依赖注入DI --- 最重要的功能,从XML的配置上说,就是ref标签,对应着spring的RuntimeBeanRefrence对象。
- 依赖检查
- 自动装配
- 指定初始化和销毁方法
- 支持回调某些方法
6、BeanFactory和ApplicationContext有什么区别?
BeanFactory和ApplicationContext是spring的两大核心接口,都可以当做spring的容器,其中ApplicationContext是BeanFactory的子接口。
依赖关系:
BeanFactory:是spring中最底层的接口,包含了各种bean的定义,读取bean配置文档,管理bean的加载,实例化,控制bean的生命周期,维护bean之间的依赖关系。
ApplicationContext是BeanFactory的子接口,除了提供BeanFactory所有的功能外,还提供了更完整的框架功能(其他继承接口的功能),如支持国际化(继承MessageSource)、统一的资源文件访问方式,提供在监听器中注册bean的事件,同时加载多个配置文件等。
加载方式:
BeanFactory采用的是延迟加载形式来注入Bean,因此只有在使用到某个Bean时(调用getBean()方法),才会该Bean进行加载实例化,这样如果存在配置问题,不容易第一时间发现
ApplicationContext是在容器启动的时候,一次性创建所有的Bean, 这样在容器启动过程中就可以发现Spring中的配置错误,有利于检查依赖是否注入。不足是占用内存空间,但在是也可以定义成延迟加载,bean的元数据中定义 lazy-init=“true”
7、spring是如何设计容器的,BeanFactory和ApplicationContext关系详解
BeanFactory 可以称为“低级容器”,底层是个HashMap, key是BeanName,value是Bean的实例,通常只提供注册(put),和获取(get)功能。
ApplicationContext可以称为“高级容器”,他继承了多个接口,因此具备了更多的功能,这个接口定义了一个refresh方法,用于刷新整个容器,即重新加载所有的bean。
IoC在spring中,其实只需要低级容器就可以实现,两个步骤如下:
1、加载配置文件,解析成BeanDefinition放在Map中;
2、调用getBean的时候,从BeanDefinition所属的Map中,拿出Class对象进行实例化,如果有依赖关系,则递归调用getBean方法,完成依赖注入。
至于高级容器ApplicationContext,包含了上述功能,同时支持不同的信息源头,支持访问文件资源,支持事件发布通知,支持接口回调等等功能。
8、ApplicationContext通常的实现有哪些?
FileSystemXmlApplicationContext: 从一个xml文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。---常用于配置文件在文件系统路径下
ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。--- 常用于配置文件在类路径下。
WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。
AnnotationConfigApplicationContext:基于注解加载bean,避免使用application.xml进行配置。相比XML配置,更加便捷。
9、什么是spring beans?
在spring中,构成应用程序主干并由spring IOC管理的对象称为bean,或者说是被Spring IoC容器初始化,装配和管理的Java对象,这些beans通过容器配置的元数据创建。
bean相关的元数据
<bean
id="bookDao" ----bean的id
name="dao" ----bean的别名
class="com.itheima.dao.BookDaoImpl" ----bean的类型,全路径名
scope="singleton" ----控制bean的实例数量,默认是singleton单例,prototype是多例bean
init-method="init" ---- bean生命周期的初始化方法
destory-method="destory" ----bean生命周期的销毁方法,销毁前运行的方法
autowire="byType" ----自动装配类型
factory-method="getInstance" ----bean的工厂方法,应用于静态工厂或者实例工厂
factory-bean="com.itheima.factory.BookDaoFactory" ----实例工厂bean
lazy-init="true" ----控制bean的延迟加载,默认是false
/>
依赖注入相关的元数据
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
<property name="bookDao" ref="bookDao"/>
</bean>
10、怎么定义类的作用域?spring支持哪几种作用域?
spring中,可以通过bean定义中的scope属性来定义bean 的作用域,spring中默认的作用域是singleton,表示单例模式。
spring中支持bean的几种作用域如下
- singleton 单例bean,在每个spring IOC 容器中只有一个实例
- prototype:多例bean,一个bean的定义有多个实例,相当于new ---频繁的创建和销毁bean会带来比较大的开销,需要注意。
- request:每次http请求都会创建一个bean,该作用域仅在基于web的spring ApplicationContext情况下有效
- session:在一个http session中,一个bean对应一个实例,该作用域仅在基于web的spring ApplicationContext情况下有效
- global-session: 在一个全局的http session中,一个bean对应一个实例,该作用域仅在基于web的spring ApplicationContext情况下有效
11、spring框架中单例bean是线程安全的吗?
不是,单例bean不是线程安全的,spring框架并没有对单例bean进行多线程的封装处理
实际上,大部分的时候,spring 中的bean都是无状态的(比如dao类,不保存数据),这种bean就无所谓线程安全了,而针对有状态的bean(view,model对象),就需要开发者自己去保证线程安全了。
解决办法:
- 使用prototype,改变bean的作用域
- 使用ThredLocal,将成员变量放在ThredLocal里面,传进来的参数都是跟随线程的,因此也是线程安全的 ---- ThredLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突,属于空间换时间
- 使用同步锁 synchronized ,属于时间换空间的办法,会影响吞吐量。
12、Spring bean 的生命周期
1、加载bean的定义
通过loadBeanDefinitions扫描所有xml配置,注解等将bean 记录在 beanDefinitionMap中
2、创建bean对象
通过doCreateBean()遍历beanDefinitionMap 来创建bean
2.1 构建对象
容器通过createBeanInstance()进行对象的构造(获取构造方法,首先是加了@Autowired注解的构方法,其次是无参构造方法、、、如果存在多个构造方法spring难以识别,则会抛出异常)
准备参数(构造器的入参) 首先是根据类型进行查找,其次通过参数名查找
构造对象,对于无参对象,则直接实例化,有参数的则进行属性的填充
2.2 属性填充
通过populateBean()方法为bean内部的属性进行赋值,通常是@Autowired注解的变量
2.3 初始化bean对象
通过initializeBean()方法对填充后的实例进行初始化
第一步是初始化容器相关信息,通过invokAwareMethods()方法,为实现了aware接口(信息感知接口)的bean设置注入 beanName,beanFactory等容器相关信息
第二步是执行bean的初始化方法,通过invokeInitMethods()方法进行初始化-------如果Bean实现了InitializingBean接口,则会调用他的afterPropertiesSet()方法,同样的,如果用户自定义了init-method方法,那么也会执行
如果Bean实现了BeanPostProcessors接口【bean后置处理器】,则会在初始化方法invokeInitMethods()前后分别执行 postProcessBeforeInitialization()和postProcessAfterInitialization() 方法,此时,bean已经准备就绪,可以被应用程序使用了
如果bean实现了DisposableBean接口,spring会调用他的destory()方法。
2.4 添加到单例池
如果bean被声明为singleton,则会被加入单例池,由spring进行管理。
3. 销毁
销毁前,调用bean中@PreDestroy注解 的方法,
然后通过postProcessBeforeDestruction方法调用destoryBean逐一销毁Bean
调用 invokeCustomDestoryMethod执行客户自定义的销毁。
13. 声明是Spring的内部bean? spring inner beans?
当一个bean仅被用作另一个bean的属性时,能被声明为一个内部bean,内部bean可以用setter注入属性 和 构造方法注入构造参数的方法来实现,内部bean通常是匿名的,scope一般是prototype。
14 spring中如何注入一个集合?
<list>类型用于注入一列值,允许有相同的值。
<set> 类型用于注入一组值,不允许有相同的值。
<map> 类型用于注入一组键值对,键和值都可以为任意类型。
<props>类型用于注入一组键值对,键和值都只能为String类型。
15、什么是bean装配?什么是bean的自动装配?
bean的装配就是指在spring容器中,把bean组装到一起,前提是知道bean的依赖关系,通过依赖注入把他们装配到一起。
自动装配就是Spring容器能够自动装配相互合作的bean,通过beanFactory自动处理bean之间的协作
XML里面配置自动装配方式 autowire属性:
- no:默认的方式是不进行自动装配的,而是通过手工配置ref属性来指定要装配的bean
- byName: 通过bean的名称进行自动装配,如果一个bean的property属性和另一个bean的name(id)相同,则进行自动装配
- byType:通过参数的数据类型进行自动装配
- constructor:利用构造函数进行装配,并且构造函数的参数通过bytype进行装配
- autodetect: 自动探测,有构造方法,则通过构造方法,没有则根据byType
@Autowired注解:
启动spring的IOC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。
- 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
- 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
- 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
16、 自动装配有哪些局限性?
基本数据类型和string字符串不能自动装配
自动装配不如显式装配精确。
17 什么是基于注解的配置?
允许在少量Java注解的帮助下,进行大部分spring的配置,而不是xml文件
如,@Configuration声明配置这是一个配置类,然后在这个类中使用@Bean注解,定义bean
@Configuration
public class StudentConfig {
@Bean
public StudentBean myStudent() {
return new StudentBean();
}
}
18 怎么开启注解装配
注解装配默认是关闭的,如果想使用,则需要在spring的配置文件中配置
<!-- 声明使用注解配置 -->
<context:annotation-config/>
<!-- 声明Spring工厂注解的扫描范围 -->
<context:component-scan base-package="com.qfedu.beans"/>
- @Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。
- @Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IoC 容器中。
- @Service:此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是 @Component,因为它以更好的方式指定了意图。
- @Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException。
19 @Required 注解有什么用?
这个注解表明bean的属性必须在配置的时候进行设置值,如果未设置,则会抛出BeanInitializationException异常。
20 @Autowired注解和@Resource注解之间的区别?
都是属性注解, @Autowired还可以用作set方法上
- @Autowired 按照类型进行装配,默认情况要求依赖对象必须要存在(可以设置required属性为false)
- @Resource 按照名称进行装配,找不到名称才会按照类型装配
21 @Qualifier注解作用?
当创建多个相同类型的bean时,并希望仅仅使用属性进行装配其中一个bean时,可以使用@Qualifier 注解进行指定,通常和@Autowired注解一起使用在setter方法
@Autowired
public void setClazz(@Qualifier("c2") Clazz clazz) {
this.clazz = clazz;
}
22 spring 支持的事务管理类型?
spring事务的本质其实是对数据库事务的支持。
- 编程式事务管理,在代码中显示调用开启事务,提交事务,回滚事务的相关方法。优点是灵活性高,但是难以维护
- 声明式事务管理,底层建立在AOP基础上,本质是对方法的前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在方法执行完之后,根据执行情况提交或者回滚事务。----从业务代码里面分离出事务管理,仅仅用注解或者xml来配置管理事务
大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
23 spring 事务隔离级别
spring中有五大隔离级别,默认值是isolation_default(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致,读未提交,读已提交,可重复读,串行化。
24 spring 的事务传播行为
事务的传播行为,指的是当多个事务同事存在的时候,spring如何处理这些事务。
- PROPAGATION_REQUIRED:如果没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,是最常见的设置
- PROPAGATION_SUPPORTS:支持当前事务,如果存在事务,就加入该事务,如果当前不存在事务,则以非事务执行
- PROPAGATION_MANDATORY:强制事务,如果当前存在事务,则加入该事务,如果不存在,则抛出异常
- PROPAGATION_REQUIRES_NEW:创建新的事务,不管当前是否存在事务
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则将事务挂起
- PROPAGATION_NEVER:以非事务执行,如果当前存在事务,则抛出异常
- PROPAGATION_NESTED:嵌套事务,如果不存在事务,则创建事务,如果存在事务,则创建一个新事务,嵌套到当前事务中。
25 spring框架的事务管理有哪些优点?
为不同的事务API提供了一个不变的编程模式(JDBC/JTA等)
为编程式事务管理提供了一套简单的API
支持声明式事务管理,易于管理,对代码侵入性小
和spring各种数据访问抽象层很好的集成。
26 什么是AOP?
Aspect - Oriented Programming ,面向切面编程。是一种通过动态代理,对原有的业务逻辑进行拦截,并且在这个拦截的横切面上添加特点的处理逻辑,实现对原有业务的增强。
连接点(jointPoint): 程序中的方法
切入点(pointCut): 被spring横切的方法
通知/增强(Advice):定义切点放在切入点的前面、后面,还是哪里。。。
切点: 添加到切入点的方法(如事务的begin,commit,rollback)
切面:定义切点方法的类
织入(weaving): 就是把切面应用到目标对象,并创建新的代理对象的过程。
27 spring 中的通知都有哪些类型?
AOP中,切面的工作被称为通知,实际上是程序执行时要通过springAOP框架触发的代码段。
spring切面可以配置以下5种类型的通知:
- 前置通知 before:在目标方法调用之前调用通知功能
- 后置通知 after: 在目标方法完成之后调用通知,不会关心方法输出什么
- 返回通知 After-returning: 在目标方法成功之后调用通知
- 异常通知 After-throwing:在目标方法异常之后调用
- 环绕通知around:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义行为。
after 和 after-returning根据配置的顺序决定执行顺序
28、代理模式,动态代理?
代理模式的优点:将通用性的工作交给代理对象完成,而被代理对象只需要专注自己的核心业务即可。减少代码耦合。
动态代理,几乎可以为所有的类产生代理对象,无需声明式的创建Java代理类,在运行过程中生成虚拟的代理类,被classLoader加载。动态代理的实现方式有两种:JDK动态代理和CGLib动态代理。
28.1 JDK动态代理:
通过被代理对象实现的接口产生代理对象的:
- 创建一个类,实现InvocationHandler接口,重写invoke方法 ---method.invoke(obj,args); 这个就是通过反射执行被代理类的方法。
- 在类中定义一个Object类型变量,并提供这个变量的有参数构造器,用于把代理对象传递进来
- 定义getProxy()方法,用于创建并返回代理对象。
public class JDKDynamicProxy implements InvocationHandler {
//被代理对象
private Object obj;
public JDKDynamicProxy(Object obj) {
this.obj = obj;
}
//产生代理对象,返回代理对象
public Object getProxy(){
//1.获取被代理对象的类加载器
ClassLoader classLoader = obj.getClass().getClassLoader();
//2.获取被代理对象的类实现的接口
Class<?>[] interfaces = obj.getClass().getInterfaces();
//3.产生代理对象(通过被代理对象的类加载器及实现的接口)
//第一个参数:被代理对象的类加载器
//第二个参数:被代理对象实现的接口
//第三个参数:使用产生代理对象调用方法时,用于拦截方法执行的处理器
Object proxy = Proxy.newProxyInstance(classLoader, interfaces,this);
return proxy;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
begin();
Object returnValue = method.invoke(obj,args); //执行method方法(insert)
commit();
return returnValue;
}
public void begin(){
System.out.println("----------开启事务");
}
public void commit(){
System.out.println("----------提交事务");
}
}
测试代码
//创建被代理对象
BookDAOImpl bookDAO = new BookDAOImpl();
StudentDAOImpl studentDAO = new StudentDAOImpl();
//创建动态代理类对象,并将被代理对象传递到代理类中赋值给obj
JDKDynamicProxy jdkDynamicProxy = new JDKDynamicProxy(studentDAO);
//proxy就是产生的代理对象:产生的代理对象可以强转成被代理对象实现的接口类型
GenaralDAO proxy = (GenaralDAO)jdkDynamicProxy.getProxy();
//使用代理对象调用方法,并不会执行调用的方法,而是进入到创建代理对象时指定的InvocationHandler类种的invoke方法
//调用的方法作为一个Method参数,传递给了invoke方法
proxy.insert(student);
28.2 CGLib动态代理
JDK是通过被代理类实现InvocationHandler接口来产生代理对象的,如果一个类没有实现接口,改如何产生代理对象呢?
CGLib动态代理,是通过创建被代理类的子类来创建代理对象的,因此即使没有实现任何接口,也可以通过CGLib动态代理产生代理对象。----因此CGLib动态代理不能够产生 final类 的代理对象
- 添加CGLib依赖
- 创建CGLib代理类,实现MethodInterceptor接口,重写intercept方法
- 类中定义一个Object类型的变量,并提供这个变量的有参构造器,用于传递被代理对象
- 定义getProxy方法创建并返回代理对象(代理对象是通过创建被代理类的子类来创建的)
public class CGLibDynamicProxy implements MethodInterceptor {
private Object obj;
public CGLibDynamicProxy(Object obj) {
this.obj = obj;
}
public Object getProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
Object proxy = enhancer.create();
return proxy;
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
begin();
Object returnValue = method.invoke(obj,objects); //通过反射调用被代理类的方法
commit();
return returnValue;
}
public void begin(){
System.out.println("----------开启事务");
}
public void commit(){
System.out.println("----------提交事务");
}
}
测试
//创建被代理对象
BookDAOImpl bookDAO = new BookDAOImpl();
StudentDAOImpl studentDAO = new StudentDAOImpl();
//通过cglib动态代理类创建代理对象
CGLibDynamicProxy cgLibDynamicProxy = new CGLibDynamicProxy(bookDAO);
//代理对象实际上是被代理对象子类,因此代理对象可直接强转为被代理类类型
BookDAOImpl proxy = (BookDAOImpl) cgLibDynamicProxy.getProxy();
//使用对象调用方法,实际上并没有执行这个方法,而是执行了代理类中的intercept方法,将当前调用的方法以及方法中的参数传递到intercept方法
proxy.update();