Spring实战
第二章 装配bean
xml配置bean:
Spring配置文件的根元素是来源于Spring beans命名空间所定义的<beans>元素.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmls="http://www.****** .xsd">
<!-- bean declarations go here -->
</beans>
在<beans>中包括<bean>元素,他不是唯一的, 还有其他的一些命名空间.Spring的核心框架自带了10个命名空间配置如下:
1.aop : 声明切面
2.beans : 声明bean和装配bean
3.context : 为配置Spring应用上下文提供了配置元素, 包括自动检测和自动装配bean, 注入非Spring直接管理的对象
4.jee : 提供了与Java EE API的集成,例如JNDI和EJB
5.jms : 为声明消息驱动的POJO提供了配置元素
6.lang : 支持配置有Groovy,JRuby 或BeanShell 等脚本实现的Bean
7.mvc : 启动Spring MVC的能力,例如面向注解的控制器,视图控制器和拦截器
8.oxm : 支持Spring的对象到XML映射配置
9.tx : 提供声明式事务配置
10.util : 提供各种工具类元素,包括把集合配置为Bean,支持属性占位符元素
声明一个简单的bean:
<bean id = "duke" class="*****.Juggler" />
Spring容器加载bean时这行代码 等价于 new Juggler();
可以使用Spring上下文获取到这个对象:(就是问Spring容器获取想要的对象)
ApplicationContext ctx = new ClassPathXmlApplicationContext("sping-idol.xml");
Performer performer = (Performer) ctx.getBean("duke"); //把bean对象赋值给了对象的接口,实现面向接口编程
performer.perform();
2.1.3 通过构造器注入
如果一个java 对象有参数的构造器, 那么可以在配置中对该对象注入参数来构造;例子如下
<bean id="snonet29" class=.../>
<bean id="duke" class=".Juggler">
<constructor-arg value="15"/> //对象要有一个接受参数的构造器,属性value可以传入基本类型的值,
<constructor-arg ref="snonet29"/> //可以使用属性ref输入引用类型(如另一个bean)
</bean>
bean对象duke 等价于 new Juggler(15,snonet29);
通过工厂方法创建Bean:
Spring支持通过<bean>元素的factory-method属性来装配工厂创建的bean(该bean是设计模式中的单例)
Stage 单列类:
public class Stage{
private Stage(){}; //私有构造器
private static class StageSingletonHolder{ //内部类: 延迟加载实例
static Stage instace = new Stage();
}
public static Stage getInstance(){ //返回实例
return StageSingletonHolder.instance;
}
}
对该单例(工厂类)的bean配置
<bean id="theStage" class="Stage" factory-method="getInstance" /> // 等于是调用这个工厂方法
2.1.4 Bean的作用域
Spring Bean 默认是单例(从Spring容器中获取的对象都是同一个对象),可以通过<bean>的属性scope来配置作用域,如下:
<bean id="ticket" class="..." scope="prototype" /> //prototype作用域的bean, 从容器中获取的时候,容器都会新new一个对象返回
作用域的类型有一下多个:
1. singleton : 单例,默认
2. prototype : 允许bean的定义可以被实例化任意次(每调用一次都创建一个实例)
3. request : 在一次HTTP请求中,每个Bean 定义对应一个实例,(请求到响应完成就没了) 该作用域给予web的spring上下文才有效(如Spring MVC)
4. session : 在一个HTTP Session中, 每个Bean定义对应一个实例,(session没有过期就一直在?) , 作用域和上面的一样
5. global-seeion : 在一个全局HTTP Session中,每个bean定义对应一个实列, 该作用域仅在Portlet上下文中才有效
小结:
Spring 的单例的概念限于Sring上下文的范围内, 你可以在自己的代码中 自己实例化对象多次(自己new)
也可以同一个类 配置多个<bean>
2.1.5 初始化 和销毁Bean
在定义<bean>时,可以通过该元素的属性init-method和destroy-method,来指定初始化后销毁时要调用对象的哪2个方法,属性的值是对象的方法名;配置如下:
<bean id="foo" class="..." init-method="方法名1" destory-method="方法名2" />
也可以在<beans> 元素上定义default-init-method和default-destory-method2个属性 , 这样之后, 它的子元素<bean>只要有对于的方面名就会对应的被调用
也就是定义全局的初始化和销毁的方法.
还有中等级的方式: 实现Spring的2个接口InitializingBean和BisposableBean,然后实现他们的方法也能实现对象的初始化方法和销毁方法,不推荐
2.2 注入Bean属性
java的Bean属性就是getter,setter的方法;
Spring理由Setter方法给bean 的实例域(成员变量)赋值
使用的<bean>的子元素<property>,例子如下:
<bean id="..." class="...">
<property name="song" value="abc.."/> //对应类中的setSong()
<property name="Boo" ref="foo"/>
</bean>
子元素<property>和构造器的注入元素<constructor-arg> 类似,使用value属性可以赋值基本类型, 使用ref属性赋值一个对象
注入内部Bean(不推荐),例子演示如下:
<property name="Boo" > 等价于<property name="Boo" ref="foo"/>
<bean class="foo"/>
</property>
内部bean不需要Id
2.2.3 使用Spring 的命名空间p装配属性
本来的写法:
<bean id="..." class="...">
<property name="song" value="abc.."/> //对应类中的setSong()
<property name="Boo" ref="foo"/>
</bean>
之后的写法:
<bean id="..." class="..."
p:song = "abc.."
p:Boo-ref ="foo"/>
变简洁了, 要输入对象的花, 需要-ref 这个后缀
2.2.4 装配集合
Spring集合元素如下:
1.<list> : 装配list类型的值, (如果bean的成员变量是Collection的都可以用该元素注入值);例子如下:
<bean ... >
<property name="foo">
<list>
<ref bean="beanid" />
...
</list>
</property>
</bean>
2.<set> : 和<list>类似,就是不允许重复出现;set就是和list例子和上面基本一样,把<list>替换为<set>
3.<map> : 对应java Map,(如果bean的成员变量是Map的都可以用该元素注入值);
<property name="...">
<map>
<entry key=",.." value-ref="beanID"/> //<entry>属性还有key-ref:应用类型; 还有value:可赋值基本类型
...
</map>
4.<props> : 对应 java properties类型, 和map 不同的是,k-v 都是String
<property name="...">
<props>
<prop key="foo" > 值...</prop>
</props>
</property>
2.2.5装配空值
如果是自动装配,可以显示的给对应属性赋null ,使用 <null/>;例子如下;
<property name="foo">
<null/>
</preperty>
显示的为属性装配null, 的另一个理由是覆盖自动装配的值
2.3 表达式装配
语法是#{ ... } ;有一下功能
1.在大括号中可以运算.
2.可以像ref属性那样用,装配一个bean
3.可以在表达式中调用bean对象的方法;如下:(就是可以调用java代码中能调用的方法,在表达式中也能)
<property name="..." value="#{foo.sayhello()}" />
4.能调用bean对象的属性(getter方法),
<property name="..." value="#{foo.song}"/> ==foo.getsong();
5.非空判断:#{foo.song?.toUpperCase()} //song属性返回非空就调用字符串大写转换方法
语法T();
可以使用java类的,一些java代码的操作,(前面讲都是针对spring中配置了的bean对象)功能如下:
1.可以用来获取对象(如工厂方法返回的对象,可以调用java对象的方法),
他的特色的可以访问类的静态方法和常量;如下:
<property name="foo" value="#{T(java.lang.Math).PI}" //访问类的常量
2.3.2 操作符
算术运算:
+(加运算,和字符串拼接)
- * ? %
^(乘方运算)
关系运算:
<(lt, 大于号,小于号在xml中属于特色符号,所以提供了文本型符号)
>(gt)
==(eq,相等运算)
<=(le)
>=(ge)
逻辑运算:
and
or
not(!)
条件运算:
判断 ? true : false //三元运算,如下2个例子:
1.value="#{songSelecter.selectSong()== 'Jingle Bells' ? piano:saxophone}" //判断语句,2中情况下装配不同的对象
2.value="#{kenny.song != null ? keng.song : 'Greensleeves'}" //非空判断,不为空时使用默认值,
?:(Elvis) //三元运算的简化版 , 如下
上面例子2的等价版本
value="#{kenny.song != null ?:'Greensleeves'}"
正则表达式: matches; 例子如下:
<property name="validEmail"
value="#{admin.email matches '[a-zA-z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}" />
2.3.3在表达式中筛选集合
1.使用spring标签<util:list> 就等于创建了一个List对象的bean:
<util:list id="cities">
<bean class=... p:name="..." p:state=".." /> //使用p:给属性赋值<property>元素的等价方式
.
.
.
</util:list>
访问集合成员
<property name="foo" value="#{cities[2]}"/> //使用中括号访问集合中第三个元素
[] 中括号可以访问集合,使用集合下标
可以方法Map,在中括号中传['key']
可以访问字符串某一个字符
可以访问properties对象中的配置信息, bean['key']
2.使用<util:properties>元素; 使用该标签其实就是创建了一个java.util.Properties类的bean;配置Properties的方式如下
<util:properties id="settings" location="classpath:settings.properties" /> //获取类路径上的这个文件
使用properties的bean中的配置数据了
<property name="accessToken" value="#{settings['twitter.accessToken']}" />
在Spring 中自带了2个properties的bean:
1.systemEnvironment : 包含了操作系统中的所以环境变量
2.systemProperties : 包含了启动该java程序时所设置的所以属性(通常使用-D参数)
例子:
@Value("#{ systemProperties['user.language'] }")
@Value("#{ systemEnvironment['path'] }")
查询集合成员
1) 使用.?[] 查询运算符,例子如下:
<property name="bigCities" value="#{citites.?[population gt 100000]}" />
查询运算符会创建一个新的集合,新集合中存放着符合中括号内的表达式成员
2) 使用.^[] 查询集合中第一个匹配项
3) 使用.$[] 集合中最后一个匹配项
投影集合
一个集合中的元素是对象的话, 可以使用投影集合的语法 ".![属性名]" 来获取集合元素的某个属性的值并组合成一个新的集合返回
<property name="cityNames" values="#{cities.![name]}" /> //返回一个字符串集合(城市名字)
还能拼接属性名
<property name="cityNames" values="#{cities.![name +', '+ state]}" />
和查询集合的语法一起使用
<property name="cityNames" values="#{cities.?[population gt 100000].![name +', '+ state]}" /> //因为查询集合返回的是集合,所以这样使用没问题
第三章 最小化Spring XML配置
3.1.1 4种类型的自动装配
1.byName : 把与Bean的属性具有相同名字(或者ID)的其他Bean自动装配到Bean的对应属性中,如果没有匹配则该属性不进行装配
手动配置:
<bean id="kenny2" class="...Instrumentalist">
<property name="instrument" ref="saxophone"/> //引用另一个bean
</bean>
使用byName自动配置:
<bean id="kenny" class="...Instrumentalist" autowire="byName" />
//该bean的instrument属性会去找一个名字为instrument的bean来配置,
他有一个限制,如果有多个这样的bean子类, 都要装配该属性,而名字属性名使用byName,那么子类的bean 都使用了同一个对象
2.byType : 把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中,如果没有,不装配
和byName有类似,就是范围变大了,如果匹配到了多个同类型bean,会报错,
Spring 有2中解决冲突方式,一直是设置bean的primay属性为true(其实是默认值,所以非首选的bean要设置为false)
摆出某个bean的方式, 使用autowirte-candidate="false",有了这个配置,该bean将不是byType匹配到的候选bean了
3.constructor : 把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean构造器的对应入参中
可是移除构造器参数配置元素<constructor-arg> 使用<bean>的autowire属性的constructor来自动装配
他的匹配范围和byType一样,匹配该构造参数的同类型的bean,所以和byType一样
4.autodetect : 首先用constructor进行自动装配,如失败用byType
最佳自动装配, 如果有构造器就会使用autodetect来创建bean, 没的话会使用byType对bean属性自动注入
<bean id="foo" class="..." autowire="autodetect">
3.1.2 默认自动装配
可以在<beans>上添加属性autowrite属性,来设置全局的自动装配,beans属性的defaule-autowire的值是none不使用自动装配(除非bean自己配置了autowire)
3.1.3 混合使用自动装配和显示装配
对一个并可能已经配置了自动装配,其实还是能显示的给他的属性或构造器手动的配置, 显示的装配比自动装配的优先级高,典型的应用是对某一个属性配置成<null/>
<bean id="kenny" class=... autowire="byType"> <!-- 虽然这里声明了自动装配,但下面还是手动装配了2个属性-->
<property name="foo" value="..."/>
<property name="foo2" > <null/> </property>
</bean>
3.2 使用注解装配
在配置文件的<beans>中配置<context:annotation-config /> 可以开启基于注解的自动装配, 现在可以对代码添加注解, 标识Spring 应该为属性,方法和构造器进行自动装配
这里的注解装配的,还没有讲到使用注解配置bean,所以目前bean还是要在xml中配置后, 在该bean的java代码中使用注解给他的属性,构造器,方法装配
Spring3 支持几种不同的用于自动装配的注解:
1.@Atutowired : Spring自带
2.@Inject : JSR-330(JAVA依赖注入的标准)
3.@Resource : JSR-250
3.2.1 使用@Autowired
@Autowired
public void setfoo(Boo b){ ...}
现在该类在xml中的配置成bean时不需要使用<property>属性了,spring发现bean有@Autowired后会给bean执行byType来自动装配
@Autowired特别之处是不但可以给setter(属性)来配置, 还可以给普通方法配置自动装配bean
@Autowired还能给构造器配置
@Autowired 还能给成员变量使用, 这样就可以省略set方法,它不受限成员变量是不是private
注意:
因为它执行的是byType的自动装配,所以匹配到多个bean,或没有匹配到会抛NoSuchBeanDefinitionException异常
可选的自动装配
默认情况@Autowired没有匹配到bean会抛异常, 但有时对装配没有成功也不在乎,可以使用它的属性required=false; 这样没有匹配到bean,那么该属性就是null
@Autowired(required=false) //此时如果装配失败不会抛异常了, 只是该字段的之未NULL了.
private Instrument instrument;
限定歧义性的依赖(限定器的使用, 就是指定某个bean注入)
方法1: 配合@Qualifier("bean Id") 可以告诉自动装配,使用对应的bean来装配,参数是bean的id
@Aurowried
@Qualifier("foo") //直接限定了beand的id
private Insterument instrument;
方法2: 在xml配置的时候可以不给<bean>设置id, 直接给一个<qualifier>的子元素,也能在@Qualifier() 中获取<qualifier>的子元素对应的bean,例子如下:
<bean class="com.juvenxu.mvnbook.account.FooAutoWire" >
<qualifier value="mybean3"></qualifier>
</bean>
方法3: 在类名上面声明@Qualifier("id") 和方法2是等价的
@Qualifier("mybean3")
public class FooAutoWire implements InterfacesFoo {
}
方法4: 看着比较复杂其实也不复杂就3步,在某些情况下(自己封装自己的框架时,用起来很方便)
1.先创建一个自己的注解,如下:
@Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface myAnnotation {
}
2. 接着给类注解:
@myAnnotation //@Qualifier("mybean3")使用自定义的限定器注解
public class FooAutoWire implements InterfacesFoo {
3. 使用限定器
@Autowired
@myAnnotation //使用自定义的限定器来注入bean, 等价的@Qualifier("mybean3")
private InterfacesFoo foo;
3.2.2 java自带的自动注入注解@Inject(在javax.inject包中, 有了spring的@Autowrie,其实没有毕业使用java的了)
@Inject 其实和Spring的@Autowrie等价
对于的限定器是@Named("id") 等价于Spring的@Qualifier("id")
用法都类似, 唯一不同的是使用自定义的限定器的时候可以使用javax.inject.Qualifier如下:
@Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier //(这个是javax.inject.Qualifier包中的,不和Spring的了)
public @interface myAnnotation {
}
可以看到其实和Sping的对应上,都差不多, java的@Qualifier只是用来创建自定义的限定器, 而不像Spring那样可以直接使用
3.2.3 在注解注入中使用表达式
在类的成员变量中,如果是String 类型或者是基本类型, 那么可以使用@Value("val") ;给成员变量注入值
主要是用它可以配合Spring的表达式SpEL一起使用,例子如下
@Value("#{systemEnvironment['PATH']}") //这个Properties对象是Spring 定义的.
private String path; //注入Properties对象中的属性
Spring表达式在第二章中已经讲过不少了,这里不重复了
3.3 自动检测Bean
上一节中,讲到使用<context:annotation-config> ; 先消灭<bean>的子元素<property>和<constructor-arg>,在类中使用Spring注解来替换,这xml配置
但使用要在xml中定义<bean> 元素
这一节要使用<context:component-scan> ,让Spring(基于注解)自动检测Bean和定义Bean,这样就消除<bean>元素,在xml中的配置了. 配置代码如下:
<context:component-scan base-package="ren.tenie" ></context:component-scan>
base-packages属性会去扫描其指定的包及其所有子包中使用注解定义为bean的类, 把他们自动注册为Spring Bean的类
3.3.1 为自动检测标注Bean(注册为Spring bean的注解)
1. @Component : 通用的构造型注解,标识该类为Spring组件
2. @Controller : 标识将该类定义为Spring MVC controller
3. @Repository : 标识将该类定义为数据仓库
4. @Service : 标识将该类定义为服务
例子如下:
@Component("fooid") //和在xml配置<bean id="fooid" class="..."> 等价的
public class foo{
}
使用这个注解注册bean,碰到一个坑,高版本的JDK,无法兼容低版本的Spring,Spring在底层对编译的字节码做了修改.所以使用Spring还要关注下jdk版本的兼容性
3.3.2 过滤组件扫描
可以使用<context:component-scan>的2个子元素,来随意调整扫描行为
1.<context:include-filter> : 如果使用第三方的jar, 不可能对他们的类去使用注解,所以用这个元素可以告诉Sping包中的哪个类型自动注册为Spring bean,例子如下:
<context:component-scan base-package="....">
<context:include-filter type="assignable" expression="ren.tenie.foo"/>
</context:component-scan>
2.<context:exclude-filter> : 这个是和上面的相对的
属性介绍:
1.type : 定义过滤类型;有5种
1.annotation : 过滤器扫描使用指定注解所标注的那些类,expression来指定注解的类(就是扫描类上面的注解,那些类)
2.assignable : 过滤器扫描类的类型, expression来指定类型(类型相同的类)
3.aspectj : 过滤器扫描与expression指定的AspectJ表达式所匹配的那些类(可以使用表达式来,获取类了)
4.custom : 使用自定义的org.springframework.core.type.TypeFilter实现类,该类有expression指定
5.regex : 过滤器扫描 类的名称 与expression中所指定的正则表达式所配备的类
2.expression : 他的值根据type定义的不同过滤器,来决定的
过滤器,感觉没必要用,第三方的类,可以手动使用xml来配置bean更清晰
3.4 使用Spring基于Java的配置
就是bean的定义在java类中定义了,好处就是可以有java的语法检查,其实也没什么难度,也不比xml和注解的方式,简单,目前注解的代码量最少
还有的好处是,如果类改名了(还要该xml可能会忘记), 要是在xml中的ID 重名了, 都可能难以检查
3.4.1 创建基于Java的配置
和使用注解扫描需要注册的bean 一样,它依赖与<context:component-scan base-package="..."/>元素,
配置类使用注解@Configuration来告诉Sping这是一个bean的注册类
3.4.2 定义一个配置类 和声明一个bean
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringIocBeanConfig{
@Bean //等价与<bean id="mybeanid" class="...FOO";
public Foo mybeanid(){ //方法名就是bean id
return new Foo();
}
}
3.4.4 使用Spring 的基于Java的配置进行注入
1.最简单的例子,//字符和基本类型的注入
@Bean
public Foo f1(){
Foo t = new Foo();
t.setSong("...");
return t;
}
2. 构造器注入, 引用另一个bean
@bean
private Boo bean2(){
new Boo();
}
@Bean
public Foo f1(){
return new Foo(bean2()); //传了方法给构造器,这个方法能生成一个对象
}
第四章 面向切面的Spring
4.1 什么是面向切面编程
一般的业务代码都需要用到安全认证,事务,日志,等一些通用的功能, 以前可以使用继承或代理, 但都会让业务代码变得复杂,而使用了AOP,就可以吧这些通用的代码集中在一起,
而业务代码就只要专注于业务逻辑.通过声明的方式定义这些通用功能以何种方式在何处(那个业务上)应用, 而无需修改业务代码.
这样通用的模块化的特殊类 被称为切面; 而那些原本要关注的安全点,等关注点不比分散在业务类的代码中了.业务模块的代码更简洁,而那些关注点的代码全部转移到切面中
4.1.1 定义AOP术语
AOP有自己的术语,这些术语过于专有不只管,学会走路之前要,学会说话一样, 我们要对这些术语名称掌握
1.通知(advice) : 定义了切面是什么时候使用?描述切面要完成的工作?何时执行这个工作,
它该应用于某个方法被调用之前?之后?之前和之后?还是只在方法抛出异常时? Spring切面的5种类型的通知,如下:
1.Before : 在方法被调用之前调用通知
2.After : 在方法完成之后调用通知,无论方法执行是否成功
3.After-returning : 在方法执行成功之后调用通知
4.After-throwing : 在方法抛出异常后调用通知
5.Around : 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为,(应用场景有:计算我们的方法执行时间)
2.切点(pointcut) : 通知它定义了什么工作和什么时候, 切点是什么地方调用切面
3.连接点(join point) :
4.切面(Aspect) : 通知和切点的结合
5.引入(Introduction) : 允许给现有的类添加新方法和新属性
6.织如(weaving) : 就是什么时候把我们 切面类的代码加入到运行时的对象中,
Spring用的是类的代理,其他一些框架可以做到编译器,和类加载期,运行期