1、Spring是什么
Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。
2、Spring有什么优点
- 1、轻量级(相对应EJB来说)
- 2、使用Spring的IOC容器,将对象之间的依赖关系交给Spring,降低组件之间的耦合性,让我们更专注于应用逻辑
- 3、AOP的很好支持,方便面向切面编程。
- 4、Spring属于低侵入,代码污染极低。
- 5、作为粘合剂,可以很好地与其他框架结合
3、Spring是如何解决循环依赖的
(1)构造器循环依赖:当循环依赖的bean都是通过构造器注入依赖的时候,无论这些bean是singleton还是prototype,在获取bean的时候都会失败。那么Spring是如何做的呢?如下举例分析:
- Spring容器创建“circleA” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleB”,并将“circleA” 标识符放到“当前创建Bean池”;
- Spring容器创建“circleB” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleC”,并将“circleB” 标识符放到“当前创建Bean池”;
- Spring容器创建“circleC” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleA”,并将“circleC” 标识符放到“当前创建Bean池”;
- 到此为止Spring容器要去创建“circleA”Bean,发现该Bean 标识符在“当前创建Bean池”中,因为表示循环依赖,抛出BeanCurrentlyInCreationException。
(2)setter属性循环依赖:当循环依赖的bean都是通过属性注入依赖的时候,根据bean的作用域是singleton还是prototpye,会有不同的表现。情况如下:
- 如果循环依赖的bean都是singleton,那么无论先获取哪个bean,都能成功。
- 如果循环依赖的bean都是prototype,那么无论先获取哪个bean,都会失败。
- 如果循环依赖的bean中有singleton,也有prototype,那么当先获取的那个bean是singleton时,就会成功,否则失败。
那么Spring是怎么做的呢?具体分析步骤如下:
- Spring容器创建单例“circleA” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“circleA” 标识符放到“当前创建Bean池”;然后进行setter注入“circleB”;
- Spring容器创建单例“circleB” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的Bean,并将“circleB” 标识符放到“当前创建Bean池”,然后进行setter注入“circleC”;
- Spring容器创建单例“circleC” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“circleC” 标识符放到“当前创建Bean池”,然后进行setter注入“circleA”;进行注入“circleA”时由于提前暴露了“ObjectFactory”工厂从而使用它返回提前暴露一个创建中的Bean;
- 最后在依赖注入“circleB”和“circleA”,完成setter注入。
4、IOC和DI的区别
IoC 控制反转,指将对象的创建权,反转到Spring容器 , DI 依赖注入,指Spring创建对象的过程中,将对象依赖属性通过配置进行注入。
5、BeanFactory 接口和 ApplicationContext 接口有什么区别
(1)BeanFacotry是spring中比较原始的Factory。原始的BeanFactory无法支持spring的许多插件,如AOP功能、Web应用等。 ApplicationContext接口,它继承了BeanFactory接口,因而提供BeanFactory所有的功能。ApplicationContext以一种更向面向框架的方式工作以及对上下文进行分层和实现继承,ApplicationContext包还提供了以下的功能:
• MessageSource, 提供国际化的消息访问
• 资源访问,如URL和文件
• 事件传播
• 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
(2)BeanFactory采取延迟加载,第一次getBean时才会初始化Bean, ApplicationContext是会在加载配置文件时初始化Bean。
6、Spring中Bean的作用域
- singleton:当一个bean的作用域为singleton, 那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
- prototype:prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean() 方法)时都会创建一个新的bean实例。根据经验,对所有有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用 singleton作用域
- request:在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用 域仅在基于web的Spring ApplicationContext情形下有效。
- session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
- global session:在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于 web的Spring ApplicationContext情形下有效。
7、Spring注入bean的三种方式
(1).构造器注入,通过 <constructor-arg> 元素完成注入。
<bean id="user" class="org.sang.User4">
<constructor-arg name="username" value="张三"/>
</bean>
(2).setter方法注入, 通过<property> 元素完成注入
<bean id="user" class="org.sang.User5">
<property name="username" value="lisi"/>
</bean>
(3).通过注解方式:@Autowired、@Resource、@Qualifier
8、@Autowired 与@Resource的区别
(1).@Autowired默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用。例如
@Autowired
@Qualifier("baseDao")
privateBaseDao baseDao;
(2)@Resource可以按照名字和类型进行匹配,主要有如下几种情况:
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
- 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
- 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
- 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配
(3)@Autowired是Spring的注解,而@Resource是属于Java的注解
9、简述SpringIOC容器的启动过程
- new ClassPathXmlApplicationContext调用构造器,然后调用refresh()方法
- 调用prepareRefresh()方法,做一些创建容器前的准备,记录容器启动时间,将容器active值设置为true,将close的值设置为false。
- 调用obtainFreshBeanFactory(),创建BeanFactory然后返回BeanFactory。
- 在这个方法中调用了refreshBeanFactory(),如果 ApplicationContext 中已经加载过 BeanFactory 了,销毁所有 Bean,关闭 BeanFactory,重新创建一个新的BeanFactory。
- 创建之后,设置 BeanFactory 的两个配置属性:是否允许 Bean 覆盖(默认两个配置文件是可以进行配置覆盖的)、是否允许循环引用(最好不要允许)
- 通过读取配置文件,将配置文件终于转换为一颗 DOM 树
- 然后开始解析DOM树,解析各种标签,最重要就是解析<bean>标签,解析完成之后会得到一个BeanDefinitionHolder的实例,这个实例包括beanDefinition、beanName和bean的别名
- 然后进行bean注册,将所有的beanDefinition放到一个Map中,key为beanName,value为beanDefinition。这是bean还没有初始化完成,只是将配置信息全部提取出来了
- 调用prepareBeanFactory()方法,准备bean容器,例如设置类加载器。初始化几个特殊的bean
- 初始化非抽象、单例、非懒加载的bean。通过反射创建bean实例
10、SpringAOP的应用场景
(1)用户鉴权 (2)日志打印 (3)服务监控,例如每个接口调用时间。(除此之外还有许多,这是我见过的)
11、Spring事务的传播行为
传送门:Spring事务的传播行为
12、Spring中用到了哪些设计模式
(1)工厂模式
通常由应用程序直接使用new创建新的对象,为了将对象的创建和使用相分离,采用工厂模式,即应用程序将对象的创建及初始化职责交给工厂对象。一般情况下,应用程序有自己的工厂对象来创建bean.如果将应用程序自己的工厂对象交给Spring管理,那么Spring管理的就不是普通的bean,而是工厂Bean。举个栗子
import java.util.Random;
public class StaticFactoryBean {
public static Integer createRandom() {
return new Integer(new Random().nextInt());
}
}
建一个config.xm配置文件,将其纳入Spring容器来管理,需要通过factory-method指定静态方法名称
<bean id="random"class="example.chapter3.StaticFactoryBean"
factory-method="createRandom" scope="prototype"/>
(2)单例模式(Singleton)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是是任意的java对象。
核心提示点:Spring下默认的bean均为singleton,可以通过singleton=“true|false” 或者 scope=“?”来指定
(3)适配器模式
在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。对类进行方法级别的切面增强,即生成被代理类的代理类, 并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。
Adapter类接口:Target
public interface AdvisorAdapter {
boolean supportsAdvice(Advice advice);
MethodInterceptor getInterceptor(Advisor advisor);
}
MethodBeforeAdviceAdapter类,Adapter
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}
(4)装饰器模式(Decorator)
在我们的项目中遇到这样一个问题:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往在spring和hibernate框架中总是配置一个数据源,因而sessionFactory的dataSource属性总是指向这个数据源并且恒定不变,所有DAO在使用sessionFactory的时候都是通过这个数据源访问数据库。但是现在,由于项目的需要,我们的DAO在访问sessionFactory的时候都不得不在多个数据源中不断切换,问题就出现了:如何让sessionFactory在执行数据持久化的时候,根据客户的需求能够动态切换不同的数据源?我们能不能在spring的框架下通过少量修改得到解决?是否有什么设计模式可以利用呢?
首先想到在spring的applicationContext中配置所有的dataSource。这些dataSource可能是各种不同类型的,比如不同的数据库:Oracle、SQL Server、MySQL等,也可能是不同的数据源:比如apache 提供的org.apache.commons.dbcp.BasicDataSource、spring提供的org.springframework.jndi.JndiObjectFactoryBean等。然后sessionFactory根据客户的每次请求,将dataSource属性设置成不同的数据源,以到达切换数据源的目的。
spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。基本上都是动态地给一个对象添加一些额外的职责。
(5)代理模式(Proxy)
为其他对象提供一种代理以控制对这个对象的访问。从结构上来看和Decorator模式类似,但Proxy是控制,更像是一种对功能的限制,而Decorator是增加职责。spring的Proxy模式在aop中有体现,比如JdkDynamicAopProxy和Cglib2AopProxy。
(6)观察者模式(Observer)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
spring中Observer模式常用的地方是listener的实现。如ApplicationListener。
(7)策略模式
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
spring中在实例化对象的时候用到Strategy模式
在SimpleInstantiationStrategy中有如下代码说明了策略模式的使用情况:
(8)模板方法(Template Method)
JdbcTemplate中的execute方法 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
Template Method模式一般是需要继承的。这里想要探讨另一种对Template Method的理解。spring中的JdbcTemplate,在用这个类时并不想去继承这个类,因为这个类的方法太多,但是我们还是想用到JdbcTemplate已有的稳定的、公用的数据库连接,那么我们怎么办呢?我们可以把变化的东西抽出来作为一个参数传入JdbcTemplate的方法中。但是变化的东西是一段代码,而且这段代码会用到JdbcTemplate中的变量。怎么办?那我们就用回调对象吧。在这个回调对象中定义一个操纵JdbcTemplate中变量的方法,我们去实现这个方法,就把变化的东西集中到这里了。然后我们再传入这个回调对象到JdbcTemplate,从而完成了调用。这可能是Template Method不需要继承的另一种实现方式吧。