spring

1、spring简介

1.1 spring是什么(1)?
  1. 是一个开源框架
  2. 为了简化企业应用开发而生,使用Spring可以使简单的JavaBean实现以前只有EJB才能实现的功能
  3. 是一个IOC和AOP的容器框架
1.2 spring是什么(2)?
  1. 轻量级:Spring是非入侵性的-基于Spring开发的应用中对象可以不依赖Spring的API
  2. 依赖注入
  3. 面向切面编程
  4. 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
  5. 框架:Spring实现了使用简单的组件配置组合成一个复杂的应用,在Spring中可以使用XML和Java注解组合这些对象
  6. 一站式:在IOC和AOP的基础山峰可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring自身也提供了展现层的SpringMVC和持久层的Spring JDBC)
1.3 spring模块

在这里插入图片描述

1.4 spring-HelloWorld
  1. Java bean
class HelloWorld {
	private String name;
	public void setName2(String name) {
		this.name = name
	}
	public String show(){
		System.out.printIn("hello : "  + name )
	} 
}
  1. 配置bean (applicationContext.xml)
<bean id="helloworld" class="com.ums.HelloWorld">
	<property name="name2" value="spring" />
</bean>
  1. 加载bean
 // 1.创建Spring的IOC容器
 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 
 // 2.创建Spring的IOC容器对象
 HelloWorld helloWorld = (HelloWorld)ctx.getBean("helloWorld");
 // 3.调用show方法
 helloWorld.show();
  1. 需要的包
commons-logging
spring-beans
spring-context
spring-core
spring-expression

2、Spring中bean配置

2.1 IOC&DI概述
2.1.1 IOC
  1. 其思想是 反转资源获取方向,传统的资源查找方式要求组件向容器发起请求查找资源。作为回应,容器适时的返回资源,
  2. 应用了IOC之后,则是容器主动的将资源推送给他所管理的组件,组件所要做的仅是选择一种合适的方式来接受资源,这种行为也被称为 查找的被动形式
2.1.2 IOC发展
  1. 分离接口与实现
  2. 采用工厂模式
  3. 反转控制
2.1.3 DI
  1. IOC的另外一种表述方式:即组件以一些预先定义好的方式接受来自于如容器的资源的注入
2.2 配置bean
2.2.1 依赖注入方式
  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自动装配
  1. Spring IOC容器可以自动装配Bean,需要做的仅仅是在bean的autowire属性中指定自动装配的模式
  2. byType(根据类型自动装配):若IOC容器中有多个与目标Bean类型一致的Bean,在这种情况下,Spring无法判断哪个Bean最合适该属性,所以不能执行自动装配
  3. byName(根据名称自动装配):必须将目标bean的名称和属性名设置的完全相同
  4. 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" />
  1. 在bean配置文件里设置autowire属性进行自动装配会装配所有属性,然而若只希望装配个别属性时,autowire属性就不够灵活
  2. autowire属性要嘛根据类型自动装配,要嘛根据名称自动装配,不能两者兼而有之。
  3. 一般情况下,实际项目中很少使用自动装配功能
2.2.3 通过调用实例工厂方法创建bean
  1. 实例工厂方法:将对象的创建过程封装到另外一个对象实例的方法里,当客户端需要请求对象时,只需要简单的调用该实例方法而不需要关心对象的创建细节
  2. 要声明通过实例工厂方法创建的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
  1. 调用静态工厂方法创建bean是将对象创建的过程封装到静态方法中,当客户端需要对象时,只需要简单的调用静态方法,而不关心创建对象的细节
  2. 要声明通过静态方法创建的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 字面值
  1. 可以使用字符串表示的值,可以使用value元素标签或者value属性进行注入
  2. 基本数据类型及其封装类、String等类型都可以采取字面注入的方式
  3. 若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来
