1、spring简介
1.1 spring是什么(1)?
- 是一个开源框架
- 为了简化企业应用开发而生,使用Spring可以使简单的JavaBean实现以前只有EJB才能实现的功能
- 是一个IOC和AOP的容器框架
1.2 spring是什么(2)?
- 轻量级:Spring是非入侵性的-基于Spring开发的应用中对象可以不依赖Spring的API
- 依赖注入
- 面向切面编程
- 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
- 框架:Spring实现了使用简单的组件配置组合成一个复杂的应用,在Spring中可以使用XML和Java注解组合这些对象
- 一站式:在IOC和AOP的基础山峰可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring自身也提供了展现层的SpringMVC和持久层的Spring JDBC)
1.3 spring模块
1.4 spring-HelloWorld
- Java bean
class HelloWorld {
private String name;
public void setName2(String name) {
this.name = name
}
public String show(){
System.out.printIn("hello : " + name )
}
}
- 配置bean (applicationContext.xml)
<bean id="helloworld" class="com.ums.HelloWorld">
<property name="name2" value="spring" />
</bean>
- 加载bean
// 1.创建Spring的IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2.创建Spring的IOC容器对象
HelloWorld helloWorld = (HelloWorld)ctx.getBean("helloWorld");
// 3.调用show方法
helloWorld.show();
- 需要的包
commons-logging
spring-beans
spring-context
spring-core
spring-expression
2、Spring中bean配置
2.1 IOC&DI概述
2.1.1 IOC
- 其思想是 反转资源获取方向,传统的资源查找方式要求组件向容器发起请求查找资源。作为回应,容器适时的返回资源,
- 应用了IOC之后,则是容器主动的将资源推送给他所管理的组件,组件所要做的仅是选择一种合适的方式来接受资源,这种行为也被称为 查找的被动形式
2.1.2 IOC发展
- 分离接口与实现
- 采用工厂模式
- 反转控制
2.1.3 DI
- IOC的另外一种表述方式:即组件以一些预先定义好的方式接受来自于如容器的资源的注入
2.2 配置bean
2.2.1 依赖注入方式
- Spring支持三种依赖注入的方式
- 属性注入
属性注入即通过setter方法注入bean的属性值或依赖的对象
属性注入使用property元素,使用name属性指定bean属性名称,value属性指定属性值
属性注入是实际中最常用的方法
- 构造器注入
通过构造方法注入Bean的属性值或依赖的对象,他保证了Bean实例在实例化之后就可以使用
构造器注入在constructor-arg元素中声明属性,constructor-arg中没有name属性
<bean id="car" class="com.ums.Car">
<constructor-arg value="Audi" index="3" type="String"/>
<constructor-arg value="xxx" />
<constructor-arg value="xxxxxx" />
</bean>
使用构造器注入属性值可以指定参数的位置和参数的类型,以区分重载的构造器
- 工厂方法注入
2.2.2 XML配置里的Bean自动装配
- Spring IOC容器可以自动装配Bean,需要做的仅仅是在bean的autowire属性中指定自动装配的模式
- byType(根据类型自动装配):若IOC容器中有多个与目标Bean类型一致的Bean,在这种情况下,Spring无法判断哪个Bean最合适该属性,所以不能执行自动装配
- byName(根据名称自动装配):必须将目标bean的名称和属性名设置的完全相同
- constructor(通过构造器装配):当bean中存在多个构造器时,此种自动装配方式将会很复杂,不推荐使用
<bean id="address" class="com.ums.Address" p:city="BeiJing" p:street="265" />
<bean id="car" class="com.ums.Car" p:brand="Audi" p:price="300000" />
<!-- 方式1 -->
<bean id="person" class="com.ums.Person" p:name="Tom" p:address-ref="address" p:car-ref="car" />
<!-- 方式2 自动装配 -->
<bean id="person" class="com.ums.Person" p:name="Tom" autowire="byName" />
- 在bean配置文件里设置autowire属性进行自动装配会装配所有属性,然而若只希望装配个别属性时,autowire属性就不够灵活
- autowire属性要嘛根据类型自动装配,要嘛根据名称自动装配,不能两者兼而有之。
- 一般情况下,实际项目中很少使用自动装配功能
2.2.3 通过调用实例工厂方法创建bean
- 实例工厂方法:将对象的创建过程封装到另外一个对象实例的方法里,当客户端需要请求对象时,只需要简单的调用该实例方法而不需要关心对象的创建细节
- 要声明通过实例工厂方法创建的bean
- 在bean的factory-bean属性里指定拥有该工厂方法的bean
- 在factory-method属性里指定该工厂方法的名称
- 使用constructor-arg元素为工厂方法传递方法参数
/**
* 实例工厂方法:实例工厂方法,即需要创建工厂本身,再调用工厂的实例方法来返回bean的实例
*/
public class InstanceCarFactory{
private Map<String, Car> cars = null;
public InstanceCarFactory(){
cars = new HashMap<String, Car>();
cars.put("audi", new Car("audi", 2000));
cars.put("ford", new Car("ford", 2000));
}
public Car getCar(String brand) {
return cars.get(brand);
}
}
<!-- 配置工厂实例 -->
<bean id="carFactory" class="com.ums.InstanceCarFactory" />
<!-- 通过实例工厂方法来配置bean -->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="ford" />
</bean>
2.2.4 通过静态工厂方法创建bean
- 调用静态工厂方法创建bean是将对象创建的过程封装到静态方法中,当客户端需要对象时,只需要简单的调用静态方法,而不关心创建对象的细节
- 要声明通过静态方法创建的bean,需要在bean的class属性里指定拥有该工厂的方法的类,同时在factory-method属性里指定工厂方法的名称,最后,使用constrctor-arg 元素为该方法传递方法参数
public class StaticCarFactory{
private static Map<String, Car> cars = new HashMap<String, Car>();
static {
cars.put("audi", new Car("audi",20000));
cars.put("ford", new Car("ford",20000));
}
public static Car getCar(String name) {
return cars.get(name);
}
}
<bean id="car1" class="com.ums.StaticFactory" factory-method="getCar" >
<constructor-arg value="audi" />
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-factory.xml") ;
CAr car1 = (Car)ctx.getBean("car1");
System.out.println(car1)
2.2.4 FactoryBean(Spring提供)
/**
* 自定义的FactoryBean 需要实现FactoryBean 接口
*/
public class CarFactoryBean implements FactoryBean<Car> {
private String brand;
public void setBrand(String brand) {
this.brand = brand;
}
// 返回bean的对象
@Override
public Car getObject() throws Exception {
return new Car(brand, 50000);
}
// 返回bean的类型
@Override
public Class<?> getObjectType() {
return Car.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
<!--
通过FactoryBean 来配置 Bean的实例
class:指向FactoryBean的全类名
property:配置FactoryBean的属性
但实际返回的实例却是 FactoryBean的getObject()方法返回的实例
-->
<bean id="car" class="com.ums.CarFactoryBean">
<property name="brand" value="BMW" />
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("xxx-Factory.xml");
Car car = (Car) ctx.getBean("car");
2.3 注入属性值细节
2.3.1 字面值
- 可以使用字符串表示的值,可以使用value元素标签或者value属性进行注入
- 基本数据类型及其封装类、String等类型都可以采取字面注入的方式
- 若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来
2.3.2 引用其他的bean
- 组成应用程序的bean经常需要相互协作用来完成应用程序的功能,要使Bean能够相互访问,就必须在Bean配置文件中指定对于Bean的引用
- 在Bean的配置文件中,可以通过ref元素或ref属性为bean的属性或者构造器参数指定对Bean的引用
- 也可以在属性或者构造器里面包含Bean的声明,这样的Bean称为内部bean
<bean id="helloworld1" class="com.ums.HelloWorld">
<property name="name2" value="spring" />
</bean>
<bean id="car" class="com.ums.Car">
<property name="name" value="spring" />
<property name="helloWorld" ref="helloworld1" />
</bean>
<property name="helloWorld" ref="helloworld1" />
<property name="helloWorld" >
<ref bean="helloWorld1"/>
</property>
2.3.3 内部bean
// 内部Bean
<property name="car" >
<bean class="xxx">
<constructor-arg value="xx"
</bean>
</property>
// 构造器-内部bean
<bean id="car" class="com.ums.Car">
<constructor-arg value="Audi" index="3" type="String"/>
<constructor-arg value="xxx" />
<constructor-arg ref="car2" />
</bean>
2.3.4 null值和级联属性
- 可以使用专用的null元素标签为Bean的字符串或者其他对象类型的属性注入null值
- 和Struts、Hiberante等框架一样,spring支持级联属性的配置
// null
<bean id="car" class="com.ums.Car">
<constructor-arg><null/></constructor-arg>
</bean>
// 级联属性赋值
<bean id="car" class="com.ums.Car">
<constructor-arg ref="car2" />
<property name="car2.price" value="3" />
</bean>
2.3.5 集合属性
- 在Spring中可以通过一组内置的xml标签( list 、set、 map)来配置集合属性
- 配置Java.util.List类型的属性,需要指定List标签,在标签里包含一些元素,这些标签可以通过value指定简单的常量值,通过ref指定对其他Bean的引用,通过bean指定内置Bean定义,通过null指定空元素,甚至可以内嵌其他集合
- 数组的定义和List一样,都使用list
- 配置Java.util.Set需要使用Set标签,定义元素的方法与List一样
- Java.util.Map通过map标签定义,map标签里可以使用多个entry作为子标签,每个条目包含一个键和一个值
- 必须在key标签里定义键
- 因为键和值的类型没有限制,所以可以自由的为他们指定value,ref,bean或null元素
- 可以将Map的键和值作为entry的属性定义:简单常量使用key和value来定义;Bean引用通过key-ref和value-ref属性来定义
- 使用props定义Java.util.Properties,该标签使用多个prop作为子标签,每个prop标签必须定义key属性
- 可以使用util schema里的集合标签定义独立的集合bean
// list
<bean id="car" class="com.ums.Car">
<property name="cars">
<list >
<ref bean="car1" />
<ref bean="car2" />
<ref bean="car3" />
</list>
</property>
</bean>
// map
private Map<String , Map> maps;
<property name="cars">
<map >
<entry key="AA" value-ref="car" />
<entry key="BB" value-ref="car1" />
</map>
</property>
// props
class DataSource {
private Properties properties;
}
<bean id="dataSource" class="com.ums.DataSource">
<property name="properties">
<props >
<prop key="user"> root </prop>
<prop key="password"> 123456 </prop>
<prop key="jdbcUrl"> jdbc:mysql://test </prop>
<prop key="driverClass">com.mysql.jdbc.Driver</prop>
</props>
</property>
</ bean>
// 配置单例的集合bean,以供多个bean进行引用
<util:list id="cars">
<ref bean="car" />
<ref bean="car1" />
</util:list>
<bean id="person" class="com.ums.Person">
<property name="name" value="Jack" />
<property name="age" value="29" />
<property name="cars" value="cars" /> <!-- 引用的是单例的集合bean -->
</bean>
2.3.6 使用p命名空间
- 为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息
- Spring自从2.5版本之后开始引入一个新的p命名空间,可以通过bean元素属性的方式配置Bean的属性
- 使用p命名空间之后,基于XML的配置方式将进一步简化
<bean id="person" class="com.ums.Person" p:age="30" p:name="li" p:cars-ref="cars" />
2.4 bean之间的关系:继承、依赖
2.4.1 继承
- Spring允许继承bean的配置,被继承bean被称为父bean,继承这个父bean的bean称为子bean
- 子bean从父bean中继承配置,包括Bean的属性配置
- 子bean也可以覆盖从父bean继承过来的配置
- 父bean可以作为配置模版,也可以作为Bean的实例。若只是想把父bean作为模版,可以设置bean的abstract属性为true,这样Spring将不会实例化这个bean
- 并不是bean元素里的所有属性都会被继承。比如:autowire,abstract等
- 也可以忽略父bean的class属性,让子bean指定自己的类,而共享相同的属性配置,但此时abstract必须设置为true
<bean id="address" class="com.ums.Address" p:city="BeiJing" p:street="WuDaoKou" />
<bean id="address2" p:city="NanJing" parent="address"/>
<!-- 抽象bean不可以实例化,实例化时会报错-->
<bean id="address" class="com.ums.Address" p:city="BeiJing" p:street="WuDaoKou" abstract="true" />
<!-- 若一个bean的class属性没有指定,则该bean必须是一个抽象bean -->
<bean id="address" p:city="BeiJing" p:street="WuDaoKou" />
2.4.2 依赖
- Spring允许用户通过depends-on属性设定Bean前置依赖的bean,前置依赖的bean会在本bean实例化之前创建好
- 如果前置依赖多个bean,则可以通过逗号,空格或的方式配置bean的名称
<bean id="car" class="com.ums.Car" p:brand="Audi" p:price="300000" />
<!-- 要求再配置person时,必须要有一个关联的car!-->
<bean id="person" class="com.ums.Person" p:name="Tom" p:address-ref="address" depends-on="car" />
2.5 bean的作用域:singleton、prototype、WEB环境作用域
<!--
使用bean的scope属性来配置bean的作用域
singleton:单例的(默认值),容器初始创建bean实例,在整个容器的生命周期内,只创建这一个bean,在创建容器时,这个bean已经被初始化好了
prototype:原形的,容器初始化时不创建bean的实例,而在每次请求时都创建一个新的bean实例,并返回
-->
<bean id="car" class="com.ums.Car" scope="singleton">
<property name="brand" value="Audi" />
<property name="price" value="300000" />
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-Scope.xml");
Car car = (Car) ctx.getBean("car");
Car car1 = (Car) ctx.getBean("car");
System.out.println(car = car1);
// singleton:单例模式下输出(true)
// prototype:输出(false)
2.6 使用外部属性文件(jdbc)
- 在配置文件里配置bean时,有时需要在bean的配置里混入系统部署的细节信息(例如:文件路径,数据源配置信息等),而这些部署细节实际上需要和bean配置相互分离
- Spring提供了一个PropertyPlaceholderConfigurer的BeanFactory后置处理器,这个处理器允许用户将bean配置的部分内容外移到属性文件中,可以在bean配置文件里使用形式为“${var}”的变量,PropertyPlaceholderConfigurer从属性文件里加载属性,并使用这些属性来替换变量
- Spring还允许在属性文件中使用“${propName}”,以实现属性之间的相互引用
<!--
使用c3p0、mysql驱动配置连接数据库:
需要导入c3p0、mysql
-->
<!--
方法1:不使用外部参数文件配置
-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root" />
<property name="password" value="123456" />
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql:///test" />
</bean>
<!--
方法2:使用外部参数文件配置
step1:新建文件 db.properties
user=root
password=123456
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///test
step2:在配置文件中导入外部参数文件
<context:propeety-placeholder location="classpath:db.properties" />
step3:导入属性文件中的参数
-->
<!-- 导入资源文件 -->
<context:propeety-placeholder location="classpath:db.properties" />
<!-- 导入C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${user}" />
<property name="password" value="${password}" />
<property name="driverClass" value="${driverClass}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
</bean>
/**
* 测试是否连接成功
*/
ApplicationContext ctx = new ClassPathXmlApplicationContext(beans-dataSource.xml);
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource.getConnection());
2.7 Spring的表达式:SpEL
Spring表达式语言:是一个支持运行时查询和操作对象图的强大表达式语言
语法类似于EL:#{…}
为bean属性进行动态赋值提供了便利
通过SpEL可以实现:
1.通过bean的id对bean进行引用
2.调用方法及引用对象中的属性
3.计算表达式的值
4.正则表达式匹配
2.7.1 字面量表示:
<property name="count" value="#{5}" />
2.7.2 引用bean、属性和方法
- 引用其他对象
- 引用其他对象的属性
- 调用其他方法,可以链式操作
- 引用bean、属性和方法
- 调用静态方法或静态属性:通过T()调用一个类的静态方法,他将返回一个ClassObject,然后再调用相应的方法或属性
2.7.3 支持运算符
- 算术运算符
- 加号可以作为字符串的连接
- 比较运算符
- 逻辑运算符号:and
- if-else运算符
- if-else的变体
- 正则表达式
2.8 IOC容器中bean的生命周期
2.8.1 概述
- Spring IOC 容器可以管理Bean的生命周期,spring允许在Bean生命周期的特定点去执行定制的任务
- Spring IOC容器对bean的生命周期进行管理的过程:
- 通过构造器或工程方法创建bean实例
- 为bean的属性设置值和对其他bean的引用
- 调用bean的初始化方法
- bean可以使用了
- 当容器关闭时,调用bean的销毁方法
- 在bean的声明里设置init-method和destory-method属性,为bean指定初始化和销毁方法
<bean id="car" class="com.ums.car" init-method="init" destory-method="destroy">
<property name="brand" value="Audi" />
</bean>
2.8.2 配置bean的后置处理器
- Spring IOC容器对bean的生命周期进行管理的过程
- 通过构造器或工厂方法创建bean实例
- 为bean的属性设置值和对其他bean的引用
- 将bean实例化传递给bean后置处理器的postProcessBeforeInitialization方法
- 调用bean的初始化方法
- 将bean实例传递给bean后置处理器的postProcessAfterInitialization方法
- bean可以使用了
- 当容器关闭时,调用bean的销毁方法
- 实现过程
- 实现接口BeanPostProcessor
- 实现postProcessBeforeInitialization方法
- 实现postProcessAfterInitialization方法
- 配置bean的后置处理器
<bean class="com.ums.MyBeanPostProcessor" />
2.9 配置形式
2.9.1 基于XML文件的方式
<bean id="helloworld" class="com.ums.HelloWorld">
<property name="name2" value="spring" />
</bean>
2.9.2 基于注解的方式(基于注解配置bean;基于注解来配置装配bean的属性)
- 在Classpath中扫描组件
- 组件扫描:Spring能够从ClassPath 下自动扫描,侦测和实例化具有特定注解的组件
- 特定的组件包括
@Component:基本注解,标识了一个受Spring管理的组件
@Repository:标识持久层组件
@Service:标识服务层(业务层)组件
@Controller:标识表现层组件
- 对于扫描到的组件,Spring有默认的命名策略
- 配置文件
- 当在组件类上使用了特定的注解之后,还需要在Spring的配置文件中声明 context:component-scan
- base-package 属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包里及其子包中的所有类
- 当需要扫描多个包时,可以用逗号分割
- 如果仅希望扫描特定的类而非基包下的所有类,可以使用 resource-pattern 属性过滤特定的类
<context:component-scan
base-package="com.ums.spring.beans"
resource-pattern="autowire/*.class" />
- context:include-filter 字节点表示要包含的目标类,要使用use-default-filters=“false”
- context:exclude-filter 字节点表示要排除在外的目标类
- context:component-scan 下可以拥有若干个 context:include-filter 和 context:exclude-filter 字节点
- context:include-filter 和 context:exclude-filter 字节点支持多种类型的过滤表达式:annotation、assignable、aspectj、regex、custom
<context:exclude-filter type="annotation" expression="com.ums,Repository" />
- 组件装配
- context:component-scan 元素还会自动注册 AutowiredAnnotationBeanPostProcessor 实例
- 该实例可以自动装配具有 @Autowired和@Resource、@Inject注解的属性
- 没有使用@Controller等注解时,使用 @Autowired(required = false) 进行判断
@Autowired(required = false)
private Test test;
- 若在使用@Controller等 注解,没有指定名称时,可以使用@Qualifier指定要装配哪个bean
@Autowired
@Qualifier("userRepositoryImpl")
private UserRepository userRepository;
2.10 泛型依赖注入
- spring 4.x中可以为子类注入子类对应的泛型类型的成员变量的引用
public class BaseService<T> {
@Autowired
protected BaseRepository<T> repository;
public void add() {
sout("add.....");
sout(repository);
}
}
@Service
public class UserService extends BaseService<User> {
}
@Repository
public class UserRepository extends BaseRepository<User>{
}
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-context.xml");
UserService userService = (UserService)ctx.getBean("userService");
userService.add();
3、Spring容器
3.1 基础
- 在SpringIOC容器读取Bean配置创建Bean实例前,必须对他进行实例化,只有在容器实例化后,才可以从IOC容器里获取Bean实例并使用
- Spring提供了两种类型的IOC容器实现
- BeanFactory:IOC容器的基本实现
- ApplicationContext:提供了更多的高级特性,是BeanFactory的子接口
- BeanFactory是Spring框架的基础设施,面向Spring本身;ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory
- 无论使用何种方式,配置文件都是相同的
3.2 ApplicationContext
3.2.1 ApplicationContext实现类有两个
- ClassPathXmlApplicationContext:从类路径下加载配置文件
- FileSystemXmlApplicationContext:从文件系统中加载配置文件
3.2.2 ConfigurableApplicationContext
- 扩展于ApplicationContext,新增加两个主要方法:refresh() 和 close(),让ApplicationContext具有启动、刷新和关闭上下文能力
- ApplicationContext在初始化上下文时就实例化所有单例的Bean
3.2.3 WebApplicationContext
- 专门为web应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
4、AOP基础
4.1 遇到的问题
- 代码混乱:越来越多的非业务需求(日志和验证等),加入后,原有的业务方法急剧膨胀,每个方法在处理逻辑的同时,还必须兼顾其他多个关注点
- 代码分散:以日志需求为例,只是为了满足这个单一的需求,就不得不在多个模块(方法)中多次重复相同的日志代码。如果日志需求发生变化,必须修改所有的模块
4.2 解决方法(使用动态代理解决上述问题)
- 代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上
public interface ArtithmeticCalculator {
int add(int i; int j);
int sub(int i; int j);
int mul(int i; int j);
int div(int i; int j);
}
@Component
public ArtithmeticCalculatorImpl implement ArtithmeticCalculator{
@Override
public int add(int i, int j) {
return i+j;
}
@Override
public int sub(int i, int j) {
return i-j;
}
@Override
public int mul(int i, int j) {
return i*j;
}
@Override
public int mul(int i, int j) {
return i/j;
}
}
public class ArtithmeticCalculatorLoggingProxy {
// 要代理的对象
private ArtithmeticCalculator target;
public ArtithmeticCalculatorLoggingProxy(ArtithmeticCalculator target) {
this.target = target;
}
public ArtithmeticCalculator getLoggingProxy() {
ArtithmeticCalculator proxy = null;
// 代理对象由哪一个类加载器负责加载
ClassLoader loader = target.getClass().getClassLoader();
// 代理对象的类型,即其中有哪些方法
Class[] interfaces = new Class[]{ArtithmeticCalculator.class};
// 当调用代理对象其中的方法是,执行该代码
InvocationHandler h = new InvocationHandler(){
/**
* proxy : 正在返回的那个代理对象,一般情况下,在 invoke方法中都不使用该对象
* method : 正在被调用的方法
* args : 调用方法时传入的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
String methodName = method.getName();
// 日志
sout("invoke begin ----");
// 执行方法
Object object method.invoke(target, args);
// 日志
sout("invoke end ----");
return result;
}
}
proxy = (ArtithmeticCalculator)Proxy.newProxyInstance(loader, interfaces, h)
return proxy;
}
}
public class Main {
public static void main(String[] args) {
ArtithmeticCalculator ac = new ArtithmeticCalculatorImpl();
ArtithmeticCalculator proxy = new ArtithmeticCalculatorLoggingProxy(ac).getLoggingProxy(target);
int result;
result = proxy.add(1,2);
sout("add result: " + result)
result = proxy.div(4,2);
sout("div result: " + result)
}
}
4.3 AOP简介
- AOP(面向切面编程):是一种新的方法论,是对传统的OOP(面向对象编程)的补充
- AOP的主要编程对象是切面(aspect),而切面模块化横切关注点
- 在应用AOP编程时,仍然需要定义公共的功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类,这样一来横切关注点就被模块化到特殊的对象(切面)中
- AOP的好处:
- 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
- 业务模块更加简洁,只包含核心业务代码
4.4 AOP术语
- 切面(aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
- 通知(advice):切面必须完成的工作
- 目标(target):被通知的对象
- 代理(proxy):向目标对象应用通知后创建的对象
- 连接点(Joinpoint):程序执行的某个特定位置
- 切点(PointCut):每个类都拥有多个连接点
4.5 Spring AOP
- AspectJ:Java社区里最完整最流行的AOP框架
- 在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP
4.6 基于AspectJ注解
4.6.1 在Spring中启用AspectJ注解支持
- 在Spring应用中使用AspectJ注解,必须在classpath中包含AspectJ类库:aopalliance.jar、sapectj.weaver.jar和Spring-aspects.jar
- 将aop Schema添加到beans的根元素中
- 要在 SpringIOC 容器中启用 AspectJ注解支持,只要在 Bean 配置文件中定义一个空的 XML元素 aop:aspectj-autoproxy
- 当 Spring ioc 容器侦测到 Bean配置文件中的 aop:aspectj-autoproxy 元素时,会自动与 AspectJ切面匹配的bean创建代理
4.6.2 用 AspectJ注解声明切面
- 要在Spring中声明 AspectJ切面,只需要在IOC容器中将切面声明为Bean实例,当在Spring IOC容器中初始化 AspectJ 切面后,Spring IOC 容器就会为那些与 AspectJ切面相匹配的Bean创建代理
- 在AspectJ注解中,切面只是一个带有@Aspect注解的Java类
- 通知是 标注有某种注解的简单的Java方法
- AspectJ支持5种类型的通知注解:
- @Before:前置通知,在方法执行之前执行
- @After:后置通知,在方法执行之后执行
- @AfterRunning:返回通知,在方法返回结果之后执行
- @AfterThrowing:异常通知,在方法抛出异常之后
- @Around:环绕通知,围绕着方法执行
4.6.3 让通知访问当前连接点的细节
- 可以在通知方法中声明一个类型为JointPoint的参数,然后就能访问链接细节如方法名称和参数值
4.6.4 后置通知
- 后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候,下面的后置通知记录了方法的终止
- 一个切面可以包括多个通知
4.6.5 返回通知、异常通知、环绕通知
- 返回通知:在方法正常结束返回之后,执行的代码,返回通知是可以访问到方法的返回值的
- 异常通知:在目标出现异常时会执行的代码,可以访问到异常对象;且可以指定再出现特定的异常时再执行通知代码。例如:NullPointerException ex;
- 环绕通知:围着方法执行(相当于动态代理)
4.6.6 指定切面的优先级
- 在同一个连接点上应用不止一个切面时,除非明确指定,否则他们的优先级是不确定的
- 切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定
- 实现 Ordered 接口,getOrder()方法的返回值越小,优先级越高
- 若使用 @Order 注解,序号出现在注解中
@Aspect
@Order(0)
public class CaculatorValidationAspect {
}
@Aspect
@Order(1)
public class CaculatorLoggingAspect {
}
4.6.7 重用切点表达式
/**
* 定义一个方法,用于声明切入点表达式,一般的,该 方法中不再需要添加入其他的代码
* 使用 @Pointcut 来声明切入点表达式
* 后面的其他通知直接引用方法名来引用当前的切入点表达式
*/
@Pointcut("execution(public int com.ums.spring.ArithmeticCaculator.add(int , int))")
public void declareJointPointExpression(){}
@Before("declareJointPointExpression()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
sout("The method begins ---- methodName : " + methodName + " , args : " + args);
}
4.6.6 例子
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.ums.spring" />
<!-- 使 AspectJ 注解起作用:自动为匹配的类生成代理对象 -->
<aop:aspectj-autoproxy />
// 1. 创建Spring的IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("Application.xml");
// 2. 从IOC容器中获取bean的实例
ArithmeticCaculator arithmeticCaculator = ctx.getBean(ArithmeticCaculator.class);
// 3. 使用bean
int result = arithmeticCaculator.add(3,6);
sout("result : " + result);
// 将这个类声明为一个切面:需要把该类放到 IOC 容器中、再声明一个切面
@Aspect
@Component
public class LoggingAspect {
@Before("execution(public int com.ums.spring.ArithmeticCaculator.add(int , int))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
sout("The method begins ---- methodName : " + methodName + " , args : " + args);
}
// 后置通知:在目标方法之后执行(无论是否发生异常),执行的通知
@After("execution(public int com.ums.spring.ArithmeticCaculator.add(int , int))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
sout("The method ends ---- methodName : " + methodName );
}
@AfterRunning("execution(public int com.ums.spring.ArithmeticCaculator.add(int , int))", returning="result")
public void afterRunning(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
sout("The method ends ---- methodName : " + methodName + ", results : " + result );
}
@AfterThrowing("execution(public int com.ums.spring.ArithmeticCaculator.add(int , int))", throwing="ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex){
String methodName = joinPoint.getSignature().getName();
String methodName = joinPoint.getSignature().getName();
sout("The method ends ---- methodName : " + methodName + ", Exception : " + ex );
}
/**
* 环绕通知需要携带ProceedingJoinPoint 类型的参数
* 环绕通知类似于动态代理的全过程
* ProceedingJoinPoint类型参数可以决定是否执行目标方法
* 且环绕通知必须有返回值,类型即为目标方法的返回值
*/
@Around("execution(public int com.ums.spring.ArithmeticCaculator.add(int , int))")
public void aroundMethod(ProceedingJoinPoint pjb){
Object result = null ;
String methodName = pjb.getSignature().getName();
try {
// 前置通知
sout("The method " + methodName + ", begins with " + Arrays.asList(pjb.getArgs()))
// 执行目标方法
result = pjb.proceed();
// 后置通知
sout("The method ends with " + result )
} catch (Exception e){
e.printStackTrace();
}
return result;
}
}
4.7 基于配置文件的方式来配置AOP
4.7.1 引入通知
- 引入通知可以使用两个实现类 MaxCaculatorImpl 和 MinCaculatorImpl,让 AirthmeticCaculatorImpl 动态的实现MaxCaculator 和 MinCaculator 接口,而这与从MaxCaculatorImpl 和 MinCaculatorImpl 中实现多继承的效果相同,但却不需要修改 ArithmeticCaculatorImpl 的源代码
- 引入通知也必须在切面中声明
- 在切面中,通过为任意字段添加 @DeclareParents 注解来引入声明
- 注解类型的value 属性表示哪些类是当前引入通知的目标。value属性值也可以使一个AspectJ类型的表达式,以捋一个即可引入多个类中,defaultImpl属性中指定这个接口使用的实现类
<!-- 配置bean -->
<bean id="arithmeticCaculatorImpl" class="com.ums.ArithmeticCaculatorImpl" />
<!-- 配置切面的bean -->
<bean id="loggingAspect" class="com.ums.LoggingAspect" />
<bean id="vlidationAspect" class="com.ums.VlidationAspect"/>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(public int com.ums.spring.ArithmeticCaculator.*(int , int))" id="pointcut" />
<!-- 配置切面及通知 -->
<aop:aspect ref="loggingAspect" order="2" >
<!-- 前置通知 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut" />
<!-- 后置通知 -->
<aop:after method="afterMethod" pointcut-ref="pointcut" />
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="e" />
<!-- 返回通知 -->
<aop:after-returning method="afterReturningMethod" pointcut-ref="pointcut"/>
<!-- 环绕通知 -->
<aop:around method="aroundMethod" pointcut-ref="pointcut" />
</aop:aspect>
<!-- 配置切面及通知 -->
<aop:aspect ref="vlidationAspect" order="1" >
<!-- 前置通知 -->
<aop:before method="vlidationArgs" pointcut-ref="pointcut" />
</aop:aspect>
</aop:config>
5、Spring对jdbc的支持
5.1 JdbcTemplete简介
- 为了使 JDBC 更加易使用,Spring在JDBC API 上定义了一个抽象层,以此建立一个JDBC 存取框架
- 作为Spring JDBC 框架的核心,JDBC模版的设计目的是为了不同类型的JDBC操作提供模版方法,每个模版方法都能控制整个过程,并允许覆盖过程中的特定任务,通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低
5.2 实例
<!-- 导入资源文件 -->
<context:propeety-placeholder location="classpath:db.properties" />
<!-- 导入C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${user}" />
<property name="password" value="${password}" />
<property name="driverClass" value="${driverClass}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
</bean>
<bean id="jdbcTemplete" class="org.springframework.jdbc.core.JdbcTemplate" >
<property name="dataSource" ref="dataSource" />
</bean>
public class JDBCTest{
private ApplicationContext ctx = null;
private JdbcTemplete jdbcTemplete;
{
ctx = new ClassPathXmlApplicationContext("applicaitonContext.xml");
jdbcTemplete = (JdbcTemplete)ctx.getBean("jdbcTemplete");
}
/**
* 批量更新:批量的insert, update, delete
*/
@Test
public void testBatchUpdate(){
String sql = "INSERT INTO employees (name, age, sex) values(?,?,?)";
List<Object[]> batchArgs = new ArrayList<>();
batchArgs.add(new Object[]{"AA", 11, 1});
batchArgs.add(new Object[]{"BB", 22, 2});
batchArgs.add(new Object[]{"CC", 33, 3});
jdbcTemplete.batchUpdate(sql, batchArgs);
}
/**
* 执行 insert, update, delete
*/
@Test
public void testUpdate(){
String sql = "UPDATE employees set name = ? where id = ? ";
jdbcTemplete.update(sql, "Jack", 5);
}
/**
* 从数据库中获取一条记录:queryForObject
* query一个list:jdbcTemplete.query(sql, rowMapper, 5)
*/
@Test
public void testQueryForObject(){
String sql = "select id, name, sex from employees where id = ? ";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
Employee employee = jdbcTemplete.queryForObject(sql, rowMapper, 1);
sout(employee);
}
/**
* 获取单个值,做统计查询
*/
@Test
public void querySingle(){
String sql = "select count(id) from employees ";
long count = jdbcTemplete.queryForObject(sql, Long.class)
sout(count)
}
}
5.3 简化 JDBC 模版查询
- 每次使用都创建一个JdbcTemplete 的新实例,这种做法效率底下
- JdbcTemplete类被设计成为线程安全的,所以可以在IOC容器中声明他的单个实例,并将这个实例注入到所有的 DAO 实例中
- JdbcTemplete也利用了Java1.5的特点(自动装箱,泛型,可变长度等)来简化开发
- Spring JDBC 框架还提供了一个 JdbcDaoSupport类来简化DAO实现,该类声明了jdbcTemplete属性,它可以从IOC容器中注入,或者自动从数据源中创建
@Repository
public class EmployeeDao{
@Autowired
private JdbcTemplete jdbcTemplete;
public Employee get(Integer id) {
String sql = "select id, name, sex from employees where id = ? ";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
Employee employee = jdbcTemplete.queryForObject(sql, rowMapper, id);
return employee;
}
}
5.4 在 JDBC 模版中使用具名参数
- 在经典的JDBC用法中,SQL参数是用占位符?表示,并且受到位置的限制。定位参数的问题在于,一旦参数的顺序发生变化,就必须改变参数的绑定
- 在Spring JDBC框架中,绑定 SQL 参数的另一种选择是使用具名参数
- 具名参数:SQL按名称(以冒号开头)而不是按位置进行指定,具名参数更易于维护,也提升了可读性,具名参数由框架类在运行时用占位符取代
- 具名参数只在NamedParameterJdbcTemplete中得到支持
<!-- 配置具名参数,该对象可以使用具名参数,其没有无参构造器,所以必须为其构造器指定参数 -->
<bean id="namedParameterJdbcTemplete" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplete" >
<constructor-arg ref="dataSource" />
</bean>
public class JDBCTest{
private NamedParameterJdbcTemplete namedParameterJdbcTemplete;
private ApplicationContext ctx = null;
{
ctx = new ClassPathXmlApplicationContext("applicaitonContext.xml");
namedParameterJdbcTemplete = ctx.getBean(NamedParameterJdbcTemplete);
}
/**
* 可以为参数取名
*/
@Test
public void testNamedParameterJdbcTemplete(){
String sql = "Insert INTO employees(last_name, email, dept_id) values(:id,:name,:age)";
Map<String, Object> paramMap = new HashMap<>{};
paramMap.put("id","1");
paramMap.put("name","FF");
paramMap.put("age","12");
namedParameterJdbcTemplete.update(sql, paramMap)
}
@Test
public void testNamedParameterJdbcTemplete(){
String sql = "Insert INTO employees(last_name, email, dept_id) values(:id,:name,:age)";
Employee employee = new Employee();
employee.setId(1);
employee.setName("name");
employee.setage(12);
SqlPArameterSource paramSource = new BeanPropertySqlParameterSource(employee);
namedParameterJdbcTemplete.update(sql, paramSource);
}
}
6、Spring的事务管理
6.1 事务简介
- 事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性
- 事务就是一系列的动作,他们被当作是一个单独的工作单元,这些动作要么全部完成,要嘛全部不起作用
- 事务的四个关键属性(ACID)
- 原子性(atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要嘛全部完成,要嘛完全不起作用
- 一致性(consistency):一旦所有的事务动作完成,事务就被提交,数据和资源就处于一种满足业务规则的一致性状态中
- 隔离性(isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据破坏
- 持久性(durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响。通常情况下,事务的结果被写道持久化的存储器中
6.2 Spring中的事务管理
- 作为企业级应用程序框架,Spring在不同的事务管理API上定义了一个抽象层。而应用程序开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制
- Spring既支持编程式事务管理,也支持声明式事务管理
- 编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码
- 声明式事务管理:大多数情况下比编程式事务管理更好用,他将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点,可以通过AOP方法模块化。Spring通过SpringAOP框架支持声明式事务管理
- Spring从不同的事务管理API中抽象了一整套的事务机制。开发人员不必了解底层的事务API,就可以利用这些事务机制,有了这些事务机制,事务管理代码就能独立于特定的事务技术了
- Spring的核心事务管理抽象是 PlatformTransactionManager 管理封装了一组独立于技术的方法。无论使用Spring的是哪种事务管理策略(编程式或声明式),事务管理器都是必须的
<!-- 导入C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${user}" />
<property name="password" value="${password}" />
<property name="driverClass" value="${driverClass}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
</bean>
<!-- 导入资源文件 -->
<context:propeety-placeholder location="classpath:db.properties" />
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager" />
/**
* 添加事务注解
*/
@TransactionManager
@Override
public void update(String name, int isbn){
// 1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
// 2.更新书的库存
bookShopDao.updateBookStock(isbn);
// 3.更新用户余额
bookShopDao.updateUserAccount(username, price);
}
6.3 事务传播属性
- 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行
- 事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为:
- REQUIRED:如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行
- REQUIRED-NEW:当前的方法必须启动新的事务,并在他自己的事务中运行。如果有事务正在运行,应该将它挂起
- SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行,否则他可以不运行在事务中
- NOT_SUPPORTE:当前的方法不应该运行在事务中,如果有运行的事务,将它挂起
- MANDATORY:当前的方法必须运行在事务内部。如果没有正在运行的事务,就抛异常
- NEVER:当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
- NESTED:如果有事务运行,当前的方法就应该在这个事务的潜逃事务内运行,否则,就启动一个新的事务,并在他自己的事务内运行
/**
* 使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用
* 如何使用事务,默认取值为 REQUIRED ,即使用调用方法的事务
*/
@Transactionl(propagation=Propagation.REQUIRED)
6.4 Spring事务的隔离级别
- 从理论上来说,事务应该彼此完全隔离,以避免并发事务所导致的问题,然而那样会对性能产生极大的影响,因此事务必须按照顺序运行
- 在实际的开发中,为了提升性能,事务会以较低的隔离级别运行
- 事务的隔离级别可以通过隔离事务属性指定
- 分类:
- DEFALUT:使用底层数据库的默认隔离级别,对于大多数的数据库来说,默认的隔离级别都是READ_COMMIRTED
- READ_UNCOMMITED:允许事务读取未被其他事务提交的变更、脏读,不可重读和幻读的问题都会出现
- READ_COMMITED:只允许事务读取已经被其他事务提交的变更,可以避免脏读,他不可重复读和幻读问题仍然可能出现
- REPEATABLE_READ:确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重读读,但幻读的问题仍然存在
- SERIALIZABLE:确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,所有并发问题都可以避免,但性能十分低下
- 事务的隔离级别要得到底层数据库引擎的支持,而不是应用程序或者框架的支持
- Oracle支持的2种事务隔离级别:READ_COMMITED、SEARIALIZABLE
- MYSQL支持四种事务隔离级别
/**
* 使用isolation指定事务的隔离级别,最常用的取值为READ_COMMITTED
*/
@Transactionl(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITED)
6.5 异常
/**
* 默认情况下Spring的声明式事务对所有的运行时异常进行回滚,可以通过对应的属性进行设置
*/
@Transactionl(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITED,noRollbackFor={UserAccountException.class})
6.6 超时和只读属性
6.6.1 只读
- 由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体的性能产生影响
- 如果一个事务只读取数据而不做修改,数据库引擎可以对这个事务进行优化
- 超时事务属性:事务在强制回滚之前可以保持多久,这样可以防止长期运行的事务占用资源
- 只读事务属性:表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务
/**
* 若真的是一个只读取数据库值的方法,应设置readOnly=true
*/
@Transactionl(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITED,noRollbackFor={UserAccountException.class},readOnly=false,timeout=1)
6.6.2 超时
/**
* 使用timeout指定强制回滚之前事务可以占用的时间
*/
@Transactionl(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITED,noRollbackFor={UserAccountException.class},readOnly=false,timeout=1)
6.7 基于xml的事务管理
6.7.1 xml配置
<!-- 1.配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
</bean>
<!-- 2.配置事务属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="purchase" propagation="REQUIRED_NEW" />
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* com.ums.spring.tx.xml.service.*.*(..))" id="txPointCut" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
</aop:config>
6、Spring整合Hibernate
6.1 Spring 整合Hibernate整合什么
- 由IOC容器来管理Hibernate的SessionFactory
- 让Hibernate使用上Spring的声明式事务
6.2 整合步骤:
- 加入 Hibernate
- 添加jar包
- 添加 Hibernate 的配置文件:hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
<!-- 1.数据源需配置到IOC容器中,所以在此处不需要配置数据源 -->
<!-- 2.关联的 .hbm.xml 也在IOC容器中配置SessionFactory实例时,再进行配置 -->
<!-- 3.配置 hibernate 的基本属性:方言,sql显示及格式化,生成数据表的策略以及二级缓存等 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<property name="hibernate.show_sql">true<property>
<property name="hibernate.format_sql">true<property>
<property name="hibernate.hbm2ddl.auto">update<property>
<!-- 配置hibernate二级缓存相关的属性 -->
</session-factory>
</hibernate-configuration>
- 加入 Spring
- jar 包
- 加入Spring的配置文件
<!-- 配置扫描的包 -->
<context:component-scan base-package="com.ums.spring.hibernate" />
<!-- 配置 数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${user}" />
<property name="password" value="${password}" />
<property name="driverClass" value="${driverClass}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
</bean>
<!-- 导入 资源文件 -->
<context:propeety-placeholder location="classpath:db.properties" />
<!-- 配置 Hibernate 的 SessionFactory 实例:通过 Spring 提供的 LocalSessionFactoryBean 进行配置 -->
<bean id="sessionFactory" class="org.springframework.orm.hiberbate4.LocalSessionFactoryBean" >
<!-- 配置 数据源属性 -->
<property name="dataSource" ref="dataSource"/>
<!-- 配置 hibernate 配置文件的位置及名称:方法1 -->
<!-- <property name="configLocation" value="classpath:hibernate.cfg.xml"/> -->
<!-- 配置 hibernate 配置文件的位置及名称:方法2 - 使用hibernateProperties 属性来配置Hiberbate原生的属性 -->
<property name="hibernateProperties" >
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
<!-- 配置 Hibernate 映射文件的位置及名称 -->
<property name="mappingLocations" value="classpath:com/ums/spring/hibernate/entities/*.hbm.html" />
</bean>
<!-- 配置 spring的声明式事务 -->
<!-- 1.配置 事务管理器 -->
<bean id="" class="org.springframework.orm.hibernate4.HibernateTransactionManager" >
<property name="sessionFactory" ref="sessionFactory" >
</bean>
<!-- 2.配置 事务属性,需要事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManage">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<!-- 3.配置 事务切点,并且吧切点和事务属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* com.ums.spring.hibernate.service.*.*(..))" id="txPointcut" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
</aop:config>
- 整合
@Repository
public class BookShopDaoImpl implements BookShopDao {
@Autowried
private SessionFactory sessionFactory;
// 不推荐使用HibernateTemplete和HibernateDaoSupport
// 因为这样会导致Dao和Spring的API进行耦合
// 可移植性变差
// private HibernateTemplete hibernateTemplete;
// 获取当前线程绑定的Session
private Session getSession(){
return sessionFactory.getCurrentSession();
}
@Override
public int findBookPriceByIsbn(String isbn) {
String hql = "SELECT price from Book where isbn = ?";
Query query = getSession().createQuery(hql).setString(0,isbn);
return (Integer)query.uniqueResult();
}
@Override
public void updateBookStock(String isbn) {
String hql2 = "SELECT stock from Book where isbn = ?";
int stock = (int)getSession().createQuery(hql2).setString(0,isbn).uniqueResult();
if (stock == 0) {
throw new BookStockException("库存不足");
}
String sql = "update book set stock = stock - 1 where isbn = ?";
getSession().createQuery(sql).setString(0,isbn).executeUpdate();
}
}
6.3 Spring Hibernate的事务流程
- 在方法开始前
- 获取Session
- 把Session和当前线程绑定,这样就可以在Dao中使用SessionFactory的 getCurrentSession() 方法来获取Session了
- 开启事务
- 若方法正常结束,即没有出现异常,则
- 提交事务
- 使和当前线程绑定的Session解除绑定
- 关闭Session
- 若方法出现异常,则:
- 回滚事务
- 使和当前线程绑定的Session解除绑定
- 关闭Session
7、Spring整合Struts2
7.1 分析 Spring 如何在 WEB应用中使用?
- jar包:spring-web、spring-webmvc
- Spring的配置文件,没有什么不同
- 如何创建 IOC 容器
-
- 非web应用在main方法中直接创建
-
- web应用应该在 WEB 应用被服务器加载时就创建 IOC 容器:在ServletContextListener#contextInitialized(ServletContextEvent sce) 方法中创建 IOC 容器
-
- 在web应用的其他组件中,如何访问 IOC 容器?在ServletContextListener#contextInitialized(ServletContextEvent sce) 方法中创建 IOC 容器之后,可以把其放在 servletContext 即 application 域的一个属性中
-
- 实际上,Spring 配置文件的名字和位置也应该是可配置的,将其配置到当前web应用的初始化参数中较为合适
public class SpringServletContextListener implements ServletContextListener {
public SpringServletContextListener(){
}
public void contextInitialized(ServletContextEvent arg0{
// 1. 获取Spring配置文件的名称
ServletContext servletContext = arg0.getServletContext();
String config = servletContext.getInitParameter("configLocation");
// 2. 创建 IOC 容器
ApplicationContext ctx = new ClassPathxmlApplicationContext(config);
// 3. 把 IOC 容器放在 ServletContext 的一个属性中
servletContext.setAttribute("ApplicationContext", ctx)
}
public void contextDestoryed(ServletContextEvent arg0){
}
}
public class Person{
private String name;
public void serName(String name) {
this.name = name;
}
public void hello(){
sout("my name is : " + name)
}
}
<!-- applicationContext.xml -->
<bean id="person" class="com.ums.spring.Person" >
<property name="name" value="li" />
</bean>
<!-- web.xml -->
<!-- 添加 -->
<!-- 配置Spring配置文件的名称 -->
<context-param>
<param-name>configLocation</param-name>
<param-value>classpath:applicationContext.xml</param-name>
</context-param>
<!-- 启动 IOC 容器的 ServletContextListener -->
<listener>
<listener-class>com.ums.spring.SpringServletContextListener</listener-class>
</listener>
<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>com.ums.spring.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/TestServlet</url-pattern>
</servlet-mapping>
// TestServlet
public class TestServlet extends HttpServlet {
private static final long SerialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// 1. 从 ApplicationContext 域对象中得到 IOC 容器的引用
ServletContext servletContext = getServletContext();
ApplicationContext ctx = servletContext.getAttribute("ApplicationContext");
// 2. 从 IOC 容器中得到需要的 bean
Person person = ctx.getBean(Person.class);
person.hello();
}
}
- 在web环境下使用Spring
- 新增 jar 包
- spring 的配置文件
- 需要在 web.xml 中加入如下配置
<!-- web.xml -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-name>
</context-param>
<listener>
<listener-class>org.springframework.web.context.contextLoaderListener </listener-class>
</listener>
<body>
<%
// 1. 从 application 域对象中得到IOC容器的实例
ApplicationContext ctx = WebApplicationContextUtils.getWebApplcationContext(application);
// 2. 从 IOC 容器中得到 bean
Person person = ctx.getBean(Person.class);
person.hello();
// 3. 使用 bean
person.hello();
%>
</body>
7.2. Spring 如何整合 Struts2 ?
- 整合目标?
- 使 IOC 容器来管理 Struts2 的 Action
- 如何进行整合
- 正常加入 Struts2
- spring 的 IOC 容器中配置Struts2的Action