Spring的起源
2002年,作者出版了一本书。
对传统的、以EJB为核心的J2EE作出了一些批判和思考。
2003年,推出一个框架: Spring。
Spring是传统的、以EJB为核心的J2EE的一种改进和简化。
传统的、以EJB为核心、以及应用服务器 (Weblogic等)为支撑的J2EE应用,被称为重量级J2EE。
以Spring为核心、无需应用服务器,只要Web服务器(Web容器)为支撑的J2EE应用,被称为轻量级J2EE。
再后来,Spring得到所有Java EE开发者的支持。
早期,taobao用php,后来就把整个taobao全部用java改写了,
jd早期用asp.net做到,在多人抢的时,总死机。换Java, 坚挺了
传统Java EE开发存在的2个主要问题:
1. 依赖关系管理存在的问题
不管是Java应用,还是Java EE应用,各组件之间大量存在A组件需要调用B组件的方法的场景。
对于这种存在方法调用的场景,可以称为A组件依赖B组件。此时有三种解决方式:
- A组件先new 一个B组件、再调用B组件的方法。
A组件的代码,直接与B组件的实现类耦合, 如果有一天应用需要更换B组件,程序就需要改A组件代码。
- A组件面向B组件的接口编程,然后A通过B组件工厂获取B组件,再调用B组件的方法。
优势是:A组件的代码,没有与B组件的实现类耦合,只和B组件的接口耦合。
如果有一天应用需要更换B组件,只要换一个实现类,接口不动,程序无需改A组件代码。
- A、B两个组件都由“容器”管理,“容器”先将B组件传给A组件,A组件直接调用B的方法
2. 事务控制存在的问题。
- 事务控制应该放在Service层完成。
- 事务控制代码通过哪个API完成
JDBC:Connection(setAutoCommit、rollback commit)
Hibernate: Transaction
MyBatis: Connection
JPA: EntityTransaction
——事务控制的API各不相同,而且都需要依赖持久层的API
那意味着,当持久层技术发生改变时,不仅需要需要修改DAO组件,还需要修改Service的事务控制代码。。
- 应用为什么要分为Service层和DAO层?
最终希望:Service层负责业务逻辑的改变, DAO层完全不需要任何修改。
DAO层负责持久层技术的改变,当持久化技术发生改变时,Service层无需做任何修改。
这种设计来自于传统的“桥接模式”的启发,23种设计模式之一。
最后希望得到一个技术:可以让业务逻辑组件不需要出现持久层API。但又可以有事务控制代码。
/**********************************
桥接模式:
当一个组件存在2个甚至2个以上维度的变化时,项目应该将组件分离2个或2个以上的组件,
每个组件就负责一个唯独的变化,组件与组件以接口方式耦合在一起,当需要最终组件时,
然后再把这些组件组合起来就可以了。
***********************************/
Spring的下载和安装:
(1) 解压下载得到的压缩包,得到如下文件结构:
docs: 包含API文档和参考手册(很好的Spring学习图书)
libs: Spring框架分模块包,每个包都有对应的jar、api文档jAR包, 源码JAR包。
schema: 是Spring配置文件的语义约束Schema文档。
Spring框架没有示例。
(2)将libs目录下所有class文件的JAR包添加到应用中+common-loging的日志包。
点UserLibraries,new一个用户库,点addExternalJARs添加jar包。
使用时 buildPath->addLibrary->UserLibrary->
(3)添加一个Spring的配置文件,文件名任意的xml文件。
(4) 创建Spring容器,并获取容器中的Bean。
Spring框架的大致结构:
JDBC SpringMVC
ORM Struts2
--------------------------
AOP
----------------------------
IoC容器
Spring容器有两个接口:
Spring上下文、Spring工厂
BeanFactory(只是提供了判断Bean是否存在、获取Bean等最基本的方法)
↑
ApplicationContext(功能更强大)
ㄊ ㄉ
ClassPathXmlApplicationContext FileSystemXmlApplictionContext
Spring的本质:通过XML配置来驱动Java代码。
Spring用的熟:几乎所有Java代码都放在XML去配置。
要求:眼中看到的XML配置,心中想的是执行的Java代码。
Spring常有有如下元素:
- bean元素 :驱动使用new调用构造器。 默认它总是调用无参数的构造器。如果想控制它调用有参数的构造器,就需要在
class属性写的必须是类,接口不行。
配置代码示例:
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">
有的构造器有两个构造器都是含一个参数的,为了明确地指定该值的类型,可以指定type属性。
- property元素: 驱动它调用setter方法。 对象创建出来之后,立即就会被调用。
- constructor-arg元素: 驱动调用有参数的构造器。
IoC (控制反转)和DI(依赖注入)
IoC是Spring作者在Java大会上提出,但大家都觉得这个名词很难理解,Martine Fowler给这个技术新名称:DI。
当A组件依赖B组件时,传统做法有两种:1. 先创建B组件的实例,然后调用B组件的方法。 2. A通过工厂去获取B组件,然后调用B组件的方法——都属于【主动】式。
用了Spring之后,A无需主动去获取B组件,等待容器将B组件注入A组件,此时就成了【被动】接受注入即可。
用了Spring之后,由原来的【主动】获取,变成了【被动】接受Spring容器【注入依赖组件】——这就是控制反转。
如果关注Spring容器干的事情: 【注入依赖组件】——这个技术被称为依赖注入。
依赖注入可分为3种:
- 接口注入。很少使用。
- 设值注入:就是通过property元素控制调用setter方法,就是所谓的设值注入。
- 构造注入:就是constructor-arg控制调用有参数的构造器,由构造器来注入被依赖组件。
就是所谓的构造器注入。
一般建议:以设值注入为主,构造器注入为辅。
对于Spring容器而言, 一切都是Bean。
Spring容器中的Bean,就是Java对象。
Spring容器是一个超级大工厂,所有Bean(对象)都是由Spring容器创建出来。
ApplicationContext的强大之处 (比BeanFactory多出来的功能)
1. ApplicationContext默认会预初始化容器中所有的singleton Bean
何谓预初始化:Spring创建容器的同时,会初始化容器中所有的singleton Bean,
并根据property调用对应的setter方法。
BeanFactory不会预初始化容器中所有的singleton Bean。
如果希望取消ApplicactionContext的预初始化也可以,只要为
2. ApplicationContext继承MessageSource接口,因此提供国际化支持。
国际化3步:
(1) 编写国际化资源文件。在src下创建mess_zh_CN.properties文件:
hi=\u60A8...
wel={0}...
创建mess_zh_CN.properties文件:
hi=hello
wel={0},welcome to ...
(2) 加载国际化资源文件。
容器中一个名为messageSource,类型为ResourceBundleMessageSource的Bean用于管理国际化资源文件,在beans.xml里配置好,可点openType找包名
配置setBasenames(String[] basenames)这个方法时,参数是数组,xml里的配置就要用list:
beans.xml里的配置示例:
(3) 调用方法根据key来获取对应的国际化消息。
ApplicationContext本身就有相应的方法。
//第一个参数是国际化消息的key,第二个参数负责为消息中占位符填参数,第三个参数是Locale
ctx.getMessage("hi", null, Locale.getDefault(Category.FORMAT));
ctx.getMessage("wel", new String[]{"八戒"}, Locale.getDefault(Category.FORMAT));
3. 资源访问, 比如访问指定URL和文件中的内容。
读取类里有Resource 和 String 类型的成员变量(提供set方法)。读取的资源是文本
public void printResource()throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(myRes.getInputStream(),charSet));
String line = null;
while( (line=br.readLine())!=null ){
System.out.println(line);
}
}
beans.xml文件里:
读文件
读网络
使用这个类:
//创建spring容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
MyReader reader = (MyReader) ctx.getBean("reader");
reader.printResource();
4. 事件机制。
5. 加载多个配置文件。
配置bean的别名
id属性是一个标识符,不能包含/*#-等特殊符号。这时就需要配置一个别名。
指定别名有两种方式:
方式一:在bean元素的name属性指定
方式二:添加
以上为某个类添加了四个别名。使用跟id没区别。Pig pig = (Pig)ctx.getBean("xy*");
/***************************************
关于
在
在
在
****************************************/
通过Bean获取Spring容器(ApplicationContextAware接口)
1.让Bean实现ApplicationContextAware接口
Spring会检查容器中所有的bean,如果发现某个bean实现了ApplicationContextAware接口,
Spring会自动调用该bean的setApplicationContext(ApplicationContext arg0)方法,
调用该方法,Spring会将自身作为参数传入。
2.实现接口中的setApplicationContext()方法
bean的作用域
就是bean能生存多久。
Bean的作用域是通过scope属性来配置的,该属性有如下值:
-- singleton:单例,无论何时通过Spring容器中取出的总是同一个实例。Spring默认采用该模式。
singleton bean何时出生,容器创建时,singleton bean会跟着预初始化。
singleton bean何时消亡,容器不死,它就不死。
singleton bean需要长期占用内存,但对象可以一次创建,无数次使用。
-- prototype:不会预初始化。每次程序通过Spring容器中获取实例,Spring都会new一个新的实例返回。
Spring容器就不再管prototype Bean,如果程序不再需要该Bean,该Bean就会被垃圾回收。
prototype Bean何时出生,当程序通过Spring容器去获取时。
prototype Bean何时消亡,程序不再需要时,就可能被垃圾回收。
prototype Bean出生之后,容器完全不管它。
--request:对应web应用中的request作用域。
只能在web应用中有效,相当于在一次web请求中是singleton的。
--session:对应web应用中的session作用域。
只能在web应用中有效,相当于在一次web会话中是singleton的。
--global session,仅在使用portlet context的时候才有效。
第二天
Spring的本质:通过XML配置去驱动Java代码的执行。
-
到底调用哪个构造器,取决于是否包含constructor--arg元素。
-
-
不管是构造注入,还是依赖注入也好,都必须配置参数值。
- 如果参数值是标量类型,用value属性或value子元素。甚至p:setterName = "参数值"
使用先导入:xmlns:p="http://www.springframework.org/schema/p"前后不能变
p:age="20" (如果参数是复合类型数据,p:age-ref="复合类型")
就是p:原来name的值[-ref]="value的值"
- 如果参数是复合类型. 比如UserService,Dog.
1. 用ref子元素或ref属性。甚至p:setterName-ref="参数值"
2. 使用嵌套Bean.
嵌套Bean代表把
该嵌套Bean所配置的对象将只是作为setter方法、构造器参数的值,不能被容器直接访问,因此无需id属性值。 在其他配置上,嵌套Bean与普通Bean是一样的。
嵌套Bean的好处:被很好保护在外部Bean里面,提供了更好的内聚性。
坏处:嵌套Bean就显得不够灵活。
3. 使用自动装配。
- 如果参数是集合类型:
数组,List: 用
Set集合:使用
Map集合:使用
Properties集合:使用
自动装配,通过autowire或default-autowir,可接受如下属性值:
no:不使用自动装配。Bean依赖必须通过ref元素定义。这是默认的配置。
byName:根据setter方法名来自动装配。BeanFactory查找容器中全部Bean,找出其中id属性与setter方法名去掉set前缀后同名的Bean来完成注入。如果没有找到匹配的Bean实例,则Spring不会进行任何注入,也不报错。
byType:根据setter方法形参类型来自动装配。BeanFactory查找容器中全部Bean,如果正好有一个Bean类型与setter形参类型匹配,就自动注入这个Bean;如果有多个这样的Bean,就抛出一个异常;如果没有找到匹配的Bean实例,则Spring不会进行任何注入,也不报错。
constructor:与byType类似,区别是用于构造注入的参数。如果BeanFactory中不是恰好有一个Bean与构造器参数类型相同,则会抛出一个异常。
autodetect:BeanFactory根据Bean内部结构,决定使用constructor或byType。如果找到一个默认的、无参的构造函数,那么就会应用byType。
auto-candidate:用于指定将某个Bean排除在自动装配的候选之外。
/************************************
Spring框架的发展历史:
Spring 1.0、Spring 1.1
根据W3C建议:XML文档中,应该多用子元素,少用属性。
Spring 1.2
Spring 2.0
p:age="20"
Spring 3.0
************************************/
Spring创建Bean的3种方式:
- 调用构造器来创建Bean。
- Spring也可调用静态工厂方法来创建Bean。
在有些时候,程序可以希望能够把对象的创建工作交给自己的工厂类完成,
这样可以提供更多灵活的控制。
主要靠的是factory-method属性,
工厂方法如需要参数,使用
实现代码:
constructor-arg可引入c空间后写在bean的属性里 c:type="实际传入的参数" 或 c:_0="实际传入的参数"
用这个方法的对象引用bean的id就好,如p:dog-ref="dog"
- Spring也可调用实例工厂方法来创建Bean。
在静态工厂方法的基础上,只增加一个factory-bean属性指定哪个实例,实例又可在xml里预初始化。
主要靠:
factory-method属性:指定工厂方法名
factory-bean属性:指定工厂实例。
工厂方法如需要参数,使用
实现代码:
抽象Bean与子Bean
假如在Spring配置文件中,出现多个Bean的配置信息大部分是相同的,只有少量的差异。
如果任由这种配置方式,导致的后果是:
- 配置文件比较臃肿。
- 如果项目需要修改,程序就需要同步修改多个地方,不利于项目维护、升级。
此时考虑将这些通用的信息抽取出来,配置成Bean模板,很明显程序并不希望将该Bean模板创建为实例,
因此需要增加abstract="true"将该Bean配置成抽象Bean。
抽象Bean, 就是一个配置信息的模板。Spring容器不会创建抽象Bean的实例,
如果程序试图获取抽象Bean实例,Spring就会报错。
子Bean就可以从Bean配置模板那里继承得到相应的配置信息。
通过指定parent属性即可设置子Bean。在
FactoryBean:
它是标准工厂Bean的接口。
当我们把工厂Bean部署在容器中、并通过getBean()方法来获取时,容器返回的不是FactoryBean实例,而是返回FactoryBean的产品(也就是该工厂Bean的getObject方法的返回值)。
示例: 该示例会实现一个特殊的FactoryBean,该FactoryBean可以获取指定类的、指定静态field的值。
FactoryBean接口包含3个方法:
- getObject()该方法的返回值就是该工厂Bean的产品。
- public Class> getObjectType(): 该方法的返回值返回该工厂Bean的产品的类型
- public Class> isSingleton():该方法的返回值用于控制该工厂Bean的产品是否为单例。
配置示例:
获取Bean的id (BeanNameAware接口):
在某些极端的情况下,程序希望定义该Bean的Bean类时,能提前预知该Bean的配置id。
一共有2步
(1)实现BeanNameAware接口。
(2)实现setBeanName()方法。
Spring会检查容器中所有的Bean,如果发现某个Bean实现了BeanNameAware接口。
Spring会自动调用该Bean的setBeanName()方法——在该Bean的setter方法之后
调用该方法时,Spring会将该Bean的配置id作为参数传入。
Bean的生命周期管理:
Bean在Spring容器中,主要有两种行为模式:singleton模式与prototype模式。
对于singleton Bean而言,Spring会一直跟踪该Bean的产生、销毁(容器不死,它就不死)
对于prototype Bean而言,Spring完全不管该Bean。创建出来之后就不再管该Bean。
由于singleton处于Spring容器的管理之下,因此存在生命周期的概念。
管理Bean的生命行为有2个时机:
- Bean的全部依赖关系(setter)被注入后。
A。 通过init-method属性来指定初始化的方法。
B. 实现InitializingBean接口,并实现该接口中afterPropertiesSet()方法
前者需要在配置文件中指定;后者需要实现特定的接口。
- Bean即将销毁之前。
A。 通过destroy-method属性来指定。
B. 实现DisposableBean接口,并实现该接口中destroy()方法
前者需要在配置文件中指定;后者需要实现特定的接口。
无论是初始化方法,还是销毁之前的方法,都是接口声明的方法先执行。
在Java SE项目中,必须注册关闭钩子,才能保证Bean正常销毁。
ctx.registerShutdownHook();
在Java Web项目,Bean总可以正常销毁。
/############ singleton bean需要调用prototype Bean #############/
当singleton Bean依赖prototype Bean时,如果配置了依赖注入,程序就可能出现问题。
singleton Bean在容器初始化时就会被创建出来,当容器创建singleton Bean时,
如果singleton Bean依赖了prototype Bean,容器就会创建prototype Bean,并立即将prototype Bean
注入singleton Bean——以后程序不会再为singelton执行依赖注入,
这样就导致,程序通过singleton Bean访问prototype Bean时,永远访问的同一个。
于是bug产生了,singleton Bean把prototype Bean变成了singleton 行为。
Spring Bean,何时应该使用singleton?何时应该使用prototype?
只要该Bean有field,该field用于保存用户的状态数据,说明该Bean是stateful,应该用prototype。
只要该Bean根本没有field,说明该Bean是stateless,应该用singleton。
结论:永远不要配置singleton Bean依赖注入prototype Bean !
如果singleton bean确实又需要prototype Bean,有两种解决方法:
-方法一: 每次要用prototype Bean , singleton Bean通过Spring容器去获取一次。
这种做法需要让Bean与Spring API耦合——代码污染。
-方法二: 使用lookup方法注入
增加lookup-method元素之后,Spring会在运行时调用CGLIB来动态创建Hunter的子类,
这个子类是具体类,而且Spring它会实现lookup-method元素中 name属性所指定的方法,bean属性里写的是要返回的bean的id
实现该方法代码只有2行:
1. 得到Spring容器。
2. return ctx.getBean(dog)
代码:Dog getDog()是抽象方法。
/**************************************
JDK动态代理: 它可以在运行时动态生成代理类,并通过代理类为原始类增加新的代码。
JDK动态代理最大限制在于,被代理的类必须实现接口。
CGLIB Code Generator lib: 更NB的JDK动态代理,被代理的类无需实现接口。
Javassist:
CGLIB与Javassist都属于字节码增强的工具,可以在运行时动态生成类。
*************************************/
Spring框架的本质:
在Spring配置文件中使用XML元素进行配置,实际上驱动Spring执行相应的代码。例如:
使用
使用
Java程序还可能有其他类型的语句:调用getter方法、调用普通方法、访问类或对象的Field,
而Spring也为这种语句提供了对应的配置语法:
- 调用getter方法:使用PropertyPathFactoryBean。
- 访问类或对象的Field值:使用FieldRetrievingFactoryBean。
- 调用普通方法:使用MethodInvokingFactoryBean。
先导入util的命名空间xmlns:util="http://www.springframework.org/schema/util"
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd
调用getter方法:
使用PropertyPathFactoryBean。 完整类名:org.springframework.beans.factory.config.PropertyPathFactoryBean
调用哪个对象: setTargetObject(Object targetObject)
调用哪个getter方法: setPropertyPath(String propertyPath)
简化语法:
要注意的是:在getBean()方法执行的时候,getter方法才会开始执行,相当于 val = obj.getter();
访问Field的值:
使用FieldRetrievingFactoryBean
如果访问静态Field的值,需要指定:
访问哪个类: setTargetClass
访问哪个Field: setTargetField
有一个简化语法:
如果访问实例Field的值(该Field必须是public修饰的才行),这个功能其实很鸡肋。需要指定:
访问哪个实例: setTargetObject
访问哪个Field: setTargetField
由于访问实例field是一个鸡肋功能,因此没有简化配置方法。
调用普通方法:
使用MethodInvokingFactoryBean。完整名:org.springframework.beans.factory.config.MethodInvokingFactoryBean
如果调用静态方法的,需要指定如下3个:
访问哪个类: setTargetClass
访问哪个方法: setTargetMethod
传入参数: setArguments(数组)
如果调用实例方法的,需要指定如下3个:
访问哪个对象: setTargetObject
访问哪个方法: setTargetMethod
传入参数: setArguments(数组)
要求:无论给出什么Java代码,我们都应该能够将其在配置文件中配置出来。
练习:
Properties props = System.getProperties();
String osName = props.getProperty("os.name");
System.out.println(osName);
props.store(new FileOutputStream("a.txt") , "aaaaa");
到底应该把哪些Java代码放在Spring的配置文件中管理呢?
一般来说,没有不要把所有Java代码都放到Spring配置文件中管理。
只需要将那些经常需要改变,以及管理组件之间耦合管理的代码才需要提取到配置文件中管理。
util命名空间。
constant:该标签用于将指定类的静态Field暴露成一个Bean实例。
它是FieldRetrievingFactoryBean的简化配置。
property-path:该标签用于将指定Bean实例的指定属性(即getter方法返回值)暴露成一个Bean实例。
它是PropertyPathFactoryBean的简化配置。
list:该标签用于定义容器中的List集合,并支持通过list-class属性指定List集合的实现类。
set:该标签用于定义容器中的Set集合,并支持通过set-class属性指定Set集合的实现类,
map:该标签用于定义容器中的Map集合,并支持通过map-class属性指定Map集合的实现类
上面3个元素与前面介绍的注入集合属性的区别并不大。
properties:该标签用于加载一份属性文件,并根据加载的属性文件创建一个Properties Bean实例。
转换关系总结:
构造器
setter方法
参数
执行静态方法
执行实例方法
执行抽象方法 bean里
getter
静态Field
普通方法 p:targetObject-ref="cat" p:targetMethod="putong">
第3天
Spring框架的本质:
通过Spring配置文件的XML元素去驱动Java代码的执行。
调用构造器: 使用
根据构造器所需的参数个数,使用对应的
调用setter方法:使用
传入参数: 参数可分为标量: 用value
复合类型: 用ref、 嵌套Bean、 用自动装配。
集合类型: list(List集合或数组),set(Set集合)、map(Map集合),props
调用getter方法:使用
访问Field: 静态field:
实例Field:使用FieldRetrivingFactoryBean
调用普通方法: MethodInvokingFactoryBean
Spring EL(表达式语言)
①:可用于对表达式求值(可以脱离Spring容器独立运行),②:也可简化Spring配置文件。
假如有"5 + 10 / 3 - 6" , 这种即可称为表达式。
SpEL主要靠如下3个接口:
- ExpressionParser:该接口的实例负责解析一个SpEL表达式的字符串,返回一个Expression对象。
- Expression:该接口的实例代表一个表达式。
- EvaluationContext:上下文。类似Struts 2的Stack Context。当表达式中有变量时
SpEL会到EvaluationContext中去解析变量。
与SpEL所支持的语法,可以参考7.5节。
①:独立使用时,可分为3步骤:
(1) 创建ExpressionParser表达式解析器。
(2) 使用解析器去解析字符串,返回Expression表达式对象。
(3)调用Expression表达式对象的getValue()方法得到表达式的值。
②:简化Spring配置文件。
记住: SpEL要放在 #{}里面。
后处理器
后处理器属于对Spring容器进行扩展的内容、属于高级知识点,因此一般用不到。
通常在框架整合等高级部分才用到;而且即使用到,通常会由技术经理写完了。
Spring后处理器分为良种:
- Bean后处理器。
- 容器后处理器。
Bean后处理器: 负责对容器中的每个Bean都进行后处理。
得到医生的某种后处理
人出生 -----------------------> 增强版的人
得到Bean后处理器的后处理
Bean出生 -----------------------> 增强版的Bean
Bean后处理器负责对容器中所有Bean进行某个方面的功能增强。
实现Bean后处理器:
(1) 实现BeanPostProceessor接口,并实现该接口中两个方法。
- Object postProcessAfterInitialization(Object bean, String beanName):初始化之后的后处理。
- Object postProcessBeforeInitialization(Object bean, String beanName):初始化之前的后处理。
* @param bean:代表该Bean后处理器将要后处理的Bean
* @param beanName:代表该Bean后处理器正在后处理的Bean的id
* @return 代表被后处理之后的Bean
该Bean后处理器非常强大,它可以对原来的Bean进行修改,甚至完全替换原有的Bean,甚至搞到没有。
(2)将BeanPostProceessor接口的实现类部署在Spring容器中,Spring容器将会自动把它当成Bean后处理器。
Bean后处理器会对容器中每一个Bean都执行一次后处理;
因此容器中有多少个Bean,Bean后处理器就需要处理几次。
容器后处理器: 负责对Spring容器本身进行后处理。
Spring容器基本已经是完美的,所以程序几乎不太需要对容器进行后处理。
实现容器后处理器:
(1) 实现BeanFactoryPostProcessor接口,并实现该接口中如下方法。
- postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory):对容器的后处理。
* @param beanFactory):代表该容器后处理器将要后处理的Spring容器
* @return 代表被后处理之后的Bean
该容器后处理器非常强大,只能修改Spring容器,不能完全替换Spring容器。
——之所以不允许完全替换Spring容器,那是因为Spring框架的本质就是Spring容器。
如果替换了Spring容器,那么你用Spring框架也就失去了意义。
(2)将BeanFactoryPostProcessor接口的实现类部署在Spring容器中,
Spring容器将会自动把它当成容器后处理器。
容器一旦创建出来,容器后处理器就会起作用。
一般来讲,一个项目只要一个Spring容器,因此容器后处理器只有一次起作用的机会。
两个常用的容器后处理器:
PropertyPlaceholderConfigurer: 属性占位符配置器。
它可以将某些集中的配置信息,提取出来放在专门的Properties文件中管理。
然后在Spring配置文件中通过${key}来引用相应key所对应的value。
它的简化配置是:
PropertyOverrideConfigurer: 属性重写配置器。
采用PropertyOverrideConfigurer之后,可以直接在Properties文件中管理依赖注入,
无需在Spring配置文件中引用
它的简化配置是:
零配置
前面介绍的,都是使用XML文件来管理Spring容器中的Bean。
从Java引入注解之后,Spring也支持使用注解来管理容器中的Bean——这种方式就被称为"零配置"。
使用“零配置”的步骤:
(1) 在配置文件中启用“零配置”搜索。
(2)使用注解:
@Component|@Controller|@Service|@Repository - 都用于标注Spring Bean。
@Controller|@Service|@Repository所标注的Bean具有特殊的Java EE组件含义。
使用该注解时,相当于id默认为类名首字母小写。
@Scope – 指定Bean的作用域。相当于scope属性
@Resource -配置依赖注入( Spring借用了Java EE的Annotation)。
作用有点类似
它不仅支持setter方法注入,还支持field注入
@PostConstruct和@PreDestroy(修饰方法,Spring借用了Java EE的Annotation)。
@PostConstruct修饰一个方法, 相当于init-method属性所指定的方法。
@PreDestroyt修饰一个方法, 相当于destory-method属性所指定的方法。
@DependsOn:强制初始化其他Bean, 相当于
@Lazy:指定延迟初始化。 相当于
@Autowired与@Qualifier:自动装配。@Autowired默认是byType的自动装配。
如果你希望采用byName注入策略,不如直接用@Resource来注入。
@Qualifier可指定byName的自动装配。
零配置的好不好?
优势: 简洁。
坏处: 用XML配置时,所有组件关系都由XML集中管理,维护起来很方便。
之所以使用Spring,通过XML配置文件,把原来放在Java代码管理管理的耦合关系,
提取到XML中进行配置管理;如果你使用注解的话,组件的耦合关系又回到Java代码中。
Struts2+Spring整合
(1)烤JAR包。
(2)在web.xml配置Strut2的核心控制器。
(3)增加struts.xml配置文件。
(4)在web.xml配置一个ContextLoaderListener, 该Listener负责2个事情
Web应用启动时,创建Spring容器、并将该容器放入Web应用application范围中
Web应用销毁时,保证以优雅的方式销毁容器中的Bean
ContextLoaderListener默认只加载一个配置文件:WEB-INF/applicationContext.xml
可通过contextConfigLocation参数来指定Spring的配置文件。
(5) 添加Spring配置文件
(6) 烤Struts2-Spring整合的插件包。struts2-spring-plugin-2.3.16.1.jar??
===========================上面5步,是Struts2_Spring整合的安装步骤=========================
整合Spring之后的区别:
1. Action不再主动获取Service组件,改为被动接受Spring容器的注入。
2. Action的setter方法要与Service的配置id对应。
Struts2+Spring+hibernate整合
(1)在Spring配置文件中配置DataSource,(里面包含用户名和密码等)
/WEB-INF/daoContext.xml
p:driverClass="${driver}" p:jdbcUrl="${url}" p:user="${user}" p:password="${pass}" p:maxPoolSize="${maxSize}" p:minPoolSize="${minSize}" p:initialPoolSize="${initSize}" /> 数据的值在 但要先导入xmlns:context="http://www.springframework.org/schema/context" 下面再加http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd (2)在Spring配置文件中配置SessionFactory p:configLocation="classpath:hibernate.cfg.xml" p:dataSource-ref="dataSource" /> --为SessionFactory增加hibernate.cfg.xml配置文件(src目录下) 持久化类增加注解 类: @Entity @Table(name="book_inf") 主键: @Id @Column(name="book_id") @GeneratedValue(strategy=GenerationType.IDENTITY) (3)Spring可以直接把SessionFactory注入Dao组件中。 基于老师提供BaseDao来开发DAO组件会更加简单。 事务也不需要处理,程序使用使用AOP配置即可。 开发步骤:配置action -> 处理类增加方法,调用服务层,返回逻辑视图写物理视图 -> 服务层增加方法,调用dao -> dao增加方法 删除功能,用实体封装要删除的id可少写一个id属性,即: 删除,另外取id也可写成:${b.id} 迭代数据时 如果写var="b",下面取value要加#,即: Spring AOP: 属于Spring框架的两大核心之一。 Spring框架的结构: 前端 后端 Struts2、SpringMVC JDBC、ORM -------------------------------------- Spring AOP -------------------------------------- Spring IoC Sping并未全部上完,比如Spring与EJB整合、Spring与JMS整合、SpringMVC…… 为什么需要AOP?AOP有什么作用? 项目开发完成时,项目具有300个业务方法,每个业务方法都需要增加事务控制代码 解决方法是: 1. 把事务控制代码写成一个方法。 2. 修改项目的300个业务方法的代码,改起来非常繁琐。 为每个业务方法之前都增加权限检查, 解决方法是: 1. 把权限检查代码写成一个方法。 2. 修改项目的300个业务方法的代码,改起来非常繁琐。 …… 由此可见,每当我们需要增加这种“通用性质横切处理”时,程序员就需要修改大量的方法——繁琐,易错。 所有需要AOP,AOP可以让我们无需干上面第2步。 AOP 面向切面编程(AOP)是作为面向对象编程(OOP)的补充。 AOP框架具有如下两个特征: - 各步骤之间的良好隔离性。 - 源代码无关性。 AOP的功能和本质 AOP的功能:保证程序员不修改方法A、方法B、方法C……的前提下, 可以为方法A、方法B、方法C……增加通用处理。 AOP的本质:依然要去【修改】方法A、方法B、方法C…… —— 只是这个修改由AOP框架完成,程序员不需要改! 按照AOP框架修改方法A、方法B、方法C的时机,AOP框架可分为如下两类: 静态AOP框架:AOP框架在编译阶段即实现对目标类的增强,生成静态的AOP代理类 (生成*.class文件已经被改掉了,需要使用特定的编译器)。以AspectJ为代表。 优势:性能更好。在编译阶段已经完成了AOP代理的增强,因此运行时无需修改代码。 劣势:需要特定的编译器。 动态AOP框架:AOP框架在运行阶段动态生成AOP代理(在内存中动态地生成AOP代理类,使用JDK代理或CGLIB), 以实现对目标对象的增强。以Spring AOP为代表。 优势:性能略差。 劣势:无需特定的编译器。 AspectJ AspectJ是一个基于Java语言的AOP框架,提供了强大的AOP功能,其他很多AOP框架都借鉴或采纳其中的一些思想。 下载和安装AspectJ: 1. 运行、下载得到的安装JAR包。 java -jar aspectj-1.8.0.jar,将该软件(绝对绿色)安装到指定目录下。 - bin: 包含了一些支撑AspectJ的工具命令。比如ajc,就是前面所介绍的特定的编译器。 - lib:包含AspectJ的核心JAR包。 - doc:包含AspectJ的文档。 2. 系统还应该将E:\Java\AOP\aspectj1.8\bin路径添加到PATH环境变量中, 将E:\Java\AOP\aspectj1.8\lib\aspectjrt.jar和aspectjtools.jar添加到CLASSPATH环境变量中。 AOP的相关概念: 切面(Aspect、方面): Aspect用于包含Advice。 实际上,一个Aspect可以包含多个Advice。 连接点(Joinpoint):程序执行过程中一个明确的点。 如方法的调用,或者异常的抛出。Spring AOP中,连接点总是方法的调用。 增强处理(Advice:通知、建议):AOP框架在特定的切入点执行的增强处理。 处理有“around”、“before”和“after”等类型。 切入点(Pointcut):可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时, 该连接点将被添加增强处理,该连接点也就变成了切入点。 引入:添加方法或字段到被处理的类。Spring允许引入新的接口到任何被处理的对象。 例如,你可以使用一个引入,使任何对象实现 IsModified接口,以此来简化缓存。 AOP框架为目标类【添加】新的方法、字段、接口的过程,就是所谓的引入。 目标对象:被AOP框架进行增强处理的对象,也被称为被增强的对象。 如果AOP框架是通过运行时代理来实现的,那这个对象将是一个被代理的对象。 简单来说,没有被改过的对象,就是目标对象。 AOP代理:AOP框架创建的对象,简单地说,代理就是对目标对象的增强。 Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理。 前者为实现接口的目标对象的代理,后者为不实现接口的目标对象的代理。 织入(Weaving):将增强处理添加到目标对象中、并创建一个被增强的对象(AOP代理)的过程就是织入。 织入有两种实现方式:编译时增强(例如AspectJ)和运行时增强(例如CGLIB)。 Spring和其他纯Java AOP框架一样,在运行时完成织入。 织入:描述的是该目标方法的过程。 做一个汉堡: 有一个面包。 相当于AOP的目标方法。 准备肉。 相当于AOP的Advice 指定将肉放在面包的上面或下面。 Spring AOP编程的过程: 需要额外增加aopallicane.jar(AOP规范)、AspectJ的JAR包(aspectjtools.jar,aspectjweaver.jar)、(本身Spring4.0一堆包,common-logging日志包必不可少) (1) 目标方法——随着项目开发完成,目标方法早就有了,因此无需操心。 (2) 准备Advice 方式一:用注解(@Aspect:类上,用于指定与用于指定方法:@Before、@After、@AfterReturning、@AfterThrowing和@Around 方法二: (1) 定义一个普通类,准备作为Aspect使用。 (2) 将准备作为Aspect使用的类配置成容器中的Bean。 (3) 使用 (4) 使用 将指定方法转换Advice (3)指定将Advice放入哪个位置。 通过pointcut或pointcut-ref属性来指定。(说明:用pointcut要写完整的表达式,pointcut-ref就直接引用 Spring配置Advice的元素有aop:before、aop:after、aop:after-returning、aop:after-throwing、aop:around, 这5个元素都能指定2个属性: - method:该属性指定将哪个方法转换为Advice。 - pointcut或pointcut-ref:该属性用于指定将Advice织入哪些目标方法。 如果需要在目标方法织入Advice,用Before 如果需要在目标方法成功返回之后织入Advice,用AfterReturning 如果需要在目标方法因为异常结束之后织入Advice,用AfterThrowing 如果需要在目标方法无论成功、还是异常结束之后织入Advice,用After 如果需要在目标方法前、后都织入Advice,用Around AfterReturning 在目标方法成功返回之后织入Advice。 可额外指定一个returning属性,用于访问目标方法的返回值。 returning属性有两个作用: 1. Advice方法可以通过该属性指定的值来访问目标方法的返回值。 2. 该属性指定的返回值还会对目标方法进行限制。 当你在advice方法指定返回值类型为Xxx,就意味着要求目标方法必须返回Xxx类型, 否则该Advice不会织入。 如果希望该返回值类型不对目标方法进行限制,可以将该类型声明为Object AfterThrowing 在目标方法因为异常结束之后织入Advice。 可额外指定一个throwing属性,用于访问目标方法抛出的异常。 throwing属性有两个作用: 1. Advice方法可以通过该属性指定的值来访问目标方法抛出的异常。。 2. 该属性指定的返回值还会对目标方法进行限制。 当你在advice方法指定异常类型为Xxx,就意味着要求目标方法必须抛出Xxx类型的异常, 否则该Advice不会织入。 如果希望该异常类型不对目标方法进行限制,可以将该类型声明为Throwable After 无论目标方法成功完成、还是因为异常结束之后都会织入该Advice 有点类似Java的finally块。 Around 在目标方法前、后都织入Advice, 它可以访问、甚至修改目标方法的调用参数。 它可以访问、甚至修改目标方法的返回值 甚至可以完全阻止目标方法的执行。 Around Advice不是线程安全的,因此需要程序员手动控制线程安全,性能也受到影响。 Around Advice方法有如下要求: A. 该Advice方法必须声明返回值类型。 B. 该advice方法必须声明一个类型ProceedingJoinpoint的形参。 C. 该Advice方法中必须有jp.proceed();否则目标方法就不会执行。 5种Advice的功能总结: Before AfterReturning AfterThrowing After Around 访问调用参数 √ √ √ √ √ 修改调用参数 × × × × √ 阻止目标方法执行 √ × × × √ 访问返回值 × √ × × √ 修改返回值 × × × × √ 访问调用参数,有两种方式 所有Advice都可访问调用参数 A. 为advice方法增加一个类型为Joinpoint的形参。 而Joinpoint类型的参数有一个getArgs方法,该方法即可获取目标方法的调用参数。 B. 使用args切入点指示符。 如果使用这种方式,它会对目标方法进行限制:要求目标方法必须有指定个数、对应类型的形参。 AOP的切入点指示符 P664~P666都是关于切入点指示符的介绍。 execution([访问权限] [返回值类型] 包.类.方法(形参) [throws 异常]); 默认情况下,都可用*作为通配符。 形参列表支持2个通配符, ..代表任意个任意类型的参数; *代表一个任意类型的参数。 (*,java.lang.String) 2个形参,且第二个形参必须是String (..,java.lang.String) 1~N个形参,最后一个形参必须是String (*, ..) 1~N个形参。 target(类型) ——要求目标对象必须是指定类型。 this(类型) ——要求AOP代理对象必须是指定类型。 args(a,b) —— 要求目标方法必须有匹配的形参。 bean(beanid) —— 专门为用Spring的菜鸟准备的。 只为特定Bean的方法织入增强处理。 原生AspectJ并不支持,只有用Spring才支持。 传统事务控制存在的问题: Java EE应用将Service、DAO分离的目的:希望Service负责业务逻辑的改变,DAO负责持久层业务的改变。 最终目的是:如果业务逻辑发生改变,只要修改Service组件;如果持久层技术发生改变,只要修改DAO组件。 但实际上,如果使用传统的编程式事务,事务控制代码必须放在Service组件中控制,而事务控制代码又必须 通过持久层API来实现(JDBC事务调用Connection、Hibernate事务调用Session、Transaction……) 实际导致的后果是:当持久层技术改变的时,事务控制代码必须修改! ——因此当持久层技术发生变化时,Service组件的事务控制代码必须随之修改。 Spring事务的特征: Spring的事务管理不需要与任何特定的事务API耦合(面向PlatformTransactionManager)。 对不同的持久层访问技术,编程式事务提供一致的事务管理,通过模块化的一致性操作管理事务。 优势: 不需要硬编码的方式写事务 让业务逻辑API与具体的事务逻辑分离 没有提供实际的事务支持,只是对底层的事务包装,所以能自由切换底层的事务 Spring的事务编程接口:PlatformTransactionManager —— 策略接口。 JDBC、Mybatis持久化技术: DataSourceTransactionManager Hibenernate技术: HibernateTransactionManager Jpa技术: JpaTransactionManager …… ApplicationContext - 将会负责为程序选择合适的事务策略实现类。 程序员在Spring配置文件中怎么指定事务策略实现类,Spring就用哪个类。 Spring的事务管理策略: 1. 编程式事务。 需要程序员自己写代码。 面向PlatformTransactionManager事务策略接口编程。 2. 声明式事务。程序员无需写Java代码,直接配置即可。 /************************************************ stratagy模式(策略模式) 当程序要做某件事情(事务控制)时,而完成这个事情有多种实现算法(不同持久化技术,事务控制代码不同)。 策略模式应该提供一个策略接口,但该接口只封装完成该时事情必须的方法,并不提供实现。 并为每个算法都提供一个对应的策略实现类。 程序面向接口编程,这样程序即可在任何实现算法之间自由切换,不需要修改代码。 策略模式还需要一个Context类,该Context将会为程序“智能”选择策略实现类。 *************************************************/ ========================SpringEL与AOP编程=========================== //直接量表达式 // String str = "5+10/3-6"; //创建数组 // String str = "new int[6]"; //创建List // String str = "{'java','swift','object-c'}"; //调用方法,要用T运算符,这个运算符用于告诉Spring将该运算符内的字符串当成“类”来处理(避免对其进行其他的解析) // String str = "T(java.lang.Math).pow(4, 3)"; //调用构造器 String str = "new javax.swing.JFrame('我的窗口')"; //1.创建ExpressionParser表达式解析器 ExpressionParser parser = new SpelExpressionParser(); //2.使用解析器去解析字符串,返回Expression表达式对象 Expression expr = parser.parseExpression(str); //3.调用Expression表达式对象的getValue()方法得到表达式的值。 System.out.println(expr.getValue()); 有变量的情况: //1.使用变量,获取user.getDog().getName() //String str = "#user.dog.name"; //?代表所谓安全导航,用于指定当user、dog为null时,程序不再报错,而是直接返回null //String str = "#user?.dog?.name";测试可将context.setVariable("user", new User());注释掉 //2.使用变量,获取Context中的Root对象的getGunDog().getName() //当表达式语言没有明确指定访问哪个对象的属性时,程序自动改为访问根对象 String str = "dog.name"; //1.创建ExpressionParser表达式解析器 ExpressionParser parser = new SpelExpressionParser(); //2.使用解析器去解析字符串,返回Expression表达式对象 Expression expr = parser.parseExpression(str); //3.调用Expression表达式对象的getValue()方法得到表达式的值。 //如果表达式中涉及变量,程序就需要EvaluationContext,该Context就像Struts2的StackContext StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("user", new User()); //StandardEvaluationContext才有设置root对象的方法 context.setRootObject(new User()); System.out.println(expr.getValue(context));//到context中去解析变量 /** * 集合选择和集合投影 */ public static void main(String[] args) { //集合选择 ?[length()> 5] 代表只保留集合中长度大于5的集合元素 //String str = "#list.?[length()> 5]"; //集合投影 ![length()]代表对集合中的每个集合元素都指定length(),将返回值收集成新的集合 String str = "#list.![length()]"; String listStr = "{'java','swift','objective-c','android'}"; //1.创建ExpressionParser表达式解析器 ExpressionParser parser = new SpelExpressionParser(); //2.使用解析器去解析字符串,返回Expression表达式对象 Expression expr = parser.parseExpression(str); Expression listExpr = parser.parseExpression(listStr); //3.调用Expression表达式对象的getValue()方法得到表达式的值。 StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("list", listExpr.getValue()); System.out.println(expr.getValue(context));//到context中去解析变量 } AOP相关概念 切面(Aspect):业务流程运行的某个特定步骤,也就是应用运行过程的关注点,关注点可能横切多个对象,所以常常也称为横切关注点。很像我们的类,包含advice 增强处理(Advice):处理代码 连接点:程序执行过程中一个明确的点(方法执行的中间不是明确的点。方法开始之前就是明确的点,方法调用后也是明确的) 切入点:可以插入增强处理的连接点。 引入:AOP框架为目标类【添加】新方法、字段、接口的过程 织入:其实即是修改目标对象的方法 public aspect AuthAspect{ before():execution(* org.fkjava.spring.service.*.*(..)){ System.out.println("==模拟权限检查=="); } } 说明:这段代码只有AOP框架aspectJ的编译器才能编译,要注意 AOP编程完成,执行的是AOP代理的方法 步骤: 方式一:用注解方式 1.要在类上加注解 @Aspect 2.在方法上加注解 @Before ( @After、 @AfterReturning、 @AfterThrowing和 @Around ) 共5种可选择 方式二:用XML配置方式(要导入aop空间)也是指定类和方法 pointcut-ref="fkPc"/> 为了pointcut可以复用,可以在 事务管理 1.配置事务管理器,在配置文件里 p:sessionFactory-ref="sessionFactory"/> p:transactionManager-ref="transactionManager"/> pointcut-ref="fkPc"/> ========================事务的一些概念======================== 事务就相当于我们做一件事情(这件事由许多小的步骤组成),要么我们就把这件事情完完整整地做成功,要么我们就一点也不要做。不能做到半截撂摊子不干了。 回到Spring来,Spring的Dao框架对事务提供了强大的支持。它包括有两种事物管理,即: 编程式事物管理(programmatic tansaction management) 声明式事物管理(Declarative tansaction management) 所谓编程式事物管理,就是把事物管理以代码的形式编写到你的应用中要使用事物管理的地方,灵活性较强。而声明式事物管理是以配置文件的形式在xml文件中定义,好处是不具有代码***性,当不需要事物管理时,可以直接修改配置文件,而不用修改代码 1. 传播行为(propagation behavior) 参数 含义 PROPAGATION_REQUIRED 如果存在事物的话,就继续这个事物,如果不存在,新建一个事物。 PROPAGATION_SUPPORTS 如果存在事物的话,就继续这个事物,如果不存在,就以非事务的方式进行。 PROPAGATION_MANDATORY 必须在现存事物中执行,否则抛出异常。 PROPAGATION_REQUIRES_NEW 建立一个新事物,如果现存一个事物,则暂停它。 PROPAGATION_NOT_SUPPORTED 不再事务中执行,如果现存事物,则暂停它。 PROPAGATION_NEVER 不再事务中执行,如果现存事物,则抛出异常。 PROPAGATION_NESTED 在一个嵌入的事物中执行,否则同PROPAGATION_REQUIRED 2. 隔离等级(isolation level) 在一个应用应用程序中,可能有多个事务在运行,这时就会产生一些问题。 dirty read 一个事物更新了数据库中的某些数据,另一个事物读取了这些数据,这时前一个事物由于某些原因回滚了,那么第二个事物读取的数据就是“脏数据”。 non-repeatable read 一个事物需要两次查询同一数据,但两次查询中间可能有另外一个事物更改了这个数据,导致前一个事物两次读出的数据不一致。 phantom read 一个事物两次查询同一个表,但两次查询中间可能有另外一个事物又向这个表中插入了一些新数据,导致前一个事物的两次查询不一致。 为了解决以上问题,Spring的事物管理定义了一些隔离级别,所谓“隔离”,即对数据的锁定。 参数 含义 ISOLATION_DEFAULT 使用数据库默认的隔离级别 ISOLATION_READ_UNCOMMITTED 容许事物读取其他并行事物还未提交的数据。这种级别会出现上面三种情况。 ISOLATION_READ_COMMITTED 容许事物读取其他并行事物已经提交(commit)的数据,可防止dirty read ISOLATION_REPEATABLE_READ 这种级别会可以防止上面三种情况发生。 ISOLATION_SERIALIZABLE 使用事物锁,锁定相应数据,可以防止上面三种情况发生,但效率比较低。 上述参数也是在org.springframework.transaction.TransactionDefinition接口中定义的(类型是public static final,值为-1,1,2,4,8)。上述参数中最常用的是ISOLATION_DEFAULT。 3. read only 应用这项属性时,底层的数据库可以对读取进行最优化,但要配合PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW或PROPAGATION_NESTED使用,即只能在事物中使用。 4. timeout 在多事物并行情况下,为了保证正确性,有些事物的操作会有延迟,甚至死锁。设置事物超时时间,可以避免事物的长时间等待。设置事物超时时间也要配合PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW或PROPAGATION_NESTED使用。 以上的四种属性及其相应方法都定义在org.springframework.transaction.TransactionDefinition接口及其实现类(如org.springframework.transaction.support.DefaultTransactionDefinition)里。