2.3.2 引用其他的bean
  1. 组成应用程序的bean经常需要相互协作用来完成应用程序的功能,要使Bean能够相互访问,就必须在Bean配置文件中指定对于Bean的引用
  2. 在Bean的配置文件中,可以通过ref元素或ref属性为bean的属性或者构造器参数指定对Bean的引用
  3. 也可以在属性或者构造器里面包含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值和级联属性
  1. 可以使用专用的null元素标签为Bean的字符串或者其他对象类型的属性注入null值
  2. 和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 集合属性
  1. 在Spring中可以通过一组内置的xml标签( list 、set、 map)来配置集合属性
  2. 配置Java.util.List类型的属性,需要指定List标签,在标签里包含一些元素,这些标签可以通过value指定简单的常量值,通过ref指定对其他Bean的引用,通过bean指定内置Bean定义,通过null指定空元素,甚至可以内嵌其他集合
  3. 数组的定义和List一样,都使用list
  4. 配置Java.util.Set需要使用Set标签,定义元素的方法与List一样
  5. Java.util.Map通过map标签定义,map标签里可以使用多个entry作为子标签,每个条目包含一个键和一个值
  6. 必须在key标签里定义键
  7. 因为键和值的类型没有限制,所以可以自由的为他们指定value,ref,bean或null元素
  8. 可以将Map的键和值作为entry的属性定义:简单常量使用key和value来定义;Bean引用通过key-ref和value-ref属性来定义
  9. 使用props定义Java.util.Properties,该标签使用多个prop作为子标签,每个prop标签必须定义key属性
  10. 可以使用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命名空间
  1. 为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息
  2. Spring自从2.5版本之后开始引入一个新的p命名空间,可以通过bean元素属性的方式配置Bean的属性
  3. 使用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 继承
  1. Spring允许继承bean的配置,被继承bean被称为父bean,继承这个父bean的bean称为子bean
  2. 子bean从父bean中继承配置,包括Bean的属性配置
  3. 子bean也可以覆盖从父bean继承过来的配置
  4. 父bean可以作为配置模版,也可以作为Bean的实例。若只是想把父bean作为模版,可以设置bean的abstract属性为true,这样Spring将不会实例化这个bean
  5. 并不是bean元素里的所有属性都会被继承。比如:autowire,abstract等
  6. 也可以忽略父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 依赖
  1. Spring允许用户通过depends-on属性设定Bean前置依赖的bean,前置依赖的bean会在本bean实例化之前创建好
  2. 如果前置依赖多个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)
  1. 在配置文件里配置bean时,有时需要在bean的配置里混入系统部署的细节信息(例如:文件路径,数据源配置信息等),而这些部署细节实际上需要和bean配置相互分离
  2. Spring提供了一个PropertyPlaceholderConfigurer的BeanFactory后置处理器,这个处理器允许用户将bean配置的部分内容外移到属性文件中,可以在bean配置文件里使用形式为“${var}”的变量,PropertyPlaceholderConfigurer从属性文件里加载属性,并使用这些属性来替换变量
  3. 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、属性和方法
  1. 引用其他对象
  2. 引用其他对象的属性
  3. 调用其他方法,可以链式操作
  4. 引用bean、属性和方法
  5. 调用静态方法或静态属性:通过T()调用一个类的静态方法,他将返回一个ClassObject,然后再调用相应的方法或属性
2.7.3 支持运算符
  1. 算术运算符
  2. 加号可以作为字符串的连接
  3. 比较运算符
  4. 逻辑运算符号:and
  5. if-else运算符
  6. if-else的变体
  7. 正则表达式
2.8 IOC容器中bean的生命周期
2.8.1 概述
  1. Spring IOC 容器可以管理Bean的生命周期,spring允许在Bean生命周期的特定点去执行定制的任务
  2. Spring IOC容器对bean的生命周期进行管理的过程:
  • 通过构造器或工程方法创建bean实例
  • 为bean的属性设置值和对其他bean的引用
  • 调用bean的初始化方法
  • bean可以使用了
  • 当容器关闭时,调用bean的销毁方法
  1. 在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的后置处理器
  1. Spring IOC容器对bean的生命周期进行管理的过程
  • 通过构造器或工厂方法创建bean实例
  • 为bean的属性设置值和对其他bean的引用
  • 将bean实例化传递给bean后置处理器的postProcessBeforeInitialization方法
  • 调用bean的初始化方法
  • 将bean实例传递给bean后置处理器的postProcessAfterInitialization方法
  • bean可以使用了
  • 当容器关闭时,调用bean的销毁方法
  1. 实现过程
  • 实现接口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的属性)
  1. 在Classpath中扫描组件
  • 组件扫描:Spring能够从ClassPath 下自动扫描,侦测和实例化具有特定注解的组件
  • 特定的组件包括

