Spring是什么?
spring 可以看成一个大的集合,他由许多开源框架和组件组成,是一个统称。而我们平常学习的是springframework,springframework 是sping 里面的一个开源框架。
springframework主要由以下几个模块组成:
Spring Core:核心类库,提供IOC服务,Spring AOP:AOP服务;Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);数据访问(jdbc,对现有的orm框架支持),Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;Spring MVC:提供面向Web应用的Model-View-Controller实现。
Spring 的优点?
(1)spring属于低侵入式设计,代码的污染极低;
(2)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
(3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
(4)spring对于主流的应用框架提供了集成支持。
Spring的AOP理解
传统的OOP开发的代码逻辑是自上而下,而这自上而下的过程中可能会产生横切性的问题,比如事务管理,日志等,这些是与主逻辑代码关联不是特别大,如果将这些代码放到主逻辑中,会时代吗臃肿和冗余,不利于维护,AOP编程就可以将这些横切性的问题和主业务之间进行分离,从而启动解耦的目的。
AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
①JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
②如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
(3)静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
BeanFactory和ApplicationContext有什么区别?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
(1)BeanFactory:
是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
①继承MessageSource,因此支持国际化。
②统一的资源文件访问方式。
③提供在监听器中注册bean的事件。
④同时加载多个配置文件。
⑤载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
(2)
①BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
③相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
(3)
BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
(4)
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
6、请解释Spring Bean的生命周期?
实例化和初始化的区别:实例化是在jvm的堆中创建了这个对象实例,此时它只是一个空的对象,所有的属性为null。而初始化的过程就是讲对象依赖的一些属性进行赋值之后,调用某些方法来开启一些默认加载。比如spring中配置的数据库属性Bean,在初始化的时候就会将这些属性填充,比如driver、jdbcurl等,然后初始化连接
(1)实例化Bean
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
(2)设置对象属性(依赖注入)
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。
(3)处理Aware接口
接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;
②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
③如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
(4)BeanPostProcessor
如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。
(5)InitializingBean 与 init-method:
如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。如果Bean实现了InitializingBean接口并且调用afterPropertiesSet()方法。
(6)BeanPostProcessor:
如果想对Bean进行一些实例化后的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessAfterInitialization(Object obj, String s)方法。
(7)DisposableBean
当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;
(8)destroy-method
最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
对于postProcessBeforeInitialization和postProcessAfterInitialization方法的调用存在一些自己不了解的问题
1、xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="test" class="com.zy.Test"></bean> </beans>
2、Test类实现BeanPostProcessor接口
public class Test implements BeanPostProcessor{ public String a; public String getA() { return a; } public void setA(String a) { this.a = a; } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println(((Test1) bean)); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { //传入的却是Test1这个对象? System.out.println(((Test1) bean)); return bean; } }
3、测试发现并没有调用这两个方法。
public class Demo { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); applicationContext.getBean("test"); } }
4、继续测试
添加类(注意这个类不要实现)
package com.zy; public class Test1{ }
修改xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="test" class="com.zy.Test"></bean> <bean class="com.zy.Test1"></bean> </beans>
测试方法不变,发现postProcessAfterInitialization()方法传入的bean竟然是Test1
5、继续测试
将Test1类实现方法BeanPostProcessor,测试发现 postProcessAfterInitialization()方法有没有被调用.......
6、继续测试,将Test修改为原型就可以触发BeanPostProcessor接口方法了
7、spring事件机制(订阅发布模式 == 观察者模式)
ApplicationContext事件机制是观察者设计模式的 实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件处理。 如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。
果我们的监听器继承ApplicationListener<ApplicationEvent>;相当于我们这个监听器监听的是Spring里面所有的消息
两个重要成员
ApplicationEvent:容器事件,必须由ApplicationContext发布;
ApplicationListener:监听器,可由容器中的任何监听器Bean担任。
1. 定义容器事件
package com.cxg.test.springPlatfrom; import org.springframework.context.ApplicationEvent; public class EmailEvent extends ApplicationEvent{ private static final long serialVersionUID = 1L; //属性 private String address; private String text; //构造方法 public EmailEvent(Object source) { super(source); } public EmailEvent(Object source, String address, String text) { super(source); this.address = address; this.text = text; } //getter和setter设置 public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getText() { return text; } public void setText(String text) { this.text = text; } }
2. 定义监听器
package com.cxg.test.springPlatfrom; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; /** * Title: email之监听类 * 容器事件的监听器类必须实现ApplicationListener接口,实现该接口就必须实现 */ public class EmailNotifier implements ApplicationListener<ApplicationEvent>{ //可以直接写成ApplicationListener<EmailEvent>:表示只监听EmailEvent事件 @Override public void onApplicationEvent(ApplicationEvent event) { if(event instanceof EmailEvent){ EmailEvent emailEvent = (EmailEvent) event; System.out.println("email's address:"+emailEvent.getAddress()); System.out.println("email's text:"+emailEvent.getText()); } else { System.out.println("the Spring's event:"+event); } } }
3. 将监听器注入到spring容器
<bean class="com.cxg.test.springPlatfrom.EmailNotifier" />
4. 测试
package com.cxg.test.springPlatfrom; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTest { public static void main(String arg[]){ //读取Spring容器的配置文件 @SuppressWarnings("resource") ApplicationContext applicationContext=new ClassPathXmlApplicationContext("application.xml"); //创建一个事件对象 EmailEvent emailEvent = new EmailEvent("hello Spring!", "cxg@126.com", "This is SpringApplicatoinContext test!"); //主动触发事件监视机制 applicationContext.publishEvent(emailEvent); } }
应用,监听spring启动完成之后的事件
@Component //applicaiton.xml注意扫描这个包 public class InitAdminListener implements ApplicationListener<ContextRefreshedEvent> { @Autowired private LogininfoService logininfoService; /** * 这个方法就是监听到指定的事件之后,要做的事情; ApplicationEvent就是这次监听到的事件对象 */ @Override public void onApplicationEvent(ContextRefreshedEvent event) { logininfoService.initAdmin(); } }
8、 解释Spring支持的几种bean的作用域。
(1)singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。
(2)prototype:为每一个bean请求提供一个实例。
(3)request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
(4)session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
(5)global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。
9、Spring框架中的单例Beans是线程安全的么?
Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。
10、Spring如何处理线程并发问题?
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
public class Test { static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>(); public static void main(String[] args) { //当前线程给ThreadLocal设置值,只能当前线程可以访问 stringThreadLocal.set("1"); System.out.println(stringThreadLocal.get()); new Thread(()->{ System.out.println(stringThreadLocal.get()); }).start(); } }
11、Spring基于xml注入bean的几种方式:
(1)Set方法注入;
(2)构造器注入:①通过index设置参数的位置;②通过type设置参数类型;
(3)静态工厂注入;
(4)实例工厂;
详细:https://blog.csdn.net/a745233700/article/details/89307518
https://blog.csdn.net/lyt_7cs1dn9/article/details/65631479
12、spring注解有哪些?
参考:https://blog.csdn.net/weixin_39805338/article/details/80770472
13、Spring事务的实现方式和实现原理:
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。