Spring概述
Spring介绍
Spring它是一个一站式的分层轻量级框架。
Spring体系结构
1. core container
a) beans与core 它们提供spring框架最基本功能,包含ioc与di
b) context 上下文对象,基于beans与cores
c) spel它是sprng提供的一个表达式语言
2. Data access/integration
a) 数据访问
b) 集成
3. Web
a) Spring本身提供springmvc
b) 也可以与其它的web层进行集成
4. AOP
AOP大部分情况下是使用动态代理来实现的。
5. Test
使用spring可以方便的进行测试
Spring框架优点
n 方便解耦,简化开发
Spring就是一个大工厂,可以将所有对象创建和依赖关系维护,交给Spring管理
n AOP编程的支持
Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
n 声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无需手动编程
n 方便程序的测试
Spring对Junit4支持,可以通过注解方便的测试Spring程序
n 方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持
n 降低JavaEE API的使用难度
Spring 对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低
IOC与DI
Spring的jar包下载
Spring的官网:spring.io
我们课程中讲解使用的是spring4.2.4
在spring3.0.2版本后,不在提供依赖jar包
docs 存在API和规范文档
libs 开发jar包
schema 开发过程中需要的xml的schema约束
spring开发环境搭建
在spring开发中,我们要根据不同的情况来导入不同的jar包,当前我们要讲解的是关于ioc与di
对于ioc与di讲解我们只需要使用spring的核心功能。
1. beans相关
2. core相关
3. context相关
4. spel相关
在spring-framework-4.2.4.RELEASE-dist\spring-framework-4.2.4.RELEASE\libs下去找
我们使用spring框架也会使用到配置文件,我们需要在src下创建一个关于spring的配置文件,一般情况名称叫applicationContext.xml
问题:applicationContext.xml的约束去哪里找?
它的路径:
spring-framework-4.2.4.RELEASE-dist\spring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html下的xsd-configuration.html中去找 , 这个文件中规定了在不同情况下的各种约束.
这里我们只需要用到的是: XML Schema-based configuration
IOC快速入门
Ioc它是什么,解决什么问题,它的原理是如何实现。
IOC inversionof Controller 控制反转。
资源由Spring创建、管理并注入给需要的应用程序或对象.
程序运行时报错
原因:当前环境需要一个commons-loggin的jar包
(spring_day01\Spring\jar包\spring Ioc基本包)
总结spring使用步骤:
1. 在applicationContext.xml文件中配置bean
<?xml version="1.0"encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTDBEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="userService"class="cn.itheima.ioc.UserServiceImpl">
</bean>
</beans>
publicclass UserServiceImpl implements IUserService {
publicvoid sayHello() {
// TODO Auto-generated method stub
System.out.println("helloSpring . ..");
}
}
2. 创建一个AppliCationContext对象
ApplicationContext它是BeanFactory的一个子接口,我们在使用时使用的是AppliCationContext的实现类ClassPathXmlApplicationContext(相当于初始化容器)
publicclass IocTest {
@Test
publicvoid test01() {
// 使用spring容器提供的ioc
// ioc本质上就是通过 xml配置文件 +反射+factory来实现.
// 在spring中提供一个BeanFactory工厂,我们一般使用的是它的一个子接口ApplicationContext
ClassPathXmlApplicationContextapplicationContext = newClassPathXmlApplicationContext(
"applicationContext.xml");
//ClassPathXmlApplicationContext
// 它可以帮助我们在classpath路径下查找applicationContext.xml配置文件
IUserService userService =(IUserService) applicationContext.getBean("userService");
userService.sayHello();
}
}
可以通过getBean(配置文件中id名称)来获取指定的对象(相当于从容器中取出指定对象)。
DI
DI:dependencyinjection 依赖注入(为应用程序提供所依赖的资源的过程 , 称为依赖注入; 应用程序被动等待Spring为其注入资源)
在spring框架负责创建Bean对象时,动态将依赖对象注入到Bean组件。需要给属性提供set/get方法
publicclass UserServiceImpl implements IUserService {
private String info ;
public String getInfo() {
returninfo;
}
publicvoid setInfo(String info) {
this.info = info;
}
publicvoid sayHello() {
System.out.println("helloSpring . .."+info);
}
}
然后再配置文件中我们就能对bean的一些属性进行注入 :
<beans>
<bean id="userService"class="cn.itheima.ioc.UserServiceImpl">
<property name="info" value="ITCAST"></property>
</bean>
</beans>
<property>中的name就是属性名.
然后执行上例中的测试代码后输出结果:he’llSpring…ITCAST .
简单说,这时UserServiceImpl中的info属性值就是ITCAST
面试题:IOC和DI区别?
IOC是一种思想,而DI(依赖注入)是一种对象赋值的手段
Bean获取与实例化
Bean的获取:
Person person = (Person) applicationContext.getBean(Person.class);
Person person = (Person) applicationContext.getBean(“person”);//id=”person”
我们不仅可以通过在getBean()方法中传入配置中的id或者name , 还可以通过类来取得bean.
ApplicationContext与BeanFactory关系
ApplicationContext它是扩展BeanFactory的子接口。
BeanFactory它采取延迟加载的方案,只有真正在getBean时才会实例化Bean
AppliCationContext它会在配置文件加载时,就会初始化Bean,并且ApplicationContext它提供不同的应用层的Context实现。例如在web开发中可以使用WebApplicationContext.
在开发中我们一般使用的是ApplicationContext,真正使用的是其实现类,有以下两个:
1.FileSystemXmlAppliCationContext根据文件路径获取
2.ClassPathXmlApplicationContext 根据类路径获取(src下就是类路径)
publicclass Bean1 {
public Bean1(){
System.out.println("bean1的无参数构造");
}
publicvoid show(){
System.out.println("bean1 show .,,,,");
}
}
Xml中的配置:
<!-- 在开发中可以使用id或name
id它遵守的xml规范,名称中不能包含特殊符号
name它是为了更好的扩展,在name中可以包含一些特殊符号,例如"/" -->
<bean id="bean1"class="cn.itheima.bean.Bean1"></bean>
publicclass BeanTest {
// 使用BeanFactory来获取Bean1
@Test
publicvoid test1(){
BeanFactoryfactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
Bean1bean1 = (Bean1) factory.getBean("bean1");
bean1.show();
}
}
测试结果:通过打断点运行我们发现在执行getbean的时候才输出无参构造的语句 .
// 使用ClassPathXmlApplicationContext来获取Bean1
@Test
publicvoid test2() {
ApplicationContextapplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Bean1bean1 = (Bean1) applicationContext.getBean("bean1") ;
bean1.show();
}
测试结果:打断点发现 , 当我们还没有运行getbean()的时候控制台就已经输出无参构造的语句了 , 说明配置文件加载时,就会初始化Bean . 所以这种方式可以提前帮我们预检查一下一些错误 .
// FileSystemXmlApplicationContext文件路径来获取Bean1
@Test
publicvoid test3() {
ApplicationContextapplicationContext = new FileSystemXmlApplicationContext("src/applicationContext.xml");
Bean1bean1 = (Bean1) applicationContext.getBean("bean1") ;
bean1.show();
}
Bean的实例化方式
有三种方式:
1.无参数构造
对于这种方式,注意Bean类中必须提供无参数构造。
在xml文件中的配置:
<bean id="bean1" class="cn.itheima.bean.Bean1"></bean>
随便取的名字而已.
2.静态工厂方法
需要创建一个工厂类,在工厂类中提供一个static返回bean对象的方法就可以。
publicclass Bean2 { //创建一个Bean2类
publicvoid show(){
System.out.println("bean2...show");
}
}
提供Bean2类的工厂:
publicclass Bean2Factory {
publicstatic Bean2 createBean2(){
returnnew Bean2();
}
}
Xml配置文件中:
<!-- 静态工厂方法 -->
<bean name="bean2"class="cn.itheima.bean.Bean2Factory" factory-method="createBean2"></bean>
注意:只要applicationContext.xml配置文件一加载就会将文件中的所有bean都初始化 .所以不用的话要即时把bean给注释掉 .
静态测试:
// 静态工厂方法获取Bean
@Test
publicvoid test4() {
ApplicationContextapplicationContext = new FileSystemXmlApplicationContext("src/applicationContext.xml");
Bean2bean2 = (Bean2) applicationContext.getBean("bean2");
bean2.show();
}
实例工厂方法
需要创建一个工厂类,在工厂类中提供一个非static的创建bean对象的方法,在配置文件中需要将工厂配置,还需要配置bean .
创建Bean3:
publicclass Bean3 {
publicvoid show(){
System.out.println("bean3...show");
}
}
创建Bean3的非static方法返回bean对象 ;
publicclass Bean3Factory {
public Bean3 createBean3(){
returnnew Bean3();
}
}
Xml配置文件中:
<!-- 实例工厂方法 -->
<bean name="bean3Factory"class="cn.itheima.bean.Bean3Factory"></bean>
<bean name="bean3"factory-bean="bean3Factory" factory-method="createBean3"></bean>
测试类:
// 实例工厂方法获取Bean
@Test
publicvoid test5() {
ApplicationContextapplicationContext = new FileSystemXmlApplicationContext("src/applicationContext.xml");
Bean3bean3 = (Bean3) applicationContext.getBean("bean3");
bean3.show();
}
Bean的作用域
在bean声明时它有一个scope属性,它是用于描述bean的作用域。
可取值有:
1.singleton:单例 代表在spring ioc容器中只有一个Bean实例 (默认的scope)
// 测试bean的scope属性
@Test
publicvoid test6() {
ApplicationContextapplicationContext = new FileSystemXmlApplicationContext("src/applicationContext.xml");
Bean1bean1 = (Bean1) applicationContext.getBean("bean1");
Bean1bean11 = (Bean1) applicationContext.getBean("bean1");
// System.out.println(bean1 ==bean11); //scope=singleton 输出true
// System.out.println(bean1 ==bean11); //scope=prototype false
}
2.prototype多例 每一次从spring容器中获取时,都会返回一个新的实例
(Struts 中的action),
修改<bean>标签中的scope属性为:
<bean id="bean1"class="cn.itheima.bean.Bean1" scope="prototype"></bean>
结果如上.
3.request 用在web开发中,将bean对象request.setAttribute()存储到request域中
4.session 用在web开发中,将bean对象session.setAttribute()存储到session域中
在开发中常用的值是singleton与prototype
Bean的生命周期
1. instantiate bean对象实例化
2. populate properties 封装属性
3. 如果Bean实现BeanNameAware执行setBeanName
4. 如果Bean实现BeanFactoryAwar或ApplicationContextAwar设置工厂setBeanFactory或上下文对象setApplicationContext
5. 如果存在类实现BeanPostProcessor(后处理Bean),执行postProcessBeforeInitialization
6. 如果Bean实现InitializingBean执行afterPropertiesSet
7. 调用自定义的init-method方法
8. 如果存在类实现BeanPostProcessor(处理Bean),执行postProcessAfterInitialization
9. 执行业务处理
10. 如果Bean实现DisposableBean执行destroy
11. 调用自定义的destroy-method
publicclass BeanLifeCycle implements BeanNameAware,ApplicationContextAware, InitializingBean, DisposableBean {
private String name;
public BeanLifeCycle() {
System.out.println("第一步:实例化BeanLifeCycle对象");
}
// 自定义功能
publicvoid add() {
System.out.println("第九步:自定义功能add.....");
}
public String getName() {
returnname;
}
publicvoid setName(String name) {
System.out.println("第二步:属性name的注入" + name);
this.name = name;
}
@Override
publicvoid setBeanName(String name) {
System.out.println("第三步:得到bean的id或name值:" + name);
}
@Override
publicvoidsetApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("第四步:得到ApplicationContext对象:" + applicationContext);
}
@Override
publicvoid afterPropertiesSet() throws Exception {
System.out.println("第六步:属性注入完成后");
}
publicvoid myInit() {
System.out.println("第七步:自定义的init方法");
}
@Override
publicvoid destroy() throws Exception {
System.out.println("第十步:执行destroy方法");
}
publicvoid myDestroy() {
System.out.println("第十一步:执行自定义的销毁方法");
}
}
publicclass MyBeanPostProcess implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第五步:beanPostProcessor的before方法");
System.out.println("处理的bean是" + bean + " 它的名称是" + beanName);
returnbean;
}
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
System.out.println("第八步:beanPostProcessor的after方法");
if (beanName.equals("beanLifeCycle")) {
// 使用动态代理可以处理bean的功能增强
Objectproxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("add")) {
longcurrentTimeMillis = System.currentTimeMillis();
Objectinvoke = method.invoke(bean, args);
longcurrentTimeMillis2 = System.currentTimeMillis();
returninvoke;
}else {
returnmethod.invoke(bean, args);
}
}
});
}
returnbean;
}
}
对于bean的生命周期方法的重点::
第三步与第四步是让Bean了解spring容器。
第五步与第八步可以针对指定的Bean进行功能增强,这时一般会使用动态代理.
<beanclass="cn.itheima.bean.MyBeanPostProcess"></bean>
第六步与第十步:通过实现指定的接口来完成init与destroy操作
但是在开发中一般不使用第6步与第10步,原因是我们可以使用第7步与第11步来完成。
第7步与第11步的初始化与销毁操作它无耦合,推荐使用的。但是必须在配置文件中指定初始化与销毁的方法
总结:
对于bean的生命周期,我们需要关注的主要有两个方法:
1. 增强bean的功能可以使用后处理Bean, BeanPostProcessor
2. 如果需要初始化或销毁操作我们可以使用init-method destroy-method
注意:destroy-method只对scope=singleton有效果。因为容器中有多个bean对象.
Bean的属性注入
在spring中bean的属性注入有两种
构造器注入
要给类提供set/get方法, 必须提供有参构造其中参数的顺序代表了xml配置文件中index的序号.序号从0开始.如果没有写index的话, 声明的顺序就代表了参数的顺序.
publicclass Car {
private String name ;
privatedoubleprice ;
public Car(String name, doubleprice) {
super();
this.name = name;
this.price = price;
}
public String getName() {
returnname;
}
publicvoid setName(String name) {
this.name = name;
}
publicdouble getPrice() {
returnprice;
}
publicvoid setPrice(doubleprice) {
this.price = price;
}
public Car() {
super();
// TODO Auto-generated constructor stub
}
}
Xml配置文件中:
<!-- 使用构造方法对car的属性进行注入 -->
<bean id="car" class="cn.itheima.di.Car">
<constructor-arg index="0"type="java.lang.String" value="奔驰"></constructor-arg>
<constructor-arg index="1"type="double" value="1000000"></constructor-arg>
</bean>
测试类:
// 测试构造方法注入
@Test
publicvoid test1() {
ApplicationContextapplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Carcar = (Car) applicationContext.getBean("car");
System.out.println(car.getName() + " " + car.getPrice());
}
Setter方法注入
前提条件: 必须提供无参构造.因为在实例化的时候需要无参构造来实例化.
xml配置文件:
<!-- 使用setter方法对car的属性进行注入 -->
<bean id="car1" class="cn.itheima.di.Car">
<property name="name"value="宝马"></property>
<property name="price"value="500000"></property>
</bean>
测试代码:
// 测试setter方法注入
@Test
publicvoid test2() {
ApplicationContextapplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Carcar = (Car) applicationContext.getBean("car1");
System.out.println(car.getName() + " " + car.getPrice());
}
关于ref属性作用
<bean id="person"class = "cn.itheima.di.Person">
<property name="name"value="张三"></property>
<property name="car"ref="car1"></property>
</bean>
使用ref来引入另一个bean对象,完成bean之间的注入
注意:property 后的name写的是set方法后的名如setNamed 那我们就写named , 而不是属性中的name;
publicclass Person {
private String name ;
private Car car ;
public String getName() {
returnname;
}
publicvoid setName(String name) {
this.name = name;
}
public Car getCar() {
returncar;
}
publicvoid setCar(Car car) {
this.car = car;
}
}
测试类:
@Test
//测试ref引用其他bean的id或者name
publicvoid test3() {
ApplicationContextapplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Personperson = (Person) applicationContext.getBean("person");
System.out.println(person.getName() + " " + person.getCar().getName()+" "+person.getCar().getPrice());
}
总结构造器和setter方法注入 , 构造器相当于newCar(name, price) , setter()方法相当于new Car() ,setName(XXX) , setPrice(XXX) , 其中有property中的ref可以大致看出IOC和DI的关系大致可以理解为:
集合属性的注入
在spring中对于集合属性,可以使用专门的标签来完成注入例如:list set map properties等集合元素来完成集合属性注入.
publicclass CollectionDemo {
private List list ;
private Set set ;
private Map map ;
private Properties props ;
public List getList() {
returnlist;
}
publicvoid setList(List list) {
this.list = list;
List属性注入
<!-- 集合属性注入 -->
<bean name="collectionDemo"class="cn.itheima.di.CollectionDemo" >
<property name="list">
<list>
<value>张三</value>
<value>10</value>
<ref bean="car"/>
</list>
</property>
测试代码:
// 测试集合属性注入---list
@Test
publicvoid test4() {
ApplicationContextapplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
CollectionDemocd = (CollectionDemo) applicationContext.getBean("collectionDemo");
System.out.println(cd.getList());
}
如果属性是数组类型也可以使用list完成注入
Set属性注入
Xml配置文件:
<property name="set">
<set>
<value>10</value>
<value>李四</value>
<ref bean="person"></ref>
</set>
</property>
测试类:
// 测试集合属性注入---set
@Test
publicvoid test5() {
ApplicationContextapplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
CollectionDemocd = (CollectionDemo) applicationContext.getBean("collectionDemo");
System.out.println(cd.getSet());
}
Map属性注入
Xml配置文件:
<property name="map">
<map>
<entry key="username"value="李四"></entry>
<entry key-ref="person"value-ref="car"></entry>
</map>
</property>
此时的Person相当于是key , car相当于是value .一共存了 2 个对象.
测试类:
// 测试集合属性注入---map
@Test
publicvoid test6() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
CollectionDemocd = (CollectionDemo) applicationContext.getBean("collectionDemo");
System.out.println(cd.getMap());
}
Properties属性注入
Java.util.Properties是java.utilsMap的实现类,相当于就是一个map,, 它的key与value都是String类型.
Xml配置文件:
<property name="props">
<props>
<prop key="company">ITCAST</prop>
<prop key="price">1000</prop>
</props>
</property>
测试类:
// 测试集合属性注入---props
@Test
publicvoid test7() {
ApplicationContextapplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
CollectionDemocd = (CollectionDemo) applicationContext.getBean("collectionDemo");
System.out.println(cd.getProps());
}
名称空间p和c的使用
Spring2.0以后提供了xml命名空间。
问题:名称空间的约束去哪里找?
它的路径:
spring-framework-4.2.4.RELEASE-dist\spring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html下的xsd-configuration.html中去找 , 这个文件中规定了在不同情况下的各种约束.
这里我们只需要用到的是:40.2.1 Referencing the schemas来配置我们的xml
<?xml version="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- bean definitions here -->
</beans>
P名称空间
C名称空间
首先它们不是真正的名称空间,是虚拟的。它是嵌入到spring内核中的。
使用p名称空间可以解决我们setter注入时<property>简化
使用c名称空间可以解决我们构造器注入时<constructor-arg>简化
创建类:提供有参无参构造
publicclass Dog {
private String name ;
private String color ;
public Dog(String name, String color) {
super();
this.name = name;
this.color = color;
}
public Dog() {
super();
// TODO Auto-generated constructor stub
}
public String getName() {
returnname;
xml配置文件中:
<?xml version="1.0"encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- <beanid="dog1" class="cn.itheima.namespace.Dog" >
<propertyname="name" value="吉娃娃" />
<propertyname="color" value="黄色" />
</bean> -->
<bean id="dog1"class="cn.itheima.namespace.Dog" p:name="国美" p:color="蓝色">
</bean>
</beans>
原本的使用setter注入方式:
<!-- <beanid="dog1" class="cn.itheima.namespace.Dog" >
<propertyname="name" value="吉娃娃" />
<propertyname="color" value="黄色" />
</bean> -->
在applicationContext.xml文件中添加p名称空间简化setter注入: xmlns:p="http://www.springframework.org/schema/p"
<bean id="dog1"class="cn.itheima.namespace.Dog" p:name="国美" p:color="蓝色">
原本使用的构造器注入方式:
<!-- <beanid="dog2" class="cn.itheima.namespace.Dog">
<constructor-argindex="0" value="黑色"></constructor-arg>
<constructor-argindex="1" value="藏獒"></constructor-arg>
</bean> -->
使用c名称空间来解决构造器注入
在applicationContext.xml文件中添加c名称空间
xmlns:c="http://www.springframework.org/schema/c"
<bean id="dog2"class="cn.itheima.namespace.Dog" c:name="国美" c:color="蓝色"></bean>
注:如果c或p名称空间操作的属性后缀是”-ref”代表要引入另一个已经存在的bean,例如
XML文件中:
<bean id="person"class="cn.itheima.namespace.Person" p:name="张三" p:dog-ref="dog2"></bean>
新建的person类:重写toString() ,并且提供set/get方法.
publicclass Person {
private String name ;
private Dog dog ;
public String getName() {
returnname;
测试类:
@Test
publicvoid test3(){
//c或p名称空间操作的属性后缀是”-ref”的测试类:
ApplicationContextapplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Personperson = (Person) applicationContext.getBean(Person.class);
System.out.println(person);
}
得到的结果是:
Person [name=张三, dog=Dog [name=国美, color=蓝色]]
我们不仅可以通过在getBean()方法中传入配置中的id或者name , 还可以通过类来取得bean.
SpEl
springexpression language 是在spring3.0以后的版本提供
它类似于ognl或el表达式,它可以提供在程序运行时构造复杂表达式来完成对象属性存储及方法调用等。
Spel表达式的格式 #{表达式}
示例1:完成bean之间的注入
<bean id="person1"class="cn.itheima.namespace.Person">
<property name="name"value="灰头"></property>
<!-- <propertyname="name" ref="dog1"></property> -->
<property name="dog"value="#{dog1}"></property>
</bean>
示例2 支持属性调用及方法调用
让person1的名字与上一个id=person的名字相同,属性调用
让person1的年龄等于id=person的年龄加上10.方法调用
<bean id="person1"class="cn.itheima.namespace.Person">
<property name="name"value="#{person.name}"></property>
<!-- <propertyname="name" ref="dog1"></property> -->
<property name="dog"value="#{dog1}"></property>
<property name="age"value="#{person.getAge()+10}"></property>
</bean>
Spring注解开发
在spring中使用注解,我们必须在applicationContext.xml文件中添加一个标签
<context:annotation-config/> . 作用是让spring中常用的一些注解生效。
要使用contex名称空间,必须在applicationContext.xml文件中引入spring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html\xsd-configuration.html中的
40.2.8 the context schema下的schema配置:直接将整个约束全部拷贝到我们的xml中去就好.并且记得添加一个<context:annotation-config/>在里面 .
<?xml version="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"> <!--bean definitions here -->
</beans>
然后我们就得到如下的配置框架:
<?xml version="1.0"encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!-- bean definitions here-->
<context:annotation-config/>
</beans>
然后我们就可以使用注解来开发了 ;
完成bean注册操作
@Component
测试时报错
原因:如果你使用的是spring3.x那么不会出现这个错误,如果使用的是spring4.x会报错,原因是缺少jar包。
在spring-framework-4.2.4.RELEASE-dist\spring-framework-4.2.4.RELEASE\libs下找到spring-aop-4.2.4.RELEASE.jar导入我们的工程
导入jar后运行还有错误
我们在applicationContext.xml文件中使用了一个标签 <context:annotation-config />,它代表的是可以使用spring的注解,但是我们在类上添加的注解,spring不知道位置。
要解决这个问题,我们可以使用另外一个注解<context:component-sacn base-package=””>它的作用是指定我们spring容器扫描的包, 配置其中的包后, Spring就知道去这个包下扫描注解,而且它包含了context:annotation-config的功能, 所以在开发中一般就不需要再配置上面的<context:annotation-config/>了 ..我们配置如下
<?xml version="1.0"encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!-- bean definitions here-->
<!-- <context:annotation-config/>-->
<context:component-scan base-package="cn.itheima.annotation"></context:component-scan>
</beans>
注解开发的基础演示代码 ;
创建Bean类:
//等效于<bean id="userService"class="cn.itheima.annotation.UserServiceImpl">
@Component("userService")
//@Service("userService")
publicclass UserServiceImpl implements IUserService{
publicvoid add() {
System.out.println("userService add ...");
}
}
publicclass AnnotationTest {
//测试@Component
@Test
publicvoid test1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserServiceuserService = (UserServiceImpl) applicationContext.getBean("userService");
userService.add();
}
结果:控制台输出: userService add ...说明注解环境搭建成功.
在spring2.5后为@Component添加了三个衍生的注解
@Repository 用于DAO层
@Service 用于service层
@Controller 用于表现层
对于我们的bean所处在的位置可以选择上述三个注解来应用,如果你的bean不明确位置,就可以使用@Component.
属性依赖注入
1. 简单的属性注入 (直接在实现类中添加字段并注解就可以了)
@Service("userService")
publicclass UserServiceImpl implements IUserService{
@Value("张三")
private String name ;
publicvoid add() {
System.out.println("userService add ..."+name);
}
} //测试输出我们就可以看见name的值了
2. 复杂的属性注入
我们要在UserServiceImpl类的add方法中注入UserDaoImpl类中的add方法 .
因此UserDaoImpl应该纳入到Spring的管理范围内所以我们要给它添加注解@Respository ,
最后再对UserServiceImpl类中相对复杂的属性注入使用@AutoWired.注意 ,我们此时新建的dao包还没有纳入到spring的扫描范围中去, 所以要在扫描的包列表中添加上dao包.
@Repository("userDao")
publicclass IUSerDaoImpl implements IUSerDao{
publicvoid add() {
System.out.println("userDao add ...");
}
}
@Service("userService")
publicclass UserServiceImpl implements IUserService{
@Value("张三")
private String name ;
@Autowired
private IUSerDao userDao ;
publicvoid add(){
//System.out.println("userService add ..."+name);
userDao.add();
}
}
注意:如果要扫描多个包下的注解可以写成以下:
<context:component-scan base-package="cn.itheima.annotation , cn.itheima.dao"></context:component-scan>
或者
<context:component-scan base-package="cn.itheima”></context:component-scan>
注意:@Value @Autowired它们可以修饰属性,也可以修饰setter方法.
如果写在属性(如上的:name)上,就不需要再给(name字段)提供setter方法。
@Value("张三")
publicvoid setName(String name){
this.name=name;
}
@Autowired它默认是根据类型进行注入(因此可以通过IuserDao类来找到类型注入 , 如果此时容器中有多个IuserDao类型的对象 , 此时AutoWired就不好使了,应该用我们在IuserDao类上的注解名称来注入了)。
如果与@Qualifier一起使用,就可以根据名称来进行注入。
@Autowired //默认是按照类型来进行注入
@Qualifier("userDao")
private IUserDAO userDao;
我们也可以使用下面的方式来根据名称进行属性注入
@Resource(name="userDao")
private IUserDAO userDao;
其它注解(了解)
@Scope它以描述bean的作用域。(其值参考前文的作用域)
@Repository("userDao")
@Scope("singleton")
publicclass UserDAOImpl implements IUserDAO {
给当前的bean指定初始化与销毁方法 ;
以前的xml开发中:
注解开发的配置:
//初始化配置 init-method
@PostConstruct
publicvoid myInit(){
}
它相当于init-method=”myInit
//销毁操作 destroy-method
@PreDestroy
publicvoid myDestroy(){
}
它相当于是destroy-method=”myDestroy”
注意:对于销毁的方法它只对bean的scope=singleton有效。
Spring在web开发中的应用
实现容器只启动一次.把容器的加载权 , 交给了Web.xml的加载实现启动tomcat就初始化容器 , 后面使用的时候从容器中取,而不用次次new 容器.
1.,在web项目中要使用spring需要导入一个jar包 ,路径在源码libs下 的spring-web-4.2.4.RELEASE.jar
2.在web.xml文件中配置Listener
使用ctrl+shift+T搜索ContextLoadListener , 关联到Spring框架源码包下libs里的spring-web-4.2.4.RELEASE-sources.jar . 然后拷贝ContextLoadListener的全路径名到web.xml中配置.
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
作用:将applicationContext存放到Servlet
怎么找的:
这个ContextLoaderListener它实现了ServletContextListener(是监听整个容器的).
在这个listener中,当服务器启动时,将ApplicationContext对象 , 应该说是它的一个实现类WebApplicationContext存入一个ServletContext中. 也就是将WebApplicationContext对象存入到了ServletContext中。我们因此可以实现从ServletContext中就能获得到一个applicationContext对象. .
在ContextLoaderListener.class源码中找到:
@Override
publicvoidcontextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
点进黄色类源码 找到:
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
此处的黄色内容 将会用到我们的Servlet类的getAttrubute()的参数中.
3.我们还需要在web.xml文件中配置applicationContext.xml文件的位置
默认情况下会在WEB-INF目录 下查找applicationContext.xml
如果applicat ionContext.xml文件不是在默认位置,我们可以在web.xml文件中配置全局的 . .
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
Classpath:applicationContext.xml它代表的是在当前工程的类路径下(可以理解成是在src)下来查找applicationContext.xml文件。
contextConfigLocation它是在listener中声明的一个常量,描述的就是spring配置文件的位置。
Spring整合junit4测试
Spring整合junit4可以方便我们的测试。
1. 需要导入一个spring-test.jar包 , 路径在源码包的lib下的spring-test-4.2.4.RELEASE.jar
2. 可以在测试类上如下操作
@RunWith(SpringJUnit4ClassRunner.class) // spring整合junit4
@ContextConfiguration(locations = "classpath:applicationContext.xml") // 指spring配置文件位置
publicclass AnnotationTest {
@Autowired
private IUserService userService;
// 测试@Component , 这是原来的测试方式
@Test
publicvoid test1() {
ApplicationContextcontext = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserServiceuserService = (IUserService) context.getBean("userService");
userService.add();
}
//这个是整合后的测试
@Test
publicvoid test2() {
userService.add();
}
}
@Service("userService")
publicclass UserServiceImpl implements IUserService {
相当于就是将容器的初始化通过注解类来交给Spring, 不用我们new application了 .
注意要getBean()中的类声明时必须@AutoWired , 而且类上也要注解开发.
Spring AOP
AOP概述
在软件业,AOP为AspectOriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承特性语言的缺点,spring2.0之后整合AspectJ第三方AOP技术。
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等
主要意图
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
原理示意代码:
通过代理方法:
实现简化去掉事务开启:
动态代理将 session类中的方法增强 , 也就是在测试类test2中调用session的方法的时候执行的是proxy中增强过的 .
AOP与OOP区别
OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
换而言之,OOD/OOP面向名词领域,AOP面向动词领域。
AOP相关术语
目标对象target
指的是需要被增强的对象,由于spring aop是通过代理模式实现,从而这个对象永远是被代理对象。
连接点(join point)
所谓连接点是指那些被拦截到的点,在spring中这些点指的是方法,因为spring只支持方法类型的连接点
切入点(pointcut)
表示一组 joint point,这些 jointpoint 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方
简单说切入点是指我们要对哪些连接点进行拦截的定义
通知(advice)
所谓通知是指拦截到连接点之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知
Advice 定义了在 pointcut 里面定义的程序点具体要做的操作
引介introduction
引介是一种特殊的通知,在不修改类代码的前提下,introduction可以在运行期为类动态地添加一些方法或属性
切面aspect
是切入点和通知的结合
织入weaving
织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期,类装载期,运行期进行。
Spring采用动态织入,而aspectj采用静态织入
代理Proxy
一个类被AOP织入增强后,就产生一个结果代理类
AOP底层实现
AOP分为静态AOP和动态AOP。静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。动态AOP是指将切面代码进行动态织入实现的AOP。Spring的AOP为动态AOP,实现的技术为: JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)
JDK动态代理
在运行 ,在JVM内部动态生成class字节码对象(Class对象)
Jdk动态代理只针对于接口进行操作
第一个参数:目标类的类加载器对象
第二个参数:目标类的实现接口的Class[]
第三个参数:InvocationHandler它是一个接口,它的作用是是代理实例的调用处理程序实现的接口,接口中定义了一个invoke方法.invoke方法的作用是在代理实例上处理方法调用并返回结果.
我的理解是:原来的对象如果是userServiceImpl, 代理对象为userServiceProxy , 那么userServiceProxy的作用就是自己本身会运行一些步骤, 然后其中有一步是调用userServiceImpl中的方法, 从而实现对原对象的增强处理.
这个InvocationHandler可以在Proxy.newInvocationHandler()中写成匿名内部类的形式, 也可以使用更简洁的方式:让类实现InvocationHandler接口, 重写其中的invoke()方法就行 , 此时的代理方法的第三个参数中传入的是this . 参见下面的代码
目标Target
publicinterface IUserService {
publicvoid login(String username,String password);
publicvoid regist();
}
publicclass UserServiceImpl implements IUserService {
//@Override
publicvoid login(String username,String password) {
System.out.println("登录操作。。。");
}
//@Override
publicvoid regist() {
System.out.println("注册操作...");
}
}
代理工厂:工厂只需要传进对象就能对对象代理 ,通用性 .
面向对象的理解:封装继承多态 .
//创始一个使用jdk的proxy完成动态代理工具
publicclass JDKProxyFactory implements InvocationHandler {
private Object target;
public JDKProxyFactory(Object target) { //有参构造
this.target = target;
}
// 创建代理对象
public Object createProxy() {
// 使用Proxy完成代理对象创建
// 1.得到目标对象的ClassLoader
ClassLoaderloader = target.getClass().getClassLoader();
// 2.得到目标对象的实现接口的Class[]
Class[]interfaces = target.getClass().getInterfaces();
// 3.第三个参数需要一个实现了InvocationHandler接口的对象
return Proxy.newProxyInstance(loader, interfaces, this);
}
// 在代理实例上处理方法调用并返回结果。
// 参数1 就是代理对象,一般不使用
// 参数2它调用的方法的Method对象
// 参数3调用的方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 功能增强操作
System.out.println("日志操作....");
returnmethod.invoke(target, args);
}
}
对this的理解:因为应该传入的是newInvocationHandler() , 但是我们为了将这个匿名内部类的方式写成更简洁的方式 , 将我们的类实现InvocationHandler接口相当于我们自己类的对象就是一个InvocationHandler对象, this指代该类自身对象 , 所以可以传入this来替换new InvocationHandler()的匿名内部类 .
测试类:
publicclass ProxyTest {
// 测试jdk的动态代理
@Test
publicvoid test1() {
// 1.创建目标对象
IUserServiceuserService = new UserServiceImpl();
// 2.通过JKDProxyFactory完成代理对象创建
JDKProxyFactoryfactory = new JDKProxyFactory(userService);
IUserServiceuserServiceProxy = (IUserService) factory.createProxy();
userServiceProxy.login("tom","123");
}
注意:JDK动态代理只能针对接口进行代理的理解; JDK的Proxy的代理与被代理对象是一种兄弟关系 , 也就是他们是实现同一接口, 而使用Cglib的代理与被代理是继承关系 . userServiceImpl不是接口 ,所以JDK动态代理的方式无法做到 .
CGLIB动态代理
CGLIB(CodeGeneration Library)是一个开源项目
是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类
如果你要单独使用CGLIB,那么需要导入cglib的jar包还需要一个asm相关jar包,但是spring框架的spring-core.jar包中已经集成了cglib与asm
注意:jdk的动态代理只可以为接口去完成操作,而cglib它可以为没有实现接口的类去做代理,也可以为实现接口的类去做代理。
Cglib动态代理
publicclass CglibProxyFactory implements MethodInterceptor {
// 得到目标对象
private Object target;
// 使用构造方法传递目标对象
public CglibProxyFactory(Object target) {
this.target = target;
}
// 创建代理对象
public Object createProxy() {
// 1.创建Enhancer
Enhancerenhance = new Enhancer();
// 2.传递目标对象的Class
enhance.setSuperclass(target.getClass());
// 3.设置回调操作 (相当于InvocationHandler)
enhance.setCallback(this);
returnenhance.create();
}
// 相当于InvocationHandler中的invoke
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methdoProxy) throws Throwable {
System.out.println("日志操作....");
returnmethod.invoke(target, args); // 与jdk的proxy中操作类似
// methdoProxy.invoke(proxy,args);
// return methdoProxy.invokeSuper(proxy, args);等效于method.invoke()
}
}
对this的理解; setCallback传递的参数应该是Callback类型, 但是用this其实相当于传的是MethodInterceptor. ,我们使用的是MethodInterceptor
测试类:
//测试cglib动态代理
@Test
publicvoid test2(){
//创建目标对象
IUserServiceuserService = new UserServiceImpl();
//2.使用CglibProxyFactory创建代理对象
CglibProxyFactoryfactory=new CglibProxyFactory(userService);
UserServiceImpl userServiceProxy = (UserServiceImpl) factory.createProxy();
userServiceProxy.regist();
}
需要注意的是为什么Cglib能够声明成UserServiceProxyImpl,而JDK不能. 因为Cglib底层相当于采用继承方案去做的动态代理 , JDK的proxy的代理对象与被代理对象是兄弟关系,就是代理与被代理是实现的同一接口. .
注意:cglib它可以为没有实现接口的类做代理,也可以为接口类做代理.
问题:spring采用的是哪一种动态机制:
如果目标对象,有接口,优先使用jdk动态代理
如果目标对象,无接口,使用cglib动态代理。
Spring AOP编程
4种:
Spring传统aop:
1:基于代理的AOP开发
2:基于aspectJ切点传统开发
Spring整合aspectj:
1:基于xml配置方案
2:基于annotation方案
一.Spring的传统aop编程
讲解的目的是为了更好的理解aop。
在传统的spring aop开发中它支持增强(advice)有五种:
1. 前置通知 目标方法执行前增强 org.springframework.aop.MethodBeforeAdvice
2. 后置通知 目标方法执行后增强 org.springframework.aop.AfterReturningAdvice
3. 环绕通知 目标方法执行前后进行增强 org.aopalliance.intercept.MethodInterceptor
4. 异常抛出通知目标方法抛出异常后的增强org.springframework.aop.ThrowsAdvice
5. 引介通知在目标类中添加一些新的方法或属性(不讲解)
org.springframework.aop.IntroductionInterceptor
经典的基于代理的AOP开发(了解)
基本的jar包
1. bean
2. core
3. context
4. expression
5. aop
6. 需要aop联盟的依赖jar包
位置在:spring-framework-3.0.2.RELEASE-dependencies\org.aopalliance\com.springsource.org.aopalliance\1.0.0下的com.springsource.org.aopalliance-1.0.0.jar
第一步:编写目标(target)
publicinterface IOrderService {
publicvoid add();
publicvoid updateOrder();
}
publicclass OrderServiceImpl implements IOrderService {
@Override
publicvoid add() {
System.out.println("orderService add...");
}
@Override
publicvoid updateOrder() {
System.out.println("orderService update...");
}
}
第二步增强(advice)
//advice
publicclass OrderHelper implements MethodBeforeAdvice,AfterReturningAdvice, MethodInterceptor {
@Override
publicvoid before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知...");
}
@Override
publicvoid afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置通知...");
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("环绕前....");
Objectvalue = mi.proceed();
System.out.println("环绕后....");
returnvalue;
}
}
注意 :实现接口进行相应的通知增强 , 如果没有导入aop联盟的包就会报错 .
第三步在applicationContext.xml文件中配置
第四 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
publicclass AopTest {
@Autowired
@Qualifier("orderServiceProxy")
private IOrderService orderService;
@Test
publicvoid test1() {
orderService.updateOrder();
}
注意: 在@AutoWired的OrderService在applicationContext.xml中有两个实现类, 所以我们要指定名称.
对于指定方法的拦截增强的配置 ;
简化上面的基于代理的aop开发.
将注释掉的部分去掉用选中的部分来替代实现简化 ..
Spring的applicationContext.xml配置文件的引入:
<import resource="./aop2.xml"/>
基于aspectJ切点传统开发
注意1:需要在xml配置文件中导入aop声明的名称空间.
注意2:因为我们使用aspectj的切面声明方式<aop:xxx>标签需要在导入aspectj的jar包
注意1:导入名称空间我们都是自己进行复制红色部分 , 然后修改成黄色部分.
<?xml version="1.0"encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd">
注意2:
Spring\spring-framework-3.0.2.RELEASE-dependencies\org.aspectj\com.springsource.org.aspectj.weaver\1.6.8.RELEASE下的com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
第一步:在spring的配置文件中定义目标与通知
第二步:使用<aop:xxx>标签来完成切面与切点声明
<!-- 目标target -->
<bean id="orderService"class="cn.itheima.aop.OrderServiceImpl"></bean>
<!-- 通知advice -->
<bean id="orderServiceAdvice" class="cn.itheima.aop.OrderHelper"></bean>
<!-- 使用aop标签来完成切面与切点声明 -->
<aop:config>
<!-- 定义切点 -->
<aop:pointcut expression="execution(*cn.itheima.aop.IOrderService.add(..))" id="orderServicePointCut"/>
<!-- 定义切面 -->
<aop:advisor advice-ref="orderServiceAdvice"pointcut-ref="orderServicePointCut" />
<!-- <aop:aspect></aop:aspect>aspectj框架它定义切面使用的 -->
</aop:config>
</beans>
测试类:与上面基于代理的测试类相同.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
publicclass AOPProxyTest {
@Autowired
@Qualifier("orderService")
private IOrderService orderService ;
@Test
publicvoid test1(){
// orderService.updateOrder(); 执行这句不会出现前置通知 , 没有增强此方法
orderService.add(); //执行这句的时候会出现前置通知 , 因为add方法是增强过的
}
传统spring aop开发总结
第一步:编写目标对象(target)
第二步:编写通知(advice)
传统的aop开发中,通知是需要实现指定接口。
第三步 在配置文件中配置切面(切面=切点+通知)
<aop:config>来声明要对aop进行配置
<aop:pointcut>它是用于声明切点(简单说就是对哪些方法进行拦截)
<aop:advisor>定义传统的aop的切面,传统的aop切面它只能包含一个切点与一个增强
<aop:aspect>定义aspectj框架的切面.,它可以包含多个切点与多个通知
关于切点表达式写法
这个语法源于aspectJ的语法,spring中aop开发,对aspectj不是完全支持,只支持部分语法。
在开发中使用的比较多的是execution语法.
关于execution语法常用:
1. execution(public * *()) 所有的public的方法
2. execution(* cn.itheima.aop.*(..)) 所有的aop包下的所有类的方法(不包含子包)
3. execution(* cn.itheima.aop..*(..)) 所有的aop包及其子包下的所有类的方法
4. execution(* cn.itheima.aop.IOrderService.*(..))IOrderService接口中定义的所有方法
5. execution(*cn.itheima.aop.IOrderService+.*(..)) 匹配实现特定接口所有类的方法
6. execution(* save*(..)) 区配所有的以save开头的方法
二.Spring整合aspectj框架实现的aop
有两种方案 ; 基于xml配置方案: 和基于annotation方案:
在现在的开发中使用这种方案比较多.
在spring2.0以后它支持jdk1.5注解,而整合aspectj后可以使用aspectj语法,可以简化开发。
Aspect:切面 =切点+通知(多个切点与多个通知的组合)
AspectJ 它是一个第三方框架,spring从2.0后可以使用aspectJ框架的部分语法.
AspectJ框架它定义的通知类型有6种
1. 前置通知Before 相当于BeforeAdvice
2. 后置通知AfterReturning 相当于AfterReturningAdvice
3. 环绕通知 Around 相当于MethodInterceptor
4. 抛出通知AfterThrowing 相当于ThrowAdvice
5. 引介通知DeclareParents 相当于IntroductionInterceptor
6. 最终通知After 不管是否异常,该通知都会执行
相比spring 的传统AOP Advice多了一个最终通知
二.1基于xml配置方案
第一步:创建目标(target)
第二步:创建通知(增强 advice)
注意:在aspectj中它的增强可以不实现任何接口也就是不implements后面一大堆了,只需要定义出增强功能(方法)
第三步:在spring的xml 配置文件中来配置
<aop:config>下的<aop:aspect>是aspectJ框架用来声明切面的。
前置通知:
<bean name="userService"class="cn.itheima.service.UserServiceImpl"></bean>
<bean name="userServiceAdvice" class="cn.itheima.service.UserServiceHelper"></bean>
<aop:config>
<aop:aspect ref="userServiceAdvice">
<aop:before method="before" pointcut="execution(**.add(..))"/>
</aop:aspect>
</aop:config>
测试代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
publicclass AspectJTest{
@Autowired
@Qualifier("userService")
private IUserService userService ;
@Test
publicvoid test2(){
//userService.update();
userService.add();
}
当执行add()方法的时候会出现前置通知,而其他方法不会增强.
后置通知
<bean name="userService"class="cn.itheima.service.UserServiceImpl"></bean>
<bean name="userServiceAdvice" class="cn.itheima.service.UserServiceHelper"></bean>
<aop:config>
<aop:pointcut expression="execution(* *.del(..))"id="delPointCut"/> //声明出来方便下面重复引用
<aop:aspect ref="userServiceAdvice">
<aop:before method="before"pointcut="execution(* *.add(..))"/>
<aop:after method="afterReturning"pointcut-ref="delPointCut"/> //后置通知
</aop:aspect>
</aop:config>
测试类:
@Test
publicvoid test3(){
userService.del();
}
环绕通知
<aop:config>
<aop:pointcut expression="execution(**.del(..))" id="delPointCut"/>
<aop:aspect ref="userServiceAdvice">
<aop:around method="around" pointcut-ref="delPointCut"/>
</aop:aspect>
</aop:config>
测试类:因为都是同一个切点 , 所以与上面的测试类相同.
异常抛出
在UserServiceHelper类中添加异常抛出通知方法:
//异常抛出通知
publicvoid afterThrowing(){
System.out.println("发现了异常...");
}
在UserServiceImpl类中给del()方法添加出现异常的语句.
publicvoid del() {
System.out.println(10/0);
System.out.println("userService del...");
}
在applicationContext.xml中配置切面
<aop:config>
<aop:pointcut expression="execution(**.del(..))" id="delPointCut"/>
<aop:aspect ref="userServiceAdvice">
<aop:after-throwing method="afterThrowing" pointcut-ref="delPointCut"/>
</aop:aspect>
</aop:config>
注意:目标行为只有抛出了异常后才会执行这个增强方法
测试类;
@Test
publicvoid test3(){
userService.del();
}
最终通知
在UserServiceHelper类中添加方法:
publicvoid after(){
System.out.println("最终通知 ...");
}
Xml中配置
<aop:after method="after" pointcut-ref="delPointCut"/>
无论是否有异常,最终通知都会执行.
关于通知上的参数
1. 在前置通知上可以添加JoinPoint参数
通过它可以获取目标相关的信息
使用前置通知可以完成日志记录,权限控制
2. 在后置通知上添加的参数
第一个参数与上述相同
第二个参数val它可以获取目标方法的返回值
注意:需要在配置文件中配置
测试流程:
UserServiceHelper类的后置通知方法中:
publicvoid afterReturning(Object val){
System.out.println("目标方法返回值:"+val);
System.out.println("后置通知...");
}
测试类:
@Test
publicvoid test4(){
userService.del();
}
此时的返回值因为IuserService接口定义的del()方法返回值为void所以结果为null .如果修改返回值void为其他 , 那么就会有相应的输出.
3. 环绕通知上的参数
它是我们开发中应用最多的,可以完成日志操作,权限操作,性能监控,事务管理
4. 抛出异常通知上的参数
第二个参数Throwable它是用于接收抛出的异常
注意:需要在配置文件中声明
测试方法与后置通知中参数Objectval相似 , 注意别忘了在UserServiceImpl类中给del()方法添加出现异常的语句.
5. 最终通知上的参数
可以使用最终通知完成资源释放
关于代理方式选择
在spring的aop开发中,它使用的是代理方案,代理实现有两种:
1. jdk的proxy
2. cglib
spring框架默认情况下,会对有接口的类使用JDK的proxy代理。没有接口的类使用cglib代理
Proxy-target-class的值默认是false,它代表有接口使用proxy代理
问题:如果现在对目标要使用cglib代理(不考虑是否有接口)?
只需要将proxy-target-class设置为true.
二.2基于annotation方案
第一步:编写目标
@Service("customerService")
publicclass CustomerServiceImpl implementsICustomerService {
publicvoid save() {
System.out.println("customerService save...");
}
publicvoid search() {
System.out.println("customerService search..");
}
publicvoid update() {
System.out.println("customerService update..");
}
}
在spring的配置文件中配置扫描注解
<context:component-scan base-package="cn.itheima"></context:component-scan>
<aop:aspectj-autoproxy/>
第二步:编写增强(advice)
//通知
@Component
@Aspect //声明当前的bean就是一个切面
publicclass CustomerServiceHelper {
//前置通知
@Before("execution(* *.s*(..))")
publicvoid before(){
System.out.println("前置通知...");
}
}
使用@Aspect来声明切面
使用@Before来声明前置通知
注意:必须在spring的配置文件中开启aspectJ注解自动代理功能。
第三步:测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
publicclass AspectAnnotationTest {
@Autowired
@Qualifier("customerService")
private ICustomerService customerService ;
@Test
publicvoid test1(){
customerService.save();
}
}
Search()方法可以增强 , 由于切的是s开头的方法 , 所以测试类中是update()的时候就不会增强.
注意:目标是由切点表达式切到的就是目标. 而不需要指定目标.
其它通知类型及参数
后置通知
//后置通知
@AfterReturning(value="execution(* *.update(..))" , returning="val")
publicvoid afterReturning(JoinPoint jp , Object val){
System.out.println("后置通知 , 目标方法的返回值是:"+val);
}
环绕通知
//环绕通知
@Around("execution(* *.s*(..))")
public Object round(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕前");
Objectvalue = pjp.proceed();
System.out.println("环绕后");
returnvalue ;
}
异常抛出通知
//异常抛出通知
@AfterThrowing(value="execution(* *.s*(..))" , throwing="ex")
publicvoid afterThrowing(JoinPoint jp , Throwable ex){
System.out.println("异常抛出通知:"+ex);
}
最终通知:
//最终通知
@After("execution(* *.s*(..))")
publicvoid after(){
System.out.println("最终通知...");
}
上述代码中存在的问题: 切点所切的范围太大了 , 比如后置通知的方法名字叫sssaaa(), 那我们的前置通知切的是s开头的方法 , 所以会导致在执行后置通知的时候前置通知也会执行导致错误 .
使用@Pointcut注解定义切点
在每一个通知中定义切点,工作量大,不方便维护,我们可以使用@Pointcut来声明切点
切点允许逻辑运算例如mypointcut()||mypointcut1 , 实现对其它的方法的增强 .
关于代理方式选择
Proxy-target-class默认值是false,代表的是如果目标是有接口的使用proxy代理,如果没有接口使用cglib.
以上会默认变动选择 . .
如果将proxy-target-class=true,不管目标是否有接口,都会使用cglib进行代理。
Spring jdbc Template
Spring对持久化层的操作 .
Spring提供了一个jdbc模板,它类似于dbutils工具。
问题:如何使用spring jdbctemplate?
第一:要导入相关的jar包
下面是我们已有的jar包:
在这个基础上我们还需要导入
spring-jdbc-4.2.4.RELEASE.jar
spring-tx-4.2.4.RELEASE.jar
事务相关的包
还需要导入相关的数据库驱动jar包。
这些所有包在spring第一天中都有.
第二:spring jdbctemplate快速入门
快速入门
第一步:导入相关jar包,创建了一个JdbcTemplateTest1测试类
第二步:创建库与表
CREATE DATABASE springtest;
USE springtest;
CREATE TABLE t_user(
id INT PRIMARY KEYAUTO_INCREMENT,
NAME VARCHAR(20),
age INT,
sex VARCHAR(20)
)
INSERT INTO t_user VALUES(NULL,'tom',20,'男');
INSERT INTO t_user VALUES(NULL,'fox',30,'男');
INSERT INTO t_user VALUES(NULL,'tony',40,'男');
SELECT * FROM t_user;
第三步:编码
创建的测试类:
publicclass JDCBCTempletTest1 {
@Test
publicvoid test1(){
//1.创建一个数据库连接池
DriverManagerDataSourcedataSource = new DriverManagerDataSource();
//2.设置DataSource相关的属性
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///springtest");
dataSource.setUsername("root");
dataSource.setPassword("123");
//3.创建一个JdbcTemplate
JdbcTemplatejdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
jdbcTemplate.execute("update t_user set name='张三' where id=1" );
//execute方法可以执行任意sql
}
}
这里我们只需要知道Template可以用来完成代码的持久化层操作.
方式一:配置spring内置默认的连接池DriverManagerDataSource
将new DriverManagerDataSource的创建对象的权利交给Spring ,
其中DriverManagerDataSource的路径是右键复制全路径来的.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="cn.itheima"></context:component-scan>
<bean id="driverManagerDataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql:///springtest"></property>
<property name="username" value="root"></property>
<property name="password" value="123"></property>
</bean>
<bean name="jdbcTemplate"class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource"ref="driverManagerDataSource"></property>
</bean>
</beans>
我们进一步将创建jdbcTemplate的权利交给spring所以添加如下配置:
其中注入了DataSource所以我们不需要在测试类中在注入DataSource属性, 直接注入jdbcTemplate属性就行了, 可以直接调用.
测试类;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
publicclass JDCBCTempletTest2 {
/*@Autowired
privateDriverManagerDataSource dataSource ;
*/
@Autowired
private JdbcTemplate jdbcTemplate ;
@Test
publicvoid test1(){
//使用JdbcTemplate来完成操作
jdbcTemplate.execute("update t_user set name='李三' where id=1" );
//execute方法可以执行任意sql
}
}
方式二:C3P0开源连接池配置
1. 导入c3p0相关的jar包
2. 创建一个ComboPoolDataSource的bean对象,设置相关的属性 ,其中c3p0的class路径可以现在某个类中国new ComboPoolDataSource() ; 来复制全路径 .
<bean id="c3p0DataSource"class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass"value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl"value="jdbc:mysql:///springtest"></property>
<property name="user" value="root"></property>
<property name="password"value="123"></property>
</bean>
<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource"ref="c3p0DataSource"></property>
</bean>
要特别注意其中的三个属性的字段与上一个方法的区别很大.在没有提示的情况下 , 要注意书写 .
@Test
publicvoid test1(){
//使用JdbcTemplate来完成操作
jdbcTemplate.execute("update t_user set name='邱老六' where id=1" );
//execute方法可以执行任意sql
}
引入外部属性文件
Spring支持将经常修改属性,在properties文件中声明,在xml配置文件中引入外部的properties文件的信息。这样就可以方便的实现更换数据库, 而不是将数据库信息直接写到代码中.现在在src下创建db.properties的文件.
其中前面的名字是自己随便定义的.
在applicationContext.xml文件中引入(location是我们properties文件的位置):
在自己配置中需要从properties文件中引入的信息可以使用${name}方式来获取
<!-- 引入外部的properties文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 创建c3p0连接bean -->
<bean id="c3p0DataSource"class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass"value="${jdbc.driverClass}"></property>
<property name="jdbcUrl"value="${jdbc.url}"></property>
<property name="user"value="${jdbc.username}"></property>
<property name="password"value="${jdbc.password}"></property>
</bean>
<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource"ref="c3p0DataSource"></property>
</bean>
测试类:
@Test
publicvoid test1(){
//使用JdbcTemplate来完成操作
jdbcTemplate.execute("update t_user set name='黄光外婆' where id=1" );
//execute方法可以执行任意sql
}
JdbcTemplate CRUD
执行insert update delete操作
只需要使用JdbcTemplate的update方法就可以执行insertupdate delete操作
修改操作:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
publicclass JDCBCTempletTest3 {
@Autowired
private JdbcTemplate jdbcTemplate ;
@Test
publicvoid test1(){
jdbcTemplate.update("updatet_user set name=? where id=?" , "tom" , 1);
}
}
插入操作:
@Test //插入操作
publicvoid test2(){
jdbcTemplate.update("insert into t_user values(null ,?,?,?)" , "仓金控" , 30 , "女");
}
删除操作:
@Test //删除操作
publicvoid test3(){
jdbcTemplate.update("delete from t_user whereid=?" , 4);
}
执行select操作
简单数据返回
其中的String.class是指返回的数据类型.
@Test //测试返回简单数据类型
publicvoid test4(){
String name = jdbcTemplate.queryForObject("select name from t_userwhere id=?",String.class , 2);
System.out.println(name);
}
@Test //测试返回简单数据类型
publicvoid test5(){
Integercount = jdbcTemplate.queryForObject("select count(*) from t_user", Integer.class);
System.out.println(count);
}
复杂数据返回
注意:如果只返回一个domain对象,可以使用queryForObject方法,如果返回的是List<?>对象,可以使用query方法,但是都需要使用RowMapper来对ResultSet进行处理(就是手动的设置查询到的结果集ResultSet如何封装到对象中去)。
首先创建一个User类与数据库中的字段一一对应,提供set/get方法.为了方便显示, 重写toString()方法.
publicclass User {
privateintid ;
private String name ;
private String sex ;
privateintage ;
publicint getId() {
returnid;
然后开始编写测试类:
其中rs.getInt()这些中的参数是数据库表中的字段名称.
@Test //测试返回一个复杂数据类型 -User
publicvoid test6(){
Useruser = jdbcTemplate.queryForObject("select * from t_user whereid=?", new RowMapper<User>(){
public User mapRow(ResultSet rs, intarg1) throws SQLException {
//完成查询id=1的结果集封装到user对象的操作
Useruser = new User() ;
user.setAge(rs.getInt("age")); //其中age是数据表中的字段
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setSex(rs.getString("sex"));
returnuser;
}
} ,1);
System.out.println(user);
}
@Test ////测试返回一个复杂数据类型 -List<User>
publicvoid test7(){
List<User>user = jdbcTemplate.query("select * from t_user", new RowMapper<User>(){
public User mapRow(ResultSet rs, intarg1) throws SQLException {
//完成查询的结果集封装到user对象的操作
Useruser = new User();
user.setAge(rs.getInt("age"));
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setSex(rs.getString("sex"));
returnuser;
}
});
System.out.println(user);
}
上面是一条一条封装查询到的结果集, 现在想将所查到的结果集直接封装成一个set .
RowMapper它有一个实现类叫BeanPropertyRowMapper
如果使用BeanPropertyRowmapper,实体类必须提供一个无参数的public构造方法,类中的bean属性名称与表中的列要对应 . 如果某个属性不对应并不会报错 , 只是某一个属性封装不上而已 . .
注意:这个类是在spring2.5后提供。
@Test //使用BEanPropertyRowMapper来完成结果集到Bean封装
publicvoid test8(){
Useruser = jdbcTemplate.queryForObject("select *from t_user where id=?", new BeanPropertyRowMapper<User>(User.class) , 1);
//List<User> user =jdbcTemplate.query("select * from t_user", newBeanPropertyRowMapper<User>(User.class));
System.out.println(user);
}
Spring 事务管理
案例—转账操作
创建一个关于帐户表
CREATE TABLE account(
id INT PRIMARY KEYAUTO_INCREMENT,
NAME VARCHAR(20),
money DOUBLE
)
INSERT INTO account VALUES(NULL,'tom',1000);
INSERT INTO account VALUES(NULL,'fox',1000);
创建service与dao
对于数据的操作使用spring jdbctemplate
publicclass AccountServiceImpl implementsIAccountService {
private IAccountDAO accountDao ; //提供set/get方法是为了后面配置中的属性注入.
public IAccountDAO getAccountDao() {
returnaccountDao;
}
publicvoid setAccountDao(IAccountDAO accountDao) {
this.accountDao = accountDao;
}
publicvoid account(String outname, String inname, doublemoney) {
//从outname转出money
accountDao.accountOut(outname, money);
//向inname转入money
accountDao.accountIn(inname, money);
}
}
publicclass AccountDAOImpl implementsIAccountDAO {
private JdbcTemplate JdbcTemplate ;
//转出
publicvoid accountOut(String outname, doublemoney) {
JdbcTemplate.update("update account set money=money-?where name=?" , money , outname);
}
//转入
publicvoid accountIn(String inname, doublemoney) {
JdbcTemplate.update("update account set money=money+?where name=?" , money , inname);
}
}
关于service与dao的配置 (才能实现spring的ioc)
<!-- servcie -->
<bean name="accountServiec" class="cn.itheima.service.AccountServiceImpl">
<property name="accountDao"ref="accountDao"></property>
</bean>
<!-- dao -->
<bean name="accountDao" class="cn.itheima.dao.AccountDAOImpl">
<!-- 当注入dataSource后 ,因为DaoImpl类继承了JdbcDaoSuppor, 所以其底层会自动穿件一个JdbcTemplate-->
<property name="dataSource"ref="c3p0DataSource"></property>
</bean>
我们让dao去extends JdbcDaoSupport类,这个类中它创建了JdbcTempate,前提是我们需要注入一个dataSource, 所以我们注入我们的c3p0DataSource后 , 其底层会自动创建一个JdbcTemplate. 所以在继承这个类之后我们在AccountDAOImpl中甚至都不需要写private JdbcTemplate JdbcTemplate ; 了 , 但是在使用的时候就需要使用父类提供的getJdbcTemplate方法来获取。
也就是此时再DaoImpl中我们看不到JdbcTemplate但是其实是有的 , 在父类里是创建了的, 此时简化开发.
下面是源码:
publicclass AccountDAOImpl extends JdbcDaoSupportimplements IAccountDAO {
//转出
publicvoid accountOut(String outname, doublemoney) {
this.getJdbcTemplate().update("update account set money=money-?where name=?" , money , outname);
}
//转入
publicvoid accountIn(String inname, doublemoney) {
this.getJdbcTemplate().update("update account set money=money+?where name=?" , money , inname);
}
}
在dao中在获取JdbcTemplate可以使用父类提供的getJdbcTemplate方法来获取。
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
publicclass AccountServiceImplTest {
@Autowired
private IAccountService accountService ;
@Test
publicvoid testAccount() {
accountService.account("tom", "fox", 500);
}
}
转账操作的问题
在AccountServiceImpl类中给添加上一个异常语句:
//转账操作的方法
publicvoid account(String outname, String inname, doublemoney) {
//从outname转出money
accountDao.accountOut(outname, money);
inta=10/0; //一定会抛出异常
//向inname转入money
accountDao.accountIn(inname, money);
}
如果在转账操作过程中出现问题,那么转账会出现问题,结果如下:一方的钱已转出另一方并没有收到钱.
也就是我们程序需要事务控制。具体解决见下声明式事务管理.
Spring事务管理机制
Spring事务管理的四个优点:
1. 提供一致的对于不同的事务管理的API
2. 支持声明式事务管理(重点)
3. 编程事务管理(在开发中应用比较少)
4. 优秀的整合与Spring的数据访问
我们重点讲解spring的事务管理的相关的API,还有声明式事务管理
Spring事务管理主要提供了三个接口来完成
1. org.springframework.transaction.PlatformTransactionManager
这是一个事务管理器,可以来选择相关的平台(jdbc hibernate jpa…)
2. TransactionDefinition
它定义事务的一些相关信息 例如 隔离 传播 超时 只读
3. TransactionStatus
它主要描述事务具体的运行状态
PlatformTransactionManager
平台事务管理器
在不同的持久化层解决技术它的事务代码不一样。
JDBC开发
Connectioncon=……;
con.setAutoCommit(false);//开启事务
con.rollback();
con.commit();
Hibernate开发
Sessionsession=….;
Transactiont=session.beginTransaction();
t.commit();
t.rollback();
PlatformTransactionManager接口API
DataSourceTransactionManager 主要针对于JdbcTemplate开发 MyBatis开发
HibernateTransactionManasger主要针对于Hibernate开发
JpaTransactionManager 主要针对于JPA开发。
TransactionDefinition
它描述的是事务的定义信息。
在TransactionDefinition中定义了大量的常量
隔离
以下是关于隔离性相关信息
事务的四个特性 ACID 原子性 一致性 隔离性 持久性。
不考虑事务隔离性有什么问题?
脏读,不可重复读 虚读。
ISOLATION_DEFUALT它使用后端数据库的默认隔离级别(spring中选项)
ISOLATION_READ_UNCOMMITTED 不能解决问题,会发生脏读 不可重复读 虚读
ISOLATION_READ_COMMITTED 可以解决脏读 会产生不可重复读与虚读。
ISOLATION_REPEATABLE_READ可以解决脏读,不可重复读解决不了虚读
ISOLATION_SERIALIZABLE串行化,可以解决所有问题
对于不现的数据库,它的底层默认事务隔离级别不一样。
Oracle数据库它默认的是read_committed
Mysql数据库它默认的是repeatable_read.
超时
默认值是-1 它使用的是数据库默认的超时时间。
只读
它的值有两个true/false,如果选择true一般是在select操作时
传播
它解决的是两个被事务管理的方法互相调用问题。它与数据库没关系,是程序内部维护的问题。
以下定义了事务的传播行为
以上操作中最常用的三种:
PROPAGATION_REQUIRED默认值 两个操作处于同一个事务,如果之前没有事务,新建一个事务
PROPAGATION_REQUIRES_NEW
两个操作处于不同的事务
PROPAGATION_NESTED
它是一种嵌套事务,它是使用SavePoint来实现的。事务回滚时可以回滚到指定的savepoint,注意:它只对DataSourceTransactionManager有作用
以下了解
PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常
PROPAGATION_NOT_SUPPORTED 以非事务运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER 当前方法不应该在事务中运行,如果有事务存在,抛出异常 .
TransactionStatus
它定义了事务状态信息,在事务运行过程中,得到某个时间点的状态
声明式事务管理
事务管理方式
1. 编码方案不建议使用,它具有侵入性。在原有的业务代码基础上去添加事务管理代码
2. 声明式事务控制,基于AOP对目标进行代理,添加around环绕通知。
这种方案,它不具有侵入性,不需要修改原来的业务代码
基于xml配置声明式事务管理方案
第一步:在applicationContext.xml文件中添加aop与tx的名称空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
配置方法如前所述 ,复制某句 , 修改context为aop或者tx既可快速实现.
第二步:在applicationContext.xml文件中配置
Spring提供的advice是传统的springadvice
1. 声明事务管理器(我们使用的JdbcTemplate开发所以使用DataSourceTransactionManager管理器.)
Ref的为我们要进行事务管理的数据源.
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource"ref="c3p0DataSource"></property>
</bean>
2. 配置通知
Spring为我们提供了一个TransactionInterceptor来完成增强 , 一下源码说明他提供了环绕通知:
对于这个增强,我们可以使用spring为我们提供的一个标签<tx:advice>来完成操作
其中transaction-manager的事务管理器传入的我们刚才配置的.
3. 配置切面
因为使用的是传统的spring的advice,需要使用<aop:advisor>
注意:要明确此时的配置通知与配置切面中都是对account方法操作, 前者为对account进行事务管理, 后者是对account方法进行增强.如果切面没有切到事务管理的通知, 那事务管理是无法实现的.
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource"ref="c3p0DataSource"></property>
</bean>
<!-- 配置通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="account"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<aop:pointcut expression="execution(*cn.itheima.service.IAccountService.account(..))" id="txPointcut"/>
<aop:advisor advice-ref="txAdvice"pointcut-ref="txPointcut"/>
</aop:config>
通过上面的事务配置后运行测试类后虽然出现错误, 但是不会导致数据库数据的修改.
@Test
publicvoid testAccount() {
accountService.account("tom", "fox", 500);
}
基于annotation声明式事务管理方案
可以使用@Transaction来在类或方法上添加声明式事务管理
注意:出了要配置事务管理器外 , 需要在applicationContext.xml文件中使用事务管理器驱动:
<tx:annotation-driven transaction-manager="transactionManager"/>
相当于开启注解事务控制.(巧记:对于annotation开发相当于孤儿, 一定要在applicationContext.xml中认领.)
可以写在类上,也可以写在方法上.设置隔离级别/传播性配置.
问题:关于xml方式与annotation方式的优缺点?
从简单上来说,使用注解更方便。
使用配置的方案,可以对事务配置进行集中维护(比如使用通配符)。
SSH框架整合
SSh=struts2+spring+hibernate
struts22.3.24
spring4.2.4
hibernate5.0.7
关于xml配置文件的整合方式
SSH整合jar包
Struts2框架需要jar包
Asm 是关于字节码操作
Commons-fileupload关于文件上传
Commons-io关于io流操作工具
Commons-lang也是一个工具,包含了关于数据与字符串操作
Freemaker标签库模板文件
Javassist它也是关于字节码操作,动态代理可以使用它实现(类似于cglib)
Log4j关于日志
Ognl 关于ognl表达式
Struts2-core xwork-cor struts2框架底层是使用xwork
Struts2与spring整合还需要这个包
如果需要使用struts2提供的json处理
注意:如果使用注解方案,我们还需要导入一个jar包
Hibernate框架需要的jar包
Antlr 语法解析包
Dom4j 解析xml
Geronimo-jta apache geronimo它是一个开源javaEE服务器 Geronimo-jta是这个开源项目提供jar包,在hibernate中是关于jta事务相关
Hibernate-commoins-annotations
这个包是我们在hibernate下来使用jpa相关的注解,这样它不依赖于hibernate
Hibernate-core开发hibernate必须
Hibernate-jpa它是关于hibernate对jpa的支持
Jandex 用于索引annotation
Javassist关于字节码操作(注意:strtus2中也引入这个jar包了)
Jboss-logging它是关于jboss统一日志处理
如果使用关于jpa相关操作需要导入jpa依赖jar包
C3p0连接池
还需要数据库相关的驱动jar包
还需要静态日志处理
Spring框架需要的jar包
Spring最基本jar包
AOP开发
Springjdbc
Spring 事务管理需要tx
Spring整合hibernate
Spring整合web开发
如果使用到junit测试
还需要commons-loggin jar包
三个框架总共导入46个jar包.
创建工程完成整合前期准备
首先导入46个jar包.
需要的配置文件:
Strtsu2框架 src/strtus.xml (配置里面的头在项目的struts核心中去找struts-2.0.dtd.)
Hibernate框架 src/hibernate.cfg.xml 在domain有 Xxx.hbm.xml (配置在项目的Hibernate核心下找mapping和configuration的dtd)
Spring框架 src/applicationContext.xml (在源码文件夹下的去找, 简易方式可以在以前的项目中去找.)
关于日志 log4j.properties
关于数据库连接 db.properties
Spring整合hibernate
基本原理:就是由spring来管理hibernate的SessionFactory
方式一:零障碍整合(了解)
我们只需要使用spring中提供的一个LocalSessionFacotry来加载Hibernate的配置文件。
在applicationContext.xml文件中配置 :
<bean id="sessionFactory"class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="configLocation"value="classpath:hibernate.cfg.xml"></property>
</bean>
创建User实体类:提供set/get方法,并重写toString()方法.
publicclass User {
privateintid ;
private String name ;
privateint age ;
publicint getId() {
returnid;
}
publicvoid setId(intid) {
this.id = id;
编写User.hbm.xml配置:
<hibernate-mapping>
<class name="cn.itheima.domain.User"table="t_user">
<id name="id">
<generator class="identity"></generator>
</id>
<property name="name"></property>
<property name="age"></property>
</class>
</hibernate-mapping>
Ssh-xml工程加载到服务器后,如果可以自动创建表,就代表spring整合hibernate 是ok的.(表是在创建sessionFactory这一步的时候创建出的表.)
注意:我们必须配置spring的ContextLoaderListener
<!-- 一下是配置Spring的web.xml启动的listener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
测试的流程:
将项目添加到tomcat , 然后发布 , 查看数据库中是否自动创建了t_user表.
方式二(spring管理hibernate配置)
不在需要hibernate.cfg.xml文件,所有关于hibernate.cfg.xml文件中的配置都在spring的配置文件中来配置。
首先要配置数据源
接下来引入properties文件
db.properties文件:
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///sshtest
jdbc.username=root
jdbc.password=123
创建LocalSessionFactoryBean来完成spring管理hibernate中的SessionFactory
其中注入一个DataSource, 就知道加载操作那个dataSource.
上述的props可以简化成下面方案
加载hbm.xml配置文件的四种方式:
mappingResources它类似于我们之前<mappingresource=””>
mappingLocations它加载时是根据类路径加载classpath:路径
mappingJarLocations它会加载jar文件中的hbm.xml映射文件
mappingDirectoryLocations它加载的目录
完整的整合代码:
<!-- spring整合Hibernate方式2 -->
<!-- 引入数据库信息的properties文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置数据源 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- 加载连接池 -->
<property name="dataSource"ref="dataSource"></property>
<property name="hibernateProperties" >
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
</props>
<!-- 可以简写成一下 -->
<!-- <value>
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.hbm2ddl.auto=update
hibernate.dialect=org.hibernate.dialect.MySQLDialect
</value> -->
</property>
<!-- 加载Hibernate的User.hbm.xml配置文件 -->
<property name="mappingResources">
<list>
<value>cn/itheima/domain/User.hbm.xml</value>
</list>
</property>
<!-- <propertyname="mappingLocations">
<list>
<value>classpath:cn/itheima/domain/User.hbm.xml</value>
</list>
</property>
<propertyname="mappingDirectoryLocations">
<list>
<value>classpath:cn/itheima/domain</value>
</list>
</property>-->
</bean>
<!-- 配置连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass"value="${jdbc.driverClass}"></property>
<property name="jdbcUrl"value="${jdbc.url}"></property>
<property name="user"value="${jdbc.username}"></property>
<property name="password"value="${jdbc.password}"></property>
</bean>
问: 之前的sessionFactory创建的具体步骤.
spring整合hibernate后的DAO
spring整合hiberante后,我们的dao只需要继承HibernateDaoSupport类
在HibernateDaoSupport中只需要注入SessionFactory就可以获得到HibernateTemplate,它是对hibernate操作的一个简单封装,可以让我们方便使用原来hibernate的操作.
Dao层:
publicinterface IUserDAO {
publicvoid add(User user);
publicvoid update(User user);
publicvoid del(User user) ;
public User findById(intid) ;
public List<User> findAll() ;
}
publicclass UserDAOImpl extendsHibernateDaoSupport implementsIUserDAO {
publicvoid add(User user) {
this.getHibernateTemplate().save(user);//session.save()
}
publicvoid update(User user) {
this.getHibernateTemplate().update(user);//session.update()
}
publicvoid del(User user) {
this.getHibernateTemplate().delete(user);//session.=delete()
}
public User findById(intid) {
returnthis.getHibernateTemplate().get(User.class, id);//session.get()
}
public List<User> findAll() {
//原来的Hibernate中session.createQuery(hql).setParameter().list()
return (List<User>) this.getHibernateTemplate().find("from User");
}
}
applicationContext.xml中:
<!-- 声明dao -->
<bean id="userDao" class="cn.itheima.dao.UserDAOImpl">
<property name="sessionFactory"ref="sessionFactory"></property>
</bean>
编写service及测试
publicinterface IUserService {
publicvoid add(User user);
publicvoid update(User user);
publicvoid del(User user) ;
public User findById(intid) ;
public List<User> findAll() ;
}
publicclass UserServiceImpl implementsIUserService {
private IUserDAO userDao ;//提供set方法帮助注入这个属性.
publicvoid setUserDao(IUserDAO userDao) {
this.userDao = userDao;
}
publicvoid add(User user) {
userDao.add(user);
}
publicvoid update(User user) {
userDao.update(user);
}
publicvoid del(User user) {
userDao.del(user);
}
public User findById(intid) {
returnuserDao.findById(id);
}
public List<User> findAll() {
return userDao.findAll();
}
}
<!-- 声明service -->
<bean id="userService" class="cn.itheima.service.UserServiceImpl">
<property name="userDao"ref="userDao"></property>
</bean>
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
publicclass UserServiceImplTest {
@Autowired
private IUserService userService ;
@Test
publicvoid testAdd(){
Useruser = new User();
user.setAge(20);
user.setName("tom");
userService.add(user);
}
在进行测试前我们还要开启事务.
事务管理
<!-- 声明式事务管理 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory"ref="sessionFactory"></property>
</bean> <!-- sessionFactory是前面定义的-->
<!-- 通知, 即对哪些方法进行事务管理增强 -->
<tx:advice transaction-manager="transactionManager" id="txAdvice">
<tx:attributes>
<tx:method name="add"/>
<tx:method name="find*"/>
<tx:method name="update"/>
<tx:method name="del"/>
</tx:attributes>
</tx:advice>
<!-- 切面 , 怎么对方法进行拦截, 声明式事务使用的是传统aop , 所以-->
<aop:config>
<!-- 定义一个pointcut切点 -->
<aop:pointcut expression="execution(*cn.itheima.service.*..*(..))" id="mypointcut"/>
<aop:advisor advice-ref="txAdvice"pointcut-ref="mypointcut"/>
</aop:config>
注意:add()的保存操作必须要开启事务才能保存成功..
其它方法测试:
@Test
publicvoid testFindById() {
Useruser= userService.findById(3);
System.out.println(user);
}
@Test
publicvoid testUpdate(){
Useruser= userService.findById(2);
user.setName("邓昌雨");
userService.update(user);
}
@Test
publicvoid testDel(){
Useruser = userService.findById(2);
userService.del(user);
}
@Test
publicvoid testFindAll(){
List<User>users = userService.findAll();
System.out.println(users);
}
HibernateTemplate API介绍
保存操作 session.save()
修改操作 session.update()
删除操作 session.delete()
类似于session.saveOrUpdate()根据持久化对象的状态来判断执行save或update
获取操作 get() load()
Find操作 类似于session.createQuery().setParameter().list()
类似于hibernate中的QBC查询,完全的面向对象方案
示例代码:
在UserDAOImpl类中添加方法:
public List<User> findAllByCriteria() {
// 先创建一个DetacheCriteria
DetachedCriteriadc= DetachedCriteria.forClass(User.class);
dc.add(Restrictions.gt("age", 10)) ; //年龄>20
return (List<User>) this.getHibernateTemplate().findByCriteria(dc);
}
在UserServiceImpl类中添加相应的方法:
public List<User> findAllByCriteria() {
return userDao.findAllByCriteria();
}
测试类:
@Test
publicvoid testFindByCriteria() {
List<User>users = userService.findAllByCriteria();
System.out.println(users);
}
下面这个可以执行命名查询
可以在User.hbm.xml文件中定义hql或sql
Spring整合struts2框架
前期准备
创建一个addUser.jsp页面
<form action="${pageContext.request.contextPath }/user_add" method="post">
name:<input type="text"name="name">
age:<input type="text"name="age">
<input type="submit"value="addUser">
</form>
</body>
</html>
创建UserAction类
publicclass UserAction extendsActionSupport implements ModelDriven<User>{
private User user = new User() ;
private IUserService userService ;
publicvoid setUserService(IUserService userService) {
this.userService = userService;
}
public User getModel() {
// TODO Auto-generated method stub
return user;
}
public String add(){
userService.add(user);
returnSUCCESS ;
}
}
Struts.xml文件中配置
Spring整合struts2原理分析
1. spring整合struts2框架必须导入一个jar包
struts2-spring-plugin.jar
2. struts2框架配置文件加载顺序
a. default.properties
b. struts-default.xml
c. strtus-plugin.xml
3. 在struts2框架中所有的action interceptor result全是bean,在struts2框架中默认是使用strtus2自己bean初化操作.
4. 当我们在工程中导入struts2-spring-plugin.jar文件
就会加载这个包下的strtus-plugin.xml
这时bean的创建由spring来管理。
5. 这时在创建Action时它执行的代码
上述代码,在执行时,首先会从spring容器中来获取,如果获取不到,会buildBean
通过上述分析,spring整合struts2框架它的方式有两种
1. spring管理action(简单说就是在applicationContext.xml文件中来声明action)
2. action自动装配service
为什么导入plugin插件后创建工厂就由Spring来管理了, 因为加载顺序.
spring整合struts2框架方式一(掌握)
这种方案是基于spring管理action
1. 在applicationContext.xml文件中配置
<!-- 配置action -->
<bean id="userAction" class="cn.itheima.action.UserAction">
<property name="userService"ref="userService" />
</bean>
2. 在action类中,给userService注入提供set方法
publicclass UserAction extendsActionSupport implements ModelDriven<User>{
private User user = new User() ;
privateIUserService userService ;
publicvoid setUserService(IUserServiceuserService) {
this.userService = userService;
}
public User getModel() {
// TODO Auto-generated method stub
returnuser;
}
public String add(){
userService.add(user);
returnSUCCESS ;
}
}
3. 在struts.xml文件
此时的Action的class是applicationContext.xml中配置的action的id ;
<struts>
<package name="default"namespace="/" extends="struts-default">
<action name="user_*"class="userAction"method="{1}">
<result name="success">/success.jsp</result>
</action>
</package>
</struts>
Class的值就是bean的id值
注意:必须在web.xml文件中配置struts2框架的Filter
<!-- 在web.xml中配置struts2的filter -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Spring整合struts2框架方式二(action中自动注入service)
Struts.xml文件中
Class还是写类的全名
这时就会将action类中需要的注入servcie自动注入
publicclass UserAction extendsActionSupport implements ModelDriven<User>{
private User user = new User() ;
private IUserService userService ;
publicvoid setUserService(IUserService userService) {
this.userService = userService;
}
public User getModel() {
// TODO Auto-generated method stub
returnuser;
}
public String add(){
userService.add(user);
returnSUCCESS ;
}
}
我们只需要保证此时Action类中set方法后面的的userService名称与applicationContext.xml中声明的service名称一样即可.
<!-- 声明service -->
<bean id="userService"class="cn.itheima.service.UserServiceImpl">
<property name="userDao"ref="userDao"></property>
</bean>
测试流程:
记得将application中的action配置给注释掉, 以保证是自动注入.
<!-- 配置action -->
<!-- <bean id="userAction"class="cn.itheima.action.UserAction">
<propertyname="userService" ref="userService" />
</bean> -->
然后启动项目 , 登录页面插入user , 看能不能插入成功.成功则说明自动注入成功,Spring整合strut2框架成功.
能成功时因为在default.properties中有一段配置
这时就会根据名称进行autoWires
如果我们修改了Action类中的set方法名称, 就不能正确的注入:
private IUserService userService ;
publicvoid setUs (IUserService userService) {
this.userService = userService;
}
上面的问题我们可以通过修改注入的方式来解决:在struts.xml文件中:
我们修改了struts.xml文件中注入的方式,现在根据type进行注入
<struts>
<constant name="struts.objectFactory.spring.autoWire"value="type"></constant>
<package name="default"namespace="/" extends="struts-default">
<action name="user_*"class="userAction" method="{1}">
<result name="success">/success.jsp</result>
</action>
</package>
</struts>
Spring整合struts2框架总结
1. 如果在struts.xml文件中<action class=”cn.itheima.action.UserAction”>如果写的是全类名,我们使用action自动装配service方案
2. 如果在struts.xml文件中<action class=”userAction”>这时,在applicationContext.xml文件中要配置<beanid=”userAction” class=” cn.itheima.action.UserAction”>
3. 以上操作的前提是必须导入struts2-spring-plugin.xml文件
在这个文件中它改变struts2的bean工厂
4. 默认情况下如果采用action自动装配service方案,这时每一次请求都会新创建一个action,并且service的装配类型是by name
5. 如果我们采用的是spring管理action这种方案我们必须在<bean>声明中添加scope=prototype”,原因是struts2框架的action每一次请求都应该是一个新的action
基于xml的ssh整合总结:
总的applicationContext.xml配置为:
<context:component-scan base-package="cn.itheima"></context:component-scan>
<!--spring整合Hibernate方式1 -->
<!-- <beanid="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<propertyname="configLocation"value="classpath:hibernate.cfg.xml"></property>
</bean> -->
<!-- spring整合Hibernate方式2 -->
<!-- 引入数据库信息的properties文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置数据源 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- 加载连接池 -->
<property name="dataSource"ref="dataSource"></property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
</props>
<!-- 可以简写成一下 -->
<!-- <value>
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.hbm2ddl.auto=update
hibernate.dialect=org.hibernate.dialect.MySQLDialect
</value>-->
</property>
<!-- 加载Hibernate的User.hbm.xml配置文件 -->
<property name="mappingResources">
<list>
<value>cn/itheima/domain/User.hbm.xml</value>
</list>
</property>
<!-- <propertyname="mappingLocations">
<list>
<value>classpath:cn/itheima/domain/User.hbm.xml</value>
</list>
</property>
<propertyname="mappingDirectoryLocations">
<list>
<value>classpath:cn/itheima/domain</value>
</list>
</property>-->
</bean>
<!-- 配置连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass"value="${jdbc.driverClass}"></property>
<property name="jdbcUrl"value="${jdbc.url}"></property>
<property name="user"value="${jdbc.username}"></property>
<property name="password"value="${jdbc.password}"></property>
</bean>
<!-- 声明dao -->
<bean id="userDao" class="cn.itheima.dao.UserDAOImpl">
<property name="sessionFactory"ref="sessionFactory"></property>
</bean>
<!-- 声明service -->
<bean id="userService" class="cn.itheima.service.UserServiceImpl">
<property name="userDao"ref="userDao"></property>
</bean>
<!-- 声明式事务管理 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory"ref="sessionFactory"></property>
</bean>
<!-- 通知, 即对哪些方法进行事务管理增强 -->
<tx:advice transaction-manager="transactionManager" id="txAdvice">
<tx:attributes>
<tx:method name="add"/>
<tx:method name="find*"/>
<tx:method name="update"/>
<tx:method name="del"/>
</tx:attributes>
</tx:advice>
<!-- 切面 , 怎么对方法进行拦截, 声明式事务使用的是传统aop , 所以-->
<aop:config>
<!-- 定义一个pointcut切点 -->
<aop:pointcut expression="execution(*cn.itheima.service.*..*(..))" id="mypointcut"/>
<aop:advisor advice-ref="txAdvice"pointcut-ref="mypointcut"/>
</aop:config>
<!-- 配置action -->
<bean id="userAction" class="cn.itheima.action.UserAction">
<property name="userService"ref="userService" />
</bean>
Struts.xml中的配置(整合struts方式一二的class有别):
<struts>
<constant name="struts.objectFactory.spring.autoWire"value="type"></constant>
<package name="default"namespace="/" extends="struts-default">
<action name="user_*"class="userAction" method="{1}">
<result name="success">/success.jsp</result>
</action>
</package>
</struts>
Hibernate.hbm.xml文件在零障碍整合时才有配置, 为如下配置:
<hibernate-configuration>
<session-factory>
<propertyname="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<propertyname="hibernate.connection.url">jdbc:mysql:///sshtest</property>
<propertyname="hibernate.connection.username">root</property>
<propertyname="hibernate.connection.password">123</property>
<propertyname="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<propertyname="hibernate.show_sql">true</property>
<propertyname="hibernate.format_sql">true</property>
<propertyname="hibernate.hbm2ddl.auto">update</property>
<mappingresource="cn/itheima/domain/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
关于annotation整合方式
Jar包导入
在原来的xml基础上在多导入一个jar包
只有导入这个jar包后,我们才能使用struts2框架的注解 @Namespace @Action
配置文件
Web.xml文件中要配置基本的:Spring启动的listener和struts2的filter .
<!-- 一下是配置Spring的web.xml启动的listener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 在web.xml中配置struts2的filter -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Spring的配置文件applicationContext.xml 以及 db.properties和 log4j.properties .
Struts2的配置文件struts.xml
使用JPA注解来定义PO类
@Entity 定义实体类
@Table 定义表
@Id 主键
@GeneratedValue生成主键策略
@Column 定义列
毫无疑问生成set/get方法就不完整截图了.
@Entity
@Table(name="t_user" , catalog="sshtest")
publicclass User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
privateintid ;
//@Column可以省略 ,也会自动生成列
private String name ;
privateint age ;
publicint getId() {
returnid;
}
publicvoid setId(intid) {
this.id = id;
}……………………………….
Spring整合hibernate
<!-- 引入数据库信息的properties文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass"value="${jdbc.driverClass}"></property>
<property name="jdbcUrl"value="${jdbc.url}"></property>
<property name="user"value="${jdbc.username}"></property>
<property name="password"value="${jdbc.password}"></property>
</bean>
<!-- 声明sessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- 加载连接池 -->
<property name="dataSource"ref="dataSource"></property>
<property name="hibernateProperties">
<value>
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.hbm2ddl.auto=update
hibernate.dialect=org.hibernate.dialect.MySQLDialect
</value>
</property>
<!-- Hibernate的User.hbm.xml配置文件加载处 , 但是这里不进行配置了, 使用annotation的方式去实现加载注解类 -->
<property name="packagesToScan">
<list>
<value>cn.itheima.domain</value>
</list>
</property>
</bean>
黄色部分配置实现去扫描该配置包下的注解.当项目启动的时候这部分就会负责把相应的表给创建出来.其中packagesToScan是固定的名称.
此时我们启动项目就能够自动创建出表了.
Dao编写
如何在dao中得到HibernateTemplate对象,原来是在applicationContext.xml文件中通过配置方案注入了一个SessionFactory对象,UserDao的父类HibernateDaoSupport,会帮助我们根据SessionFactory来得到HibernateTemplate
@Repository("userDao")
publicclass UserDAOImpl extendsHibernateDaoSupport implementsIUserDAO {
@Autowired
@Qualifier("sessionFactory") //这个名称是applicationContext.xml中声明的sessionFactory.
publicvoid setSuperSessionFactory(SessionFactory factory){
super.setSessionFactory(factory); //自己创建的方法, 由父类设置sessionFactory , 与之前的xml开发的意思相同, 相当于是注入一个sessionFactory后父类会自动创建出HibernateTemplate对象给我们使用.
}
publicvoid add(User user) {
this.getHibernateTemplate().save(user);//session.save()
}
publicvoid update(User user) {
this.getHibernateTemplate().update(user);//session.update()
}
Service编写
@Service("userService")
publicclass UserServiceImpl implementsIUserService {
@Autowired
@Qualifier("userDao") //表示注入的是@Repository(“userDao”)这个类,不写也可以 , 会按类型注入,此案例中我们只有一个userDao类.
private IUserDAO userDao ;//提供set方法帮助注入这个属性.
publicvoid setUserDao(IUserDAO userDao) {
this.userDao = userDao;
}
publicvoid add(User user) {
userDao.add(user);
}
注意:对于注解这种孤儿一定要有配置来认领 .
<!-- 在applicationContext中开启注解扫描@Repository , @Service ,@Controller -->
<context:component-scan base-package="cn.itheima"></context:component-scan>
问题:service需要事务管理,怎样处理?
<!-- 声明事务管理器 , 注意Hibernate管理器的版本 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory"ref="sessionFactory"></property>
</bean>
还要开启事务注解驱动.
<!-- 事务注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
然后在service实现类UserServiceImpl类上添加事务注解.或者在其方法上加注解.@Transactional()中可以加属性.
Action编写
@Controller
@Scope("protorype")
@Namespace("/") //相当于xml开发中配置的<action>标签中的namespace
@ParentPackage("struts-default")//相当于xml开发中配置的<action>标签中的extends=
publicclass UserAction extendsActionSupport implements ModelDriven<User>{
private User user = new User() ;
@Autowired
@Qualifier("userService")
private IUserService userService ;
publicvoid setUserService(IUserService userService) {
this.userService = userService;
}
public User getModel() {
// TODO Auto-generated method stub
returnuser;
}
@Action(value="user_add" ,results={@Result(name="success" , location="/success.jsp")})
public String add(){
userService.add(user);
returnSUCCESS ;
}
}
使用@Controller @Scope 注解的作用是由spring管理action,它是一个多例的。
黄色部分是完成struts2框架的流程注解 , 其中value=”user_add”说明页面提交路径为user_add时过来找到的是add()方法.
基于annotation的ssh整合总结
在ssh的annotation整合时,必须要多导入的一个包
对于dao,service,action我们使用
@Repository @Service @Controller来完成bean注册。
在dao中我们使用以下方案将SessionFactory注入,在dao中就可以使用HibernateTemplate
在service及action中使用@Autowire来注入dao及service
一定要在applicationContext.xml文件中开启注解扫描
对于PO类我们使用JPA的注解 @Entiry@Table @Id @GeneratedValue @Column
要在applicationContext.xml文件中配置SessionFactory时来确定扫描包
对于Struts2框架,我们需要@Namespace@Action @ParentPakcage @Result来定义struts2流程
要求action类必须是在actionactions struts struts2这样的包中才会扫描struts2相关注解
总的applicationContext.xml文件:
<!-- 引入数据库信息的properties文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass"value="${jdbc.driverClass}"></property>
<property name="jdbcUrl"value="${jdbc.url}"></property>
<property name="user"value="${jdbc.username}"></property>
<property name="password"value="${jdbc.password}"></property>
</bean>
<!-- 声明sessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- 加载连接池 -->
<property name="dataSource"ref="dataSource"></property>
<property name="hibernateProperties">
<value>
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.hbm2ddl.auto=update
hibernate.dialect=org.hibernate.dialect.MySQLDialect
</value>
</property>
<!-- Hibernate的User.hbm.xml配置文件加载处 , 但是这里不进行配置了, 使用annotation的方式去实现 -->
<property name="packagesToScan">
<list>
<value>cn.itheima.domain</value>
</list>
</property>
</bean>
<!-- 在applicationContext中开启注解扫描@Repository , @Service ,@Controller -->
<context:component-scan base-package="cn.itheima"></context:component-scan>
<!-- 声明事务管理器 , 注意Hibernate管理器的版本 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory"ref="sessionFactory"></property>
</bean>
<!-- 事务注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
SSH整合延迟加载问题解决
出现问题的条件:1,使用load对象, 2session关闭, 3使用延迟对象.
1. 修改UserDao中的findById的get方法为load方法.
2. 添加一个user.jsp页面
3. 在UserAction中处理user_findById请求
以上程序在执行后,报错
解决no session问题:
1. 不使用延迟加载 .(延迟加载能提高性能, 所以不使用不太合理.)
2. 手动将延迟加载初始化 Hibernate.initialize(延迟对象)
3. 使用spring提供的一个OpenSessionInViewFilter来解决
基本的原理就是将session的关闭操作不在service层完成,而是在web层才关闭session.
注意:openSessionInViewFilter一定要在Struts2 Filter前配置.先加载OpenSessionInViewFilter, 再加载struts2 filter..以防struts filter先关闭了session后 ,如果页面需要获取到一些数据显示 ,此时就无法实现了..