- 1.1Spring的优点?
- 1.2Spring使用到了哪些设计模式?
- 1.3什么是AOP?
- 1.4aop有哪些实现方式?
- 1.5JDK动态搭理和CGLIB动态代理的区别?
- 1.6SpringAOP中的相关术语
- 1.7Spring通知有哪些类型?
- 1.8什么是ioc?
- 1.9ioc容器的初始化过程?
- 1.10Bean的生命周期
- 1.11BeanFactory和FactoryBean的区别?
- 1.12Bean注入容器有哪些方式?
- 1.13Bean的作用域
- 1.14Spring自动装配有哪些方式?
- 1.15@Autowired和@Resource的区别?
- 1.16@Qualifier 注解有什么作用
- 1.17@Bean和@Component有什么区别?
- 1.18Spring事务实现的方式有哪些?
- 1.19Spring如何解决循环依赖的问题?
- 1.20spring的启动过程
- 1.21Spring的单例bean是否有线程安全问题?
1.1Spring的优点?
(1)轻量,基本版本大约2MB;
(2)通过控制反转和依赖注入实现松耦合;
(3)支持面向切面编程,提高业务的可扩展性和维护性;
(4)方便集成各种优秀框架。
1.2Spring使用到了哪些设计模式?
(1)简单工厂模式:BeanFactory就是简单工厂模式的体现,根据传入一个唯一标识来获得Bean对象。
BeanFactory:延迟注入(使用到某个bean的时候才会注入),相比于ApplicationContext来说会占用更少的内存,程序启动的速度会更快;
ApplicationContext:容器启动的时候,不管你用没用到,都是一次性创建所有bean。BeanFactory仅提供了最基本的依赖注入支持,ApplicationContext扩展了BeanFactory,除了有BeanFactory的功能还有例如支持国际化的消息访问、支持aop、消息的发布订阅响应模式等;
@Override
public Object getBean(String name) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name);
}
(2)工厂方法模式:FactoryBean就是典型的工厂方法模式。Spring在调用getBean()获得该bean对象时,会自动调用该bean的getObject()方法。每个Bean都会对应一个FactoryBean,比如sqlSessionFactory对应sqlSessionFactoryBean。
(3)单例模式:在我们的应用程序中,有些对象其实我们只需要一个,比如:线程池、缓存、日志对象等;一个类仅创建一个实例对象,Spring创建Bean实例的时候默认是单例的。Spring通过ConcurrentHashMap实现单例注册表的特殊方式实现单例模式。使用单例模式的好处是:1.对于频繁使用的对象,可以节省创建对象所花费的时间;2.由于new的操作次数会减少,因此对系统内存的使用频率也会降低,减轻GC压力,缩短GC的停顿时间。
(4)适配器模式:SpringMVC中的适配器HandlerAdapter。由于会有多个Controller实现,如果需要调用Controller的方法,那么需要先判断是该由哪一个Controller处理请求,然后调用相应的方法。当增加新的Controller,需要修改原来的逻辑,违反了开闭原则(对修改关闭,对扩展开放)。所以,Spring就提供了一个适配器接口,每种Controller都会对应一种HandlerAdapter实现类,当请求过来的时候,SpringMVC会调用getHandler()获取相应的Controller,然后获取该Controller对应的HandlerAdapter,最后调用HandlerAdapter的handler()方法来处理请求,实际上就是调用Controller的handlerRequest()。每次添加新的Controller的时候,只需要增加一个适配器类就可以了,无需修改原来的逻辑。
常用的处理器适配器有:HttpRequestHandlerAdapter、AnnotationMethodHandlerAdapter等。
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {//handler是被适配的对象,这里使用的是对象的适配器模式
return (handler instanceof HttpRequestHandler);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
}
(5)代理模式:springAOP使用了动态代理,有两种方式JdkDynamicAopProxy和Cglib2AopProxy。
(6)观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用,在很多场景下都可以解耦代码。
ApplicationEvent充当事件的角色,它是一个抽象类,继承了EventObject类并实现了Serizliazable接口;
ApplicationEventPublisher充当了事件的发布者,它是一个接口,子类通过实现这个接口重写它的publishEvent()发布消息;
ApplicationListener充当事件的订阅者和消费者的角色,它是一个接口,子类通过实现这个接口重写onApplicationEvent()来实现消息的订阅和消费。
// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数
public class DemoEvent extends ApplicationEvent{
private static final long serialVersionUID = 1L;
private String message;
public DemoEvent(Object source,String message){
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{
//使用onApplicationEvent接收消息
@Override
public void onApplicationEvent(DemoEvent event) {
String msg = event.getMessage();
System.out.println("接收到的信息是:"+msg);
}
}
// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher {
@Autowired
ApplicationContext applicationContext;
public void publish(String message){
//发布事件
applicationContext.publishEvent(new DemoEvent(this, message));
}
}
(7)模板模式:spring中的jdbcTemplate、hibernateTemplate等,就使用到了模板模式。
1.3什么是AOP?
springAOP是spring框架core模块的重要功能,意为面向切面编程。通过预编译或运行期动态代理实现程序功能的统一维护的一种技术;利用aop可以对业务逻辑的各个部分进行隔离,使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。
简单来说,它可以统一解决一批组件的共性需求(如权限管理、记录日志、事务管理)等等。在AOP的思想下,将这批组件共性需求的代码独立出来,然后通过配置的方式,声明这些代码在什么地方,什么时候调用。当满足条件时,AOP会将该业务代码织入到我们指定的位置,从而统一解决了问题,又不需要修改这一批组件的代码。
打个比方,我在留言板项目中使用到了springAop的功能来实现监控service层执行时间的功能。首先创建一个ServiceLogAspect类,并加上@Aspect注解表明这是一个切面,@Component注解,把类交给spring容器来管理。然后需要使用到环绕通知。在织入通知的方法上声明@Around注解,然后需要定义这个方法的切面表达式,表示的是只要是调用了XXservice.Impl包下的任何类的任何方法(任意参数、任意返回值);然后就可以写织入通知的逻辑了,针对执行service流程耗时记录不同的日志。面向切面编程的实现也是依赖动态代理技术来实现的,比如xxxserviceImpl实现了xxxservice接口,springAop就会使用jdk动态代理技术来生成这个目标类serviceImpl的代理类,代理类也去实现xxxservice接口,重写与目标类相同的方法,然后按照约定流程来织入通知。
@Aspect
@Component
public class ServiceLogAspect {
final static Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);
//*.*表示任何类的任何方法
@Around("execution(* com.imooc.*.service.impl..*.*(..))")
public Object recordTimeOfService(ProceedingJoinPoint joinPoint)
throws Throwable {
logger.info("==== 开始执行 {}.{}====",
joinPoint.getTarget().getClass(),
joinPoint.getSignature().getName());
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
long takeTime = end - start;
if (takeTime > 3000) {
logger.error("当前执行耗时:{}", takeTime);
} else if (takeTime > 2000) {
logger.warn("当前执行耗时:{}", takeTime);
} else {
logger.info("当前执行耗时:{}", takeTime);
}
return result;
}
}
1.4aop有哪些实现方式?
AOP有两种实现方式:静态代理和动态代理。
静态代理:代理类在编译阶段生成,在编译阶段将通知织入java字节码中,也称为编译时增强。缺点是代理对象需要与目标对象实现一样的接口,并且实现接口的方法,会有冗余代码。同时,一旦接口增加方法,目标对象与代理对象都要维护。
动态代理:代理类在程序运行时创建,AOP框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强。
1.5JDK动态搭理和CGLIB动态代理的区别?
SpringAop中的动态代理主要有两种方式:JDK动态代理和CGLIB动态代理。
【JDK动态代理】:
如果目标类实现了接口,SpringAOP就会选择使用JDK动态代理目标类。代理类根据目标类实现的接口动态生成,不需要自己敲代码, 生成的动态代理类和目标类都实现相同的接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
缺点是:目标类必须有实现的接口,如果这个类没有实现接口的话,那这个类就不能用jdk动态代理。
【GLIB动态代理】:
通过继承实现。如果目标类没有实现接口,那么SpringAOP就会选择使用CGLIB来动态代理目标类。CGLIB可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类。但是在springboot中默认使用的是cglib动态代理。
CGLIB是通过继承的方式来实现的动态代理,因此如果某个类被final关键字修饰了,那么它是无法使用CGLIB做动态代理的。另外不受容器管理的对象也是不能增强的。
优点:目标类不需要实现特定的接口,更加灵活。
如果目标类实现了接口,默认情况下会采用jdk的动态代理实现aop,当然也可以强制使用CGLIB的方式来实现aop,如果目标对象没有实现接口那必须采用CGLIB库。
两者的区别:
jdk动态代理使用jdk中的类Proxy来创建代理对象,它使用反射技术来实现,不需要导入其他依赖。cglib需要引入相关依赖:asm.jar,它使用字节码增强技术来实现。在相同的情况下invokeSuper+setSuperClass永远比invoke+setInterfaces慢,因为invokeSuper+setSuperClass方式生成的FastClass类更大,如果方法过多的话switch case也更多。
1.6SpringAOP中的相关术语
(1)连接点(joint point):对应的是具体被拦截的对象,因此spring只能支持方法,所以被拦截的对象往往就是指特定的方法,joint point是所有可能被织入Advice的候选的方法。
(2)切点(point cut):我们知道所有的方法(joint point)都可以织入Advice,但是我们并不希望在所有方法上都织入Advice,而pointcut的作用就是提供一组规则来匹配joint point,给满足规则的joint point添加Advice。
(3)通知(Advice):就是按照约定的流程下的方法,分为前置通知、后置通知、环绕通知、事后返回通知和异常通知,它会根据约定织入流程中。
(4)织入(weaving):它是通过动态代理技术,为原有服务对象生成动态代理对象,然后将与切点匹配的连接点拦截,并按约定将各类通知织入约定流程。
(5)切面(aspect):就是point cut和Advice的组合。
1.7Spring通知有哪些类型?
(1)前置通知(@Before):在目标方法被调用之前调用执行功能;
(2)后置通知(@After):在目标方法调用完成之后执行通知;
(3)返回通知(@AfterReturning):在方法正常执行,正常返回后执行通知;
后置通知与返回通知的区别:后置通知应用时机是在返回通知以后,在任何情况下都会执行;而返回通知只有方法正常执行正常返回后才执行;
(4)异常通知(@AfterThrowing):目标方法抛出异常之后,执行通知;
(5)环绕通知(@Around):方法调用之前和调用之后执行。
环绕通知有目标方法的执行权,能够控制目标方法是否执行。而其他的通知更多是对目标方法的一个增强,无法影响目标方法的执行。
1.8什么是ioc?
ioc:控制反转,由spring容器管理bean的整个生命周期。通过反射实现对其他对象的控制,包括初始化、创建、销毁等,解放手动创建对象的过程,同时降低类之间的耦合度。在需要其他依赖的对象的时候,只需要向容器进行申请。在传统的javase中, 应用程序是老大,要获取什么资源都是主动出击,但是在ioc/di的思想下,应用程序就变成被动的了,被动的等待ioc容器来创建并注入它所需要的资源了。
所有对象的创建与销毁都交由spring容器来管理,也就是说控制对象生命周期的不再是它的引用,而是spring。对于某个具体的对象而言,以前控制它的是其他对象,或者它控制了其他对象,现在是所有的对象都交由spring来控制,这就是控制反转。ioc容器实际上就是一个concurrentHashMap,key是bean对象的name,value是bean对象本身。
控制:对象创建(实例化、管理)的权力;
反转:控制权交给外部环境(ioc容器);
【依赖注入】
依赖:bean对象的创建依赖于容器;
注入:bean对象中的所有属性中依赖的外部资源由容器来注入。
ioc的一个重点就是在系统运行时,动态的向某个对象提供它所需要的另一个对象,这一点是通过di来实现的。打个比方,对象A在操作数据库时,需要先获取连接,之前我们总是在A中自己编写一个代码来获取Connection对象。有了spring以后,我们只需要告诉spring,A需要一个Connection对象,至于这个Connection何时构造,如何构造,A都不需要知道。在系统运行时,spring会在合适的时候制造一个Connection对象,就好像在打针一样注入到A对象中, 所以依赖注入的名字就是这么来的。di的实现方式是通过反射,反射的强大功能允许系统在运行时,动态的创建一个对象,执行这个对象的方法,获取或修改这个对象的属性,spring就是通过反射来注入的。
1.构造器注入
被注入对象在它的构造方法中声明它依赖对象的参数列表,让外部知道它需要哪些依赖对象。然后,iocServiceProvider会检查被注入对象构造方法中依赖参数列表,进而为其注入相应的对象。构造方法注入方式比较直接,对象被构造完成后,就进入了就绪状态,可以马上使用。
2.setter方法注入
通过setter方法,可以改变相应的对象属性。所以,当前对象只需要为所依赖的对象添加setter方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。setter方法注入虽然不像构造方法注入那种,让对象构造完成后即可使用,但相对来说更宽松一些,可以在对象构造完成后注入。
1.9ioc容器的初始化过程?
(1)从XML中读取配置文件;
(2)将bean标签解析成BeanDefinition;
(3)将BeanDefinition注册到容器BeanDefinitionMap中;
(4)BeanFactory根据BeanDefinition的定义信息创建和初始化bean。
(5)依赖注入
单例bean的初始化以及依赖注入一般都是在容器初始化阶段进行,只有懒加载的单例bean是在应用第一次调用getBean()的时候进行初始化和依赖注入的。
1.10Bean的生命周期
(1)对Bean进行实例化;
(2)依赖注入;
(3)如果Bean实现了BeanNameAware接口,spring将调用setBeanName(),设置Bean的id(xml文件中bean标签的id);
(4)如果Bean实现了BeanFactoryAware接口,spring将调用setBeanFactory();
(5)如果Bean实现了ApplicationContextAware接口,spring将调用setApplicationContext();
(6)如果存在BeanPostProcessor,spring将调用它们的postProcessBeforeInitialization(预初始化方法),在Bean初始化前对其进行处理;
(7)如果Bean实现了InitalizingBean接口,spring将调用它的afterPropertiesSet方法,然后调用xml定义的init-method方法,这两个方法的作用类似,都是在初始化Bean的时候执行。
(8)如果存在BeanPostProcessor,spring将调用它们的postProcessAfterInitialization(后初始化方法),在Bean初始化后对其进行处理;
(9)Bean初始化完成,供应用使用,直到应用被销毁;
(10)如果Bean实现了DispoableBean接口,spring将调用它的destory方法,然后调用在xml中定义的destory-method方法,都是在Bean实例销毁前执行。
1.11BeanFactory和FactoryBean的区别?
BeanFactory:BeanFactory 是顶层接口,提供了IOC容器最基本的形式,给具体的IOC容器的实现提供了规范。
FactoryBean:它也是一个接口,是一个bean组件。通常是用来创建比较复杂的bean,一般的bean直接用xml配置即可,但如果一个Bean的创建过程中涉及到很多其他的bean和复杂的逻辑,直接用xml配置比较麻烦,这时可以考虑用FactoryBean,可以隐藏实例化Bean的复杂细节。
当读取配置文件中bean标签的class属性配置的实现类是FactoryBean时,通过getBean()方法返回的不是FactoryBean本身,而是调用FactoryBean的getObject()方法所返回的对象,相当于是FactoryBean的getObject()代理了getBean()方法。
Mybatis提供了SqlSessionFactoryBean,可以简化SqlSessionFactory的配置,spring将会在应用启动时创建sqlSessionFactory。
1.12Bean注入容器有哪些方式?
将普通的类交给Spring容器来管理,通常有以下方式:
(1)使用@Configuration与@Bean注解;
(2)使用@Controller、@Service、@Repository、@Component 注解标注该类,然后启用@ComponentScan自动扫描
(3)使用@Import注解把bean导入到当前容器中。
//@SpringBootApplication
@ComponentScan
/*把用到的资源导入到当前容器中*/
@Import({Dog.class, Cat.class})
public class App {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
System.out.println(context.getBean(Dog.class));
System.out.println(context.getBean(Cat.class));
context.close();
}
}
1.13Bean的作用域
(1)singleton:单例,spring中的bean默认都是单例的;
(2)prototype:每次请求都会创建一个新的bean实例;
(3)request:每一次http请求都会产生一个新的bean,该bean仅在当前http request内有效;
(4)session:每一次http请求都会产生一个新的bean,该bean仅在当前http session内有效;
(5)global-session:全局session作用域;
1.14Spring自动装配有哪些方式?
Spring的自动装配有三种方式:ByType(根据类型)、ByName(根据名称)、Construct(根据构造函数)
(1)ByType
找到与依赖类型相同的bean注入到另外的bean中,这个过程需要借助setter注入来完成,因此必须存在set方法,否则注入失败。当xml文件中存在多个相同类型名称不同的实例Bean时,Spring容器依赖注入依然会失败,因为存在多种适合的选项,Spring容器无法知道该注入哪种。此时我们需要为Spring容器指定注入哪个Bean实例,可以通过
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<!-- autowire-candidate="false" 过滤该类型 -->
<bean id="userDao2" autowire-candidate="false" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<!-- byType 根据类型自动装配userDao-->
<bean id="userService" autowire="byType" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
(2)ByName
将属性名与bean进行匹配,如果找到则注入依赖bean;
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<!-- byName 根据名称自动装配,找到UserServiceImpl名为 userDao属性并注入-->
<bean id="userService" autowire="byName" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
(3)Constructor
存在的按个实例则优先按类型进行参数匹配,当存在多个类型相同的实例时,按名称优先分配,如果没有找到对应名称,则注入失败,此时可以通过autowire-candidate=”false” 过滤来解决。
<!--只存在userDao2,userDao3 无法成功注入-->
<bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<bean id="userDao3" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<bean id="userService" autowire="constructor" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
@Autowired 可以传递了一个required=false的属性,false指明当userDao实例存在就注入,不存在就忽略,如果为true,就必须注入,若userDao实例不存在,就抛出异常。由于默认情况下@Autowired是按类型匹配的(byType),如果需要按名称(byName)匹配的话,可以使用@Qualifier注解与@Autowired结合。
public class UserServiceImpl implements UserService {
//标注成员变量
@Autowired
@Qualifier("userDao1")
private UserDao userDao;
}
上述两种自动装配的依赖注入并不适合简单值类型,如int、boolean、long、String以及Enum等,对于这些类型,Spring容器也提供了@Value注入的方式。@Value接收一个String的值,该值指定了将要被注入到内置的java类型属性值,Spring 容器会做好类型转换。一般情况下@Value会与properties文件结合使用。
1.15@Autowired和@Resource的区别?
@Autowired注解是按照类型(byType)装配依赖对象的,但是存在多个类型⼀致的bean,⽆法通过byType注⼊时,就会再使⽤byName来注⼊,如果还是⽆法判断注⼊哪个bean则会抛出异常。
@Resource会⾸先按照byName来装配,如果找不到bean,会⾃动byType再找⼀次。@Resource有两个中重要的属性:name和type。Spring容器对于@Resource注解的name属性解析为bean的名字,type属性则解析为bean的类型。因此使用name属性,则按byName模式的自动注入策略,如果使用type属性则按 byType模式自动注入策略。倘若既不指定name也不指定type属性,Spring容器将通过反射技术默认按byName模式注入。
@Resource(name=“userDao”)
private UserDao userDao;//用于成员变量
1.16@Qualifier 注解有什么作用
当创建多个类型相同的bean并希望使用属性装配其中一个bean时,可以使用@Qualifier 注解和 @Autowired 通过指定应该装配哪个 bean 来消除歧义。
1.17@Bean和@Component有什么区别?
这俩都是使用注解来定义Bean。@Bean 是使用 Java 代码装配 Bean,@Component 是自动装配 Bean。
@Component注解用在类上,表明这个类是一个组件类,需要spring为这个类创建bean实例,每个类对应一个bean。
@Bean注解作用在方法上,表示这个方法会返回一个Bean。@Bean 需要在配置类中使用,即类上需要加上@Configuration注解。
@Bean 注解更加灵活。当需要将第三方类装配到 Spring 容器中,因为没办法源代码上添加@Component注解,只能使用@Bean 注解的方式,当然也可以使用 xml 的方式。
@Component
public class Student {
private String name = "lkm";
public String getName() {
return name;
}
}
@Configuration
public class WebSocketConfig {
@Bean
public Student student(){
return new Student();
}
}
@Component:最普通的组件,可以被注入到spring容器进行管理。
@Controller:将类标记为 Spring Web MVC 控制器。
@Service:将类标记为业务层组件。
@Repository:将类标记为数据库访问组件,即DAO组件
1.18Spring事务实现的方式有哪些?
事务就是一系列操作原子执行,Spring事务机制主要包括声明式事务和编程式事务。
(1)编程式事务:通过编程的方式管理事务,这种方式带来了很大的灵活性,但很难维护。
(2)声明式事务:将事务管理代码从业务方法中分离出来,通过aop进行封装。Spring声明式事务使得我们不需要去处理获得连接、关闭连接、事务提交和回滚这些操作。使用@Transaction注解开启声明式事务,如果@Transaction注解作用在类上,这个类的所有的公共非静态方法都将开启事务功能,如果作用在方法上,代表这个方法将启用事务功能,后面可以跟上事务的隔离级别,以及传播行为的参数。
事务的传播行为就是:一个事务方法被另一个事务方法调用时,这个事务方法应该如何执行。打个比方:methodA调用methodB方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务,这就是由methodB的事务传播行为决定的。Spring在TransactionDefinition接口中定义了7种事务传播行为,其中required和requires-new是使用的最多的。
(1)propagation_required:如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。这是最常见的Spring的默认的事务传播行为。
(2)propagation_requires_new:创建新事务,无论当前是否存在事务,都要创建新事务。
(3)propagation_supports:支持当前事务,如果当前存在事务就加入该事务;如果当前不存在事务,就以非事务执行;
(4)propagation_mandatory:支持当前事务,如果当前存在事务就加入该事务;如果当前不存在事务,就抛出异常;
(5)propagation_not_support:以非事务的方式执行,如果当前存在事务,就把当前事务挂起;
(6)propagation_never:以非事务到的方式执行,如果当前存在事务,则抛出事务;
(7)propagation_nested:如果当前存在事务,就嵌套在事务内执行;如果当前没有事务,则按required的方式执行。
propagation_nested和requires_new传播行为的区别是:
propagation_nested嵌套事务表示的是被调用的方法是一个已存在事务的嵌套子事务,当嵌套事务开始执行的时候,会获得一个savapoint,如果这个嵌套事务执行失败,会回滚到这个savapoint。嵌套事务是外部事务的一部分,只有外部事务结束后才能被提交。外层事务的回滚可以引起内层事务的回滚。
requires_new表示的是外层事务和内层事务是两个独立的事务。一旦内层事务进行了提交后,外层事务不能对其进行回滚。这两个事务互不影响。 `
1.19Spring如何解决循环依赖的问题?
循环依赖问题:在A中注入了B,在B中注入了A,那么Spring在创建A的时候会出现这种现象;在创建A实例后,在依赖注入时需要B,然后就去创建B,这时候发现又需要依赖注入A,这样就导致了循环依赖。
构造器注入的循环依赖:spring处理不了,直接抛异常。
非单例的循环依赖:无法处理
Spring创建的过程可以简单概括为实例化->依赖注入->初始化。而Spring解决循环依赖的方法就是在实例化之后,依赖注入之前,将实例化的对象放到缓存中进行提前曝光,后面的对象则在实例化前,先到缓存中查找有无对应的实例化对象即可。
1.20spring的启动过程
(1)读取web.xml文件;
(2)创建ServletContext,为ioc容器提供宿主环境;
(3)触发容器初始化事件,在这个方法会初始化一个应用上下文WebApplicationContext,即spring的ioc容器。ioc容器在初始化完成以后会被存储到ServletContext中。
(4)初始化web.xml配置中的Servlet,比如dispatcherServlet,用于匹配、处理每个servlet请求。
1.21Spring的单例bean是否有线程安全问题?
当多个用户同时请求一个服务时,容器会给每个请求分配一个线程去处理,这时多个线程会并发执行该请求对应的业务逻辑,如果业务逻辑有对单例状态的修改(体现为这个单例的成员属性),则必须考虑线程安全问题。如果每个线程对全局变量、静态变量只有读操作,没有写操作,那就不会有线程安全问题;如果有多个线程同时执行写操作,一般都需要考虑线程同步,否则就会有线程安全问题。
换句话来说,spring本身并不管你的Bean是否是线程安全的,如果你的某个类的对象new出来本来就是线程安全的,那么这个类的对象交给Spring管理也是线程安全的;如果它直接new出来不是线程安全的,spring也不能把他变成线程安全的。
无状态bean和有状态bean
无状态的bean:线程中的操作不会对bean的成员执行查询以外的操作,那么这个单例bean就是线程安全的,比如Controller、service、dao等,这些bean大多数都是无状态的,只关注方法本身。
有状态的bean:有实例变量的对象,可以保存数据,是否线程安全的。每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即有状态;一旦用户调用结束,bean的生命周期也结束。
在Spring中无状态的bean适合用单例模式,这样可以共享实例提高性能。有状态的bean在多线程环境下不安全,一般使用ThreadLocal来解决安全问题。