Spring学习
1. Spring的几个概念
- 非侵入式框架
- 侵入式:对于EJB,Structs这样的框架,要实现特定的接口,继承特定的类才能增强功能,改变了类的结构
- 非侵入式,对于Hibernate,Spring等,通过AOP等技术,对现有类无影响,增强JavaBean功能
- 松耦合框架
面向接口编程,通过实现对应的接口来实现,例如DAO层和Service层通过DaoFactory实现松耦合 - AOP
面向切面编程,在执行某些代码前,执行另外的代码 - DI(同IOC)
依赖注入,将对象的创建的权力及对象的生命周期的管理过程交由Spring框架来处理 - IOC(降耦合,资源集中管理)
控制反转,对象的创建交给外部容器(IOC容器)完成,使得实现对象不用再程序中写死,创建对象时,只需要为IOC容器提供配置对象的信息就行 - 模块
- Spring Core核心功能,提供IOC容器,解决对象创建以及依赖关系
- Spring Web 对Web的支持
- Spring DAO 对JDBC的支持
- Spring ORM 对ORM框架的支持(HIbernate,mybatis,SpringData等)
- Spring AOP 面向切面编程,增强JavaBean的功能
- SpringEE 对其它javaEE模块的支持
2. 依赖注入
因为对象的属性上有其它对象的变量,因此对象之间的依赖关系,实际就是给对象的属性赋值。
Spring给属性赋值的方式:
- 构造函数
- set方法注入
- p名称空间
对set方法的一种优化
applicationContext.xml配置文件<bean id="userDao" class="UserDao"/> <bean id="userService" class="UserService" p:userDao-ref="userDao"/>
- 自动装配
- 根据名字装配
<bean id="userDao" class="UserDao"/> <!--1.通过名字来自动装配 2.发现userService中有个叫userDao的属性 3.看看IOC容器中没有叫userDao的对象 4.如果有,就装配进去--> <bean id="userService" class="UserService" autowire="byName"/>
- 根据类型装配
<bean id="userDao" class="UserDao"/> <!--1.通过名字来自动装配 2.发现userService中有个叫userDao的属性 3.看看IOC容器UserDao类型的对象 4.如果有,就装配进去--> <bean id="userService" class="UserService" autowire="byType"/>
- 注解
@Autowired
使用注解完成自动装配- 在构造器上修饰
- 在setter上修饰
- 使用@Inject,同Autowired
3. IOC
控制反转,实现IOC的两种方式:依赖查找,依赖注入(更加适用)
原理:
- 通过Java反射技术获取类的所有信息
- 通过配置文件(xml,@Configuration)或注解描述类之间的关系
- 通过这些配置信息和反射技术构建对象间的依赖关系
Spring IOC容器实现对象和依赖的方式
- 根据Bean配置信息在容器内创建Bean的定义注册表
- 根据注册表加载,实例化Bean,建立Bean与Bean的依赖关系
- 准备就绪的Bean放入Map缓冲池中,等待程序调用
Spring容器:
- BeanFactory
基础的Bean工厂,面向Spring的 - ApplicationContext
在BeanFactory基础上,面向Spring框架的开发者,大多数场合都会使用ApplicationContext
区别:
- ApplicationContext利用反射识别配置文件中定义的BeanPostProcessor,InstantiationAwareBeanPostProcessor和BeanFactoryPostProcessor后置器,并自动注册到应用上下文中,而BeanFactory需要手工盗用addBeanPostProcessor()方法进行注册
- ApplicationContext初始化应用上下文就可以实例化所有的单例Bean,BeanFactory初始化并不会实例化Bean,直到第一次访问某个bean才实例化
Bean初始化过程:
- BeanDefinitionReader读取Resource指向的配置文件资源,然后解析配置文件。配置文件中的每个
<bean>
都会解析为一个BeadDefinition对象,并保存在BeanDefinitionRegistry中 - 容器扫描BeanDefinitionRegistry中的BeanDefinition;调用InstantiationStrategy进行Bean实例化的工作,使用BeanWrapper完成Bean属性的设置工作
- 单例Bean的缓冲池,是通过HashMap实现的缓冲池,key为BeanName
IOC容器装配Bean
-
装配Bean方式
-
XML配置(常见)
//通过无参的构造函数实例化 <bean id="user" class="User"/> //通过有参的构造函数实例化,value表示一个普通类型的值,ref可以只想一个对象的值 <bean id="user" class="User"> <!--通过constructor这个节点来指定构造函数的参数类型、名称、第几个--> <constructor-arg index="0" name="id" type="java.lang.String" value="1"></constructor-arg> <constructor-arg index="1" name="username" type="java.lang.String" value="xxx"></constructor-arg> </bean>
通过IOC容器获取对象
//1.加载Spring配置文件,通过XmlBeanFactory+配置文件创建IOC容器 Resource resource=new ClassPathResource("applicationContext.xml"); BeanFactory beanFactory=new XmlBeanFactory(resource); User user1=(User) beanFactory.getBean("user"); //2.直接通过ClassPathXmlApplicationContext获取IOC容器 ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml"); User user2=(User) ac.getBean("user");
工厂静态方法创建对象
- 工厂类
public class Factory { public static User getBean(){ return new User(); } }
- applicationContext.xml
<bean id="factory_user" class="com.zakary.springtest.controller.Factory" factory-method="getBean"></bean>
- 使用静态工厂方法返回该对象
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml"); User user=(User) applicationContext.getBean("factory_user");
工厂非静态方法创建对象
- 工厂类
public class Factory { public User getBean(){ return new User(); } }
- applicationContext.xml
<!--先创建工厂对象--> <bean id="factory" class="com.zakary.springtest.controller.Factory"/> <!--指定工厂对象和工厂方法--> <bean id="user" class="com.zakary.springtest.dao.User" factory-bean="factory" factory-method="getBean"/>
- 工厂类
-
注解(常见)
常见的注解@ComponentScan 扫描器 @Configuration 配置类 @Component/@Name 指定把一个对象加入IOC容器 @Repository 作用同@Component; 在持久层使用 @Service 作用同@Component; 在业务逻辑层使用 @Controller 作用同@Component; 在控制层使用 @Resource 依赖关系 如果@Resource不指定值,那么就根据类型来找,相同的类型在IOC容器中不能有两个 如果@Resource指定了值,那么就根据名字来找
- 引入context名称空间
xmlns:context="http://www.springframework.org/schema/context"
- 开启注解扫描器
- 通过xml文件
<context:component-scan base-package="com.zakary.springtest.dao"></context:component-scan>
- 通过配置类
使用@ComponentScan的时候,测试类需要@ContextConfiguration注解来加载配置类//表明该类是配置类 @Configuration //启动扫描器,扫描该包下的,也可以指定多个基础包,指定类型 @ComponentScan("com.zakary.springtest.dao") public class AnnotaionScan { }
- 引入context名称空间
-
JavaConfig
spring不能对第三方组件库组件进行装配,于是需要显式装配配置,显示装配方法:- java代码装配
编写一个java类,使用@Configuration修饰该类,被@Configuration修饰的类就是配置类- 配置类
@org.springframework.context.annotation.Configuration public class Configuration { @Bean public UserDao userDao(){ UserDao userDao=new UserDao(); System.out.println("created in configuration"); return userDao; } }
- 使用配置类创建bean
使用@Bean修饰方法,该方法返回一个对象,Spring内部会将该对象加入IOC容器中,bean的Name默认为方法名
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao=(UserDao) applicationContext.getBean("userConfigDao");
- XML显式装配
注解,JavaConfig,xml是可以混合使用的,可以创建更高级的配置类进行组合
Java引用XML,使用@ImportResources()
XML中引用JavaConfig,直接使用<bean>
- java代码装配
-
基于GroovyDSL配置
bean的一些属性
- scope 单例/多例(singleton/prototype),使用单例时,对象在IOC容器之前就创建了,使用多例时,从IOC中获取的对象都是不同的,且在被使用的时候才创建
- lazy-init 只对单例对象有效,默认为false,设置该对象在使用时才创建
- init-method 对象在创建后执行某个方法
- destroy-method IOC容器销毁后执行某个方法
-
-
依赖注入方式
- 属性注入 setter()方法
- 构造函数注入
- 工厂方法注入
-
对象间的关系
- 依赖 (depend-on 前置依赖,依赖的bean初始化后,当前的bean才初始化)
- 继承 (abstract,parent实现继承关系)
- 引用 (ref关系)
-
Bean作用域
- 单例Singleton
- 多例prototype
- web相关的Bean作用域
- request
- session
-
自动装配的歧义性
同一个接口不同的实现类- 使用@Primary注解设置为首选的注入Bean
- 使用@Qualifier注解设置特定名称的Bean来限定注入
-
Spring框架中Bean的生命周期
- Spring容器从XML中读取Bean的定义,并实例化
- Spring根据Bean的定义填充所有的属性
- 如果Bean实现了BeanNameAware接口,Spring传递Bean的ID到setBeanName方法
- 如果Bean实现了BeanFactoryAware接口,Spring传递beanfactory给setBeanFactory方法
- 如果有任何与Bean相关联的BeanPostProcessors,Spring会在postProcessorBeforeInitialication方法中调用他们
- 如果Bean实现了InitilizingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法
- 如果有BeanPostProcessors和Bean管理,这些Bean的postProcessorsAfterInitialization()方法将被调用
- 如果Bean实现了DisposableBean,它讲调用destroy()方法
4. AOP
AOP,面向切面编程,将分散在各业务逻辑代码中相同的代码通过横向切割的方式抽取
代理可以增强对象的行为
静态代理:
实现:AspectJ,JBossAOP,语言级别的AOP实现,扩展了Java语言,定义了AOP语法,能够在编译期提供横切代码的织入,有专门的编译期来生成遵守Java字节码规范的Class文件
AOP术语:
- 连接点(JoinPoint) 能被拦截的地方,动态代理中是方法拦截的,每个成员方法都是拦截点
- 切点(PointCut) 具体定位的连接点,动态代理中具体定位到某个方法就成为连接点
- 通知(Advice) 表示添加到切点的一段逻辑代码,并定位连接点的方位信息,Advice类型有(前置,后置,返回,异常,环绕等)
- 织入(Weaving) 将Advice添加到目标类的过程
- 引入(Introduction)向现有类添加新的方法或属性,是一种特殊的增强
- 切面(Aspect) 由切点,Advice组成,包括横切逻辑的定义,连接点的定义
SpringAOP的构建在动态代理的基础上,因此Spring对AOP的支持局限于方法拦截
Java中有两种动态代理方式
- JDK动态代理
实现某个接口 - CGLib动态代理
生成的代理对象是目标类的子类
使用单例时最好使用CGLib,多例使用JDK代理,JDK在创建对象时性能优于CGLib,但生成代理对象的运行性能却比CGLib低
Spring对AOP的支持
- 基于道理的SpringAOP 需要实现接口,手动创建代理
- POJO切面 使用XML配置,aop命名空间
- @AspectJ注解驱动的切面
Spring中Advice类型
- 前置增强,在目标方法执行前增强
- 后置增强 在目标方法执行后增强
- 环绕增强 在目标方法执行前后都增强
- 异常增强 在目标方式抛出异常的时候增强
- 引入增强 在目标类中增加新的方式和属性
Spring中切点的类型
- 静态方法切点 静态方法切点的抽象基类,默认匹配所有的类
- 动态方法切点 动态方法切点的抽象基类,默认匹配所有的类
- 注解切点 实现类表示注解切点
- 表达式切点 为支持AspectJ切点表达式语法而定义的接口
- 流程切点 根据程序执行堆栈信息查看目标方法是否有某方法间接或直接发起调用,判断是否为匹配的对象
- 复合切点 为创建多个切点而提供的方便操作类,可以使用连接表达式对切点进行操作
Spring中基于注解和命名空间的AOP编程
- 在XML配置文件中为AOP提供命名空间
- 支持AspectJ切点表达式
- 集成AspectJ
实现:
- 方法切点函数
- execution() 匹配满足模式串的所有目标类方法的连接点
- @annotation() 表示标注了特定注解的目标类方法的连接点
- 方法入参切点函数
- args() 通过判别目标类方法的入参对象的类型指定连接点
- @args() 通过判别目标类方法运行时入参对象的类是否标注特定注解来执行连接点
- 目标类切点函数
- within() 表示特定域下的所有连接点
- target() 假如目标类按类型匹配于指定类,则目标类的所有连接点匹配这个切点
- @within() 假如目标类按类型匹配于指定类,则目标类的所有连接点匹配这个切点
- @target() 加入目标类标注了特定注解,则目标类的所有连接点都匹配该切点
- 代理类切点函数
- this() 代理类按类型匹配于执行类,则被代理的类的所有连接点都匹配该切点
常见问题
1. Spring中用到的设计模式
- 工厂模式,BeanFactory,简单工厂模式
- 单例模式 Bean默认为单例模式
- 代理模式 AOP功能
- 观察者模式 定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象会得到通知被动更新
2. BeanFactory,ApplicationContext关系
Spring这两种接口都表示容器
BeanFactory可以理解为HashMap,key为BeanName,Value为Bean实例,只提供注册,获取功能,为低级容器
ApplicationContext提供了更多功能,继承了多个接口,实现了较多功能,例如资源的获取,支持多种消息,对BeanFactory多了工具级别的支持,该接口定义了refresh方法,用于刷新整个容器,重新加载所有Bean。
- BeanFactory
需要手动注册Bean
Spring中最底层的接口,包含各种Bean的定义,读取bean的配置文档,管理bean的加载实例化,控制bean的生命周期,维护bean间的依赖关系
采用延迟加载的形式注入Bean,只有在使用该Bean的时候再加载实例化,不能发现Spring的配置问题 - ApplicationContext
自动注册Bean
派生BeanFactory,除了提供BeanFactory具有的功能外,还提供更完整的框架功能- 继承MessageSource,支持国家话
- 统一的资源文件访问方式
- 提供在监听器中注册bean的事件
- 同时加载多个配置文件
- 载入多个上下文,使得每个上下文都专注于一个特定的层次
在容器启动的时候一次性创建了所有的Bean,即容器启动的时候就能检查到配置错误,有利于检查依赖属性是否注入
实现方式:
FileSystemXmlApplicationContext
:此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数ClassPathXmlApplicationContext
:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置WebXmlApplicationContext
:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean
3. SpringBean作用域
- singleton 每个IOC容器中只有一个实例(缺省,线程不安全的)
- prototype 可以有多个实例
- request 每次http请求都会创建一个bean,尽在基于web的SpringApplicationContext下有效
- session 在一个HttpSession中创建一个bean,
- global-session 在全局的HttpSession中,一个Bean对应一个实例
5. SpringBean生命周期
- Spring对Bean实例化
- 填充属性
- 如果实现了BeanNameAware接口,Spring将bean的ID传给SetBeanName方法
- 如果实现了BeanFactoryAware接口,Spring调用setBeanFactory方法,将BeanFactory容器实例传入
- 如果实现了ApplicationContextAware接口,Spring将调用setApplicaitonContext()方法,将bean所在的应用上下文引用传入进来
- 如果Bean实现了BeanPostProcessor接口,Spring将调用postProcessorBeforeInitialization()方法
- 如果实现了InitialzingBean接口,Spring将调用afterPropertiesSet()方法
- 如果实现了BeanPostProcessor接口,Spring将调用他们的post-ProcessAfterInitialization()方法,
此时Bean准备就绪,并且可以使用,驻留在应用上下文中,直到应用上下文销毁 - 如果Bean实现了DisposableBean接口,Spring将调用他们的destory接口方法