@Component:基本注解,标识了一个受Spring管理的组件
@Repository:标识持久层组件
@Service:标识服务层(业务层)组件
@Controller:标识表现层组件

  • 对于扫描到的组件,Spring有默认的命名策略
  1. 配置文件
  • 当在组件类上使用了特定的注解之后,还需要在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" />
  1. 组件装配
  • 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 泛型依赖注入
  1. 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 基础
  1. 在SpringIOC容器读取Bean配置创建Bean实例前,必须对他进行实例化,只有在容器实例化后,才可以从IOC容器里获取Bean实例并使用
  2. Spring提供了两种类型的IOC容器实现
  • BeanFactory:IOC容器的基本实现
  • ApplicationContext:提供了更多的高级特性,是BeanFactory的子接口
  1. BeanFactory是Spring框架的基础设施,面向Spring本身;ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory
  2. 无论使用何种方式,配置文件都是相同的
3.2 ApplicationContext

在这里插入图片描述

3.2.1 ApplicationContext实现类有两个
  1. ClassPathXmlApplicationContext:从类路径下加载配置文件
  2. FileSystemXmlApplicationContext:从文件系统中加载配置文件
3.2.2 ConfigurableApplicationContext
  1. 扩展于ApplicationContext,新增加两个主要方法:refresh() 和 close(),让ApplicationContext具有启动、刷新和关闭上下文能力
  2. ApplicationContext在初始化上下文时就实例化所有单例的Bean
3.2.3 WebApplicationContext
  1. 专门为web应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作

4、AOP基础

4.1 遇到的问题
  1. 代码混乱:越来越多的非业务需求(日志和验证等),加入后,原有的业务方法急剧膨胀,每个方法在处理逻辑的同时,还必须兼顾其他多个关注点
  2. 代码分散:以日志需求为例,只是为了满足这个单一的需求,就不得不在多个模块(方法)中多次重复相同的日志代码。如果日志需求发生变化,必须修改所有的模块
4.2 解决方法(使用动态代理解决上述问题)
  1. 代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上
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简介
  1. AOP(面向切面编程):是一种新的方法论,是对传统的OOP(面向对象编程)的补充
  2. AOP的主要编程对象是切面(aspect),而切面模块化横切关注点
  3. 在应用AOP编程时,仍然需要定义公共的功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类,这样一来横切关注点就被模块化到特殊的对象(切面)中
  4. AOP的好处:
  • 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
  • 业务模块更加简洁,只包含核心业务代码
4.4 AOP术语
  1. 切面(aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
  2. 通知(advice):切面必须完成的工作
  3. 目标(target):被通知的对象
  4. 代理(proxy):向目标对象应用通知后创建的对象
  5. 连接点(Joinpoint):程序执行的某个特定位置
  6. 切点(PointCut):每个类都拥有多个连接点
4.5 Spring AOP
  1. AspectJ:Java社区里最完整最流行的AOP框架
  2. 在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP
4.6 基于AspectJ注解
4.6.1 在Spring中启用AspectJ注解支持
  1. 在Spring应用中使用AspectJ注解,必须在classpath中包含AspectJ类库:aopalliance.jar、sapectj.weaver.jar和Spring-aspects.jar
  2. 将aop Schema添加到beans的根元素中
  3. 要在 SpringIOC 容器中启用 AspectJ注解支持,只要在 Bean 配置文件中定义一个空的 XML元素 aop:aspectj-autoproxy
  4. 当 Spring ioc 容器侦测到 Bean配置文件中的 aop:aspectj-autoproxy 元素时,会自动与 AspectJ切面匹配的bean创建代理
4.6.2 用 AspectJ注解声明切面
  1. 要在Spring中声明 AspectJ切面,只需要在IOC容器中将切面声明为Bean实例,当在Spring IOC容器中初始化 AspectJ 切面后,Spring IOC 容器就会为那些与 AspectJ切面相匹配的Bean创建代理
  2. 在AspectJ注解中,切面只是一个带有@Aspect注解的Java类
  3. 通知是 标注有某种注解的简单的Java方法
  4. AspectJ支持5种类型的通知注解:
  • @Before:前置通知,在方法执行之前执行
  • @After:后置通知,在方法执行之后执行
  • @AfterRunning:返回通知,在方法返回结果之后执行
  • @AfterThrowing:异常通知,在方法抛出异常之后
  • @Around:环绕通知,围绕着方法执行
4.6.3 让通知访问当前连接点的细节
  1. 可以在通知方法中声明一个类型为JointPoint的参数,然后就能访问链接细节如方法名称和参数值
4.6.4 后置通知
  1. 后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候,下面的后置通知记录了方法的终止
  2. 一个切面可以包括多个通知
4.6.5 返回通知、异常通知、环绕通知
  1. 返回通知:在方法正常结束返回之后,执行的代码,返回通知是可以访问到方法的返回值的
  2. 异常通知:在目标出现异常时会执行的代码,可以访问到异常对象;且可以指定再出现特定的异常时再执行通知代码。例如:NullPointerException ex;
  3. 环绕通知:围着方法执行(相当于动态代理)
4.6.6 指定切面的优先级
  1. 在同一个连接点上应用不止一个切面时,除非明确指定,否则他们的优先级是不确定的
  2. 切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定
  3. 实现 Ordered 接口,getOrder()方法的返回值越小,优先级越高
  4. 若使用 @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 引入通知
  1. 引入通知可以使用两个实现类 MaxCaculatorImpl 和 MinCaculatorImpl,让 AirthmeticCaculatorImpl 动态的实现MaxCaculator 和 MinCaculator 接口,而这与从MaxCaculatorImpl 和 MinCaculatorImpl 中实现多继承的效果相同,但却不需要修改 ArithmeticCaculatorImpl 的源代码
  2. 引入通知也必须在切面中声明
  3. 在切面中,通过为任意字段添加 @DeclareParents 注解来引入声明
  4. 注解类型的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简介
  1. 为了使 JDBC 更加易使用,Spring在JDBC API 上定义了一个抽象层,以此建立一个JDBC 存取框架
  2. 作为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 模版查询
  1. 每次使用都创建一个JdbcTemplete 的新实例,这种做法效率底下
  2. JdbcTemplete类被设计成为线程安全的,所以可以在IOC容器中声明他的单个实例,并将这个实例注入到所有的 DAO 实例中
  3. JdbcTemplete也利用了Java1.5的特点(自动装箱,泛型,可变长度等)来简化开发
  4. 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 模版中使用具名参数
  1. 在经典的JDBC用法中,SQL参数是用占位符?表示,并且受到位置的限制。定位参数的问题在于,一旦参数的顺序发生变化,就必须改变参数的绑定
  2. 在Spring JDBC框架中,绑定 SQL 参数的另一种选择是使用具名参数
  3. 具名参数:SQL按名称(以冒号开头)而不是按位置进行指定,具名参数更易于维护,也提升了可读性,具名参数由框架类在运行时用占位符取代
  4. 具名参数只在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 事务简介
  1. 事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性
  2. 事务就是一系列的动作,他们被当作是一个单独的工作单元,这些动作要么全部完成,要嘛全部不起作用
  3. 事务的四个关键属性(ACID)
  • 原子性(atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要嘛全部完成,要嘛完全不起作用
  • 一致性(consistency):一旦所有的事务动作完成,事务就被提交,数据和资源就处于一种满足业务规则的一致性状态中
  • 隔离性(isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据破坏
  • 持久性(durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响。通常情况下,事务的结果被写道持久化的存储器中
6.2 Spring中的事务管理
  1. 作为企业级应用程序框架,Spring在不同的事务管理API上定义了一个抽象层。而应用程序开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制
  2. Spring既支持编程式事务管理,也支持声明式事务管理
  3. 编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码
  4. 声明式事务管理:大多数情况下比编程式事务管理更好用,他将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点,可以通过AOP方法模块化。Spring通过SpringAOP框架支持声明式事务管理
  5. Spring从不同的事务管理API中抽象了一整套的事务机制。开发人员不必了解底层的事务API,就可以利用这些事务机制,有了这些事务机制,事务管理代码就能独立于特定的事务技术了
  6. 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 事务传播属性
  1. 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行
  2. 事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为:
  • REQUIRED:如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行
  • REQUIRED-NEW:当前的方法必须启动新的事务,并在他自己的事务中运行。如果有事务正在运行,应该将它挂起
  • SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行,否则他可以不运行在事务中
  • NOT_SUPPORTE:当前的方法不应该运行在事务中,如果有运行的事务,将它挂起
  • MANDATORY:当前的方法必须运行在事务内部。如果没有正在运行的事务,就抛异常
  • NEVER:当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
  • NESTED:如果有事务运行,当前的方法就应该在这个事务的潜逃事务内运行,否则,就启动一个新的事务,并在他自己的事务内运行
/**
 * 使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用
 * 如何使用事务,默认取值为 REQUIRED ,即使用调用方法的事务
 */
@Transactionl(propagation=Propagation.REQUIRED)
6.4 Spring事务的隔离级别
  1. 从理论上来说,事务应该彼此完全隔离,以避免并发事务所导致的问题,然而那样会对性能产生极大的影响,因此事务必须按照顺序运行
  2. 在实际的开发中,为了提升性能,事务会以较低的隔离级别运行
  3. 事务的隔离级别可以通过隔离事务属性指定
  4. 分类:
  • DEFALUT:使用底层数据库的默认隔离级别,对于大多数的数据库来说,默认的隔离级别都是READ_COMMIRTED
  • READ_UNCOMMITED:允许事务读取未被其他事务提交的变更、脏读,不可重读和幻读的问题都会出现
  • READ_COMMITED:只允许事务读取已经被其他事务提交的变更,可以避免脏读,他不可重复读和幻读问题仍然可能出现
  • REPEATABLE_READ:确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重读读,但幻读的问题仍然存在
  • SERIALIZABLE:确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,所有并发问题都可以避免,但性能十分低下
  1. 事务的隔离级别要得到底层数据库引擎的支持,而不是应用程序或者框架的支持
  2. Oracle支持的2种事务隔离级别:READ_COMMITED、SEARIALIZABLE
  3. 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 只读
  1. 由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体的性能产生影响
  2. 如果一个事务只读取数据而不做修改,数据库引擎可以对这个事务进行优化
  3. 超时事务属性:事务在强制回滚之前可以保持多久,这样可以防止长期运行的事务占用资源
  4. 只读事务属性:表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务
/**
 * 若真的是一个只读取数据库值的方法,应设置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整合什么
  1. 由IOC容器来管理Hibernate的SessionFactory
  2. 让Hibernate使用上Spring的声明式事务
6.2 整合步骤:
  1. 加入 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>
  1. 加入 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>
  1. 整合
@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的事务流程
  1. 在方法开始前
  • 获取Session
  • 把Session和当前线程绑定,这样就可以在Dao中使用SessionFactory的 getCurrentSession() 方法来获取Session了
  • 开启事务
  1. 若方法正常结束,即没有出现异常,则
  • 提交事务
  • 使和当前线程绑定的Session解除绑定
  • 关闭Session
  1. 若方法出现异常,则:
  • 回滚事务
  • 使和当前线程绑定的Session解除绑定
  • 关闭Session

7、Spring整合Struts2

7.1 分析 Spring 如何在 WEB应用中使用?
  1. jar包:spring-web、spring-webmvc
  2. Spring的配置文件,没有什么不同
  3. 如何创建 IOC 容器
    1. 非web应用在main方法中直接创建
    1. web应用应该在 WEB 应用被服务器加载时就创建 IOC 容器:在ServletContextListener#contextInitialized(ServletContextEvent sce) 方法中创建 IOC 容器
    1. 在web应用的其他组件中,如何访问 IOC 容器?在ServletContextListener#contextInitialized(ServletContextEvent sce) 方法中创建 IOC 容器之后,可以把其放在 servletContext 即 application 域的一个属性中
    1. 实际上,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();
	}
}
  1. 在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 ?
  1. 整合目标?
  • 使 IOC 容器来管理 Struts2 的 Action
  1. 如何进行整合
  • 正常加入 Struts2
  • spring 的 IOC 容器中配置Struts2的Action
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值