Spring简介
Spring是一个开源代码地设计框架,解决的是业务逻辑层和其他各层的松耦合问题,它将面向接口编程的思想贯穿整个系统应用。Spring是于2003年兴起的一个轻量级的Java开发框架,由Rod Johnson创建。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式)轻量级开源框架。
Spring特点
1.降低了组件之间的耦合性, 实现了软件各个层之间的解耦
2.可以使用spring容器提供的服务, 如: 事务管理, 消息服务
3.容器提供单例模式支持
4.容器提供AOP技术, 利用它很容易实现权限拦截, 运行期监控
5.容器提供了众多的辅助类, 能加快应用的开发(org.springframework.jdbc.core.JDBCTemplate 等)
6.Spring对主流的应用框架提供了集成支持, 例如: hibernate,JPA, Struts, Mybatis(IBatis)
7.Spring属于低侵入式设计, 代码污染度极低
8.独立于各种应用服务器
9.Spring的DI机制降低了业务对象替换的复杂性
10.Spring的高度开发性, 并不强制应用完全依赖于Spring, 开发者可以自由选择Spring的部分或者全部
Spring的官网:http://spring.io/
Spring Framework的网址:https://projects.spring.io/spring-framework/
Spring框架下载网址:http://repo.spring.io/release/org/springframework/spring/
Spring体系结构
Spring核心概念
IoC(核心中的核心):Inverse of Control,控制反转。对象的创建权力由程序反转给Spring框架。
AOP:Aspect Oriented Programming,面向切面编程。在不修改目标对象的源代码情况下,增强IoC容器中Bean的功能。
DI:Dependency Injection,依赖注入。在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件中。
Spring容器:指的就是IoC容器。
IoC容器
所谓的IoC容器就是指的Spring中Bean工厂里面的Map存储结构(存储了Bean的实例)。
Spring框架中的工厂
ApplicationContext接口()
实现了BeanFactory接口
实现ApplicationContext接口的工厂,可以获取到容器中具体的Bean对象
BeanFactory工厂(是Spring框架早期的创建Bean对象的工厂接口)
实现BeanFactory接口的工厂也可以获取到Bean对象
通过源码分析,不管是BeanFactory还是ApplicationContext,其实最终的底层BeanFactory都是DefaultListableBeanFactory
ApplicationContext和BeanFactory的区别
创建Bean对象的时机不同:
BeanFactory采取延迟加载,第一次getBean时才会初始化Bean
ApplicationContext是加载完applicationContext.xml时,就创建具体的Bean对象的实例。(只对BeanDefition中描述为是单例的bean,才进行饿汉式加载)
ApplicationContext接口常用实现类
ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件 推荐使用这种
FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置
AnnotationConfigApplicationContext:当使用注解配置容器对象时,需要使用此类来创建 spring 容器,它用来读取注解
Java应用中创建IoC容器
ApplicationContext context = new ClassPathXmlApplicationContext(xml路径);
Web应用中创建IoC容器
web.xml中配置ContextLoaderListener接口,并配置ContextConfigLocation参数
web容器启动之后加载web.xml,此时加载ContextLoaderListener监听器(实现了ServletContextListener接口)
ContextLoaderListener监听器会在web容器启动的时候,触发contextInitialized()方法
contextInitialized()方法会调用initWebApplicationContext()方法,该方法负责创建Spring容器(DefaultListableBeanFactory)
Web三类八种监听器
监听域对象的生命周期:
ServletContextListener:
创建:服务器启动
销毁:服务器正常关闭
spring ContextLoaderListener(服务器启动时负责加载Spring配置文件)
HttpSessionListener
创建:第一次访问request.getHttpSession()
销毁:调用invalidate();非法关闭;过期
ServletRequestListener
创建:每一次访问
销毁:响应结束
监听域对象的属性:(添加、删除、替换)
ServletContextAttributeListener
HttpSessionAttributeListener
ServletRequestAttributeListener
监听HttpSession中JavaBean的改变:
HttpSessionBindingListener(HttpSession和JavaBean对象的绑定和解绑)
HttpSessionActivationListener(HttpSession的序列化,活化、钝化)
源码分析
web服务器(tomcat)启动会加载web.xml(启动ContextLoaderListener监听器):
调用ContextLoaderListener类的contextInitializd方法,创建Web环境中的Spring上下文对象
调用ContextLoader类的initWebApplicationContext方法
继续调用ContextLoader类的configureAndRefreshWebApplicationContext方法,该方法中调用最终初始化Bean的refresh方法
图示
Spring容器初始化源码分析
容器初始化主流程分析
主流程入口:
ApplicationContext context = new ClassPathXmlApplicationContext(“applicationContext.xml”)
ClassPathXmlApplicationContext类:重载的构造方法依次调用,进入下面代码
AbstractApplicationContext的refresh方法:初始化Spring容器的核心代码
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//1、 Prepare this context for refreshing.
prepareRefresh();
//创建DefaultListableBeanFactory(真正生产和管理bean的容器)
//加载BeanDefition并注册到BeanDefitionRegistry
//通过NamespaceHandler解析自定义标签的功能(比如:context标签、aop标签、tx标签)
//2、 Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//3、 Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
//4、 Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
//实例化并调用实现了BeanFactoryPostProcessor接口的Bean
//比如:PropertyPlaceHolderConfigurer(context:property-placeholer)
//就是此处被调用的,作用是替换掉BeanDefinition中的占位符(${})中的内容
//5、 Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
//创建并注册BeanPostProcessor到BeanFactory中(Bean的后置处理器)
//比如:AutowiredAnnotationBeanPostProcessor(实现@Autowired注解功能)
// RequiredAnnotationBeanPostProcessor(实现@d注解功能)
//这些注册的BeanPostProcessor
//6、 Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
//7、 Initialize message source for this context.
initMessageSource();
//8、 Initialize event multicaster for this context.
initApplicationEventMulticaster();
//9、 Initialize other special beans in specific context subclasses.
onRefresh();
//10、 Check for listener beans and register them.
registerListeners();
//创建非懒加载方式的单例Bean实例(未设置属性)
//填充属性
//初始化实例(比如调用init-method方法)
//调用BeanPostProcessor(后置处理器)对实例bean进行后置处理
//11、 Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
//12、 Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
图示
创建BeanFactory流程分析
获取新的BeanFactory子流程
子流程入口(从主流程refresh方法中的第二步开始)
调用AbstractApplicationContext中的obtainFreshBeanFactory方法
调用AbstractRefreshableApplicationContext的refreshBeanFactory方法
加载解析BeanDefinition子流程(loadDefinitions方法)
子流程入口(AbstractRefreshableApplicationContext类的方法)
此处依次调用多个类的loadBeanDefinitions方法(AbstractXmlApplicationContextà AbstractBeanDefinitionReaderà XmlBeanDefinitionReader),一直调用到XmlBeanDefinitionReader 类的doLoadBeanDefinitions方法
对于doLoadDocument方法不是关注的重点,进入到该类的registerBeanDefinitions方法看看
此处有两个地方需要关注的:一个createRederContext方法,一个是DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions方法,先进入createRederContext方法看看
至此,14个NamespaceHandlerResolver初始化成功。然后再进入DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions方法
继续进入到该类的doRegisterBeanDefinitions方法看看,这是真正干活的方法
继续进入parseBeanDefinitions方法
看到有两种解析方案,先看看parseDefaultElement方法
不过重点是看看BeanDefinitionParserDelegate类的parseCustomElement方法(AOP标签、tx标签的解析都是在该步骤中完成的)
getNamespaceURI方法的作用一目了然,就不用去追踪,接下来进入DefaultNamespaceHandlerResolver类的resolve方法看看:
在上面代码中,看到了一行代码:namespaceHandler.init();这个方法是很重要的。它实现了自定义标签到处理类的注册工作,不过NamespaceHandler是一个接口,具体的init方法需要不同的实现类进行实现,通过AopNamespaceHandler了解一下init的作用,其中aop:config标签是由ConfigBeanDefinitionParser类进行处理:
至此,了解到了xml中的aop标签都是由哪些类进行处理的了。不过init方法只是注册了标签和处理类的对应关系,那么什么时候调用处理类进行解析的呢?再回到BeanDefinitionParserDelegate类的parseCustomElement方法看看
最后一行执行了parse方法,那么parse方法,在哪呢?需要到NamespaceHandlerSupport类中去看看,它是实现NamespaceHandler接口的,并且AopNamespaceHandler是继承了NamespaceHandlerSupport类,那么该方法也会继承到AopNamespaceHandler类中。
至此,整个XML文档的解析工作,包括bean标签以及自定义标签如何解析为BeanDefinition信息的过程,已经了解
图示
创建Bean流程分析
子流程入口
进入finishBeanFactoryInitialization方法看看:
继续进入DefaultListableBeanFactory类的preInstantiateSingletons方法,找到下面部分的代码,看到工厂Bean或者普通Bean,最终都是通过getBean的方法获取实例的
继续跟踪下去,进入到了AbstractBeanFactory类的doGetBean方法,这个方法中的代码很多,直接找到核心部分:
接着进入到AbstractAutowireCapableBeanFactory类的方法,找到以下代码部分
终于找到核心的地方了,进入doCreateBean方法看看,该方法关注两块重点区域:
对于如何创建Bean的实例,和填充属性,暂时先不去追踪,先去看看initializeBean方法是如何调用BeanPostProcessor的,因为这个牵扯到对于AOP动态代理的理解
至此,如何创建Bean,以及AOP在哪产生代理的步骤,已经分析过了
IOC基于源码使用
创建工程
导入Jar包
Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
具体实现
编写UserService接口及实现类
public interface UserService {
public void saveUser();
}
import cn.zjut.spring.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public void saveUser() {
System.out.println("保存用户");
}
}
编写XML文件:applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
UserService Bean定义
id:Bean实例id
class:Bean实例化全路径名
-->
<bean id="userService" class="cn.zjut.spring.service.impl.UserServiceImpl"></bean>
</beans>
编写单元测试代码
import cn.zjut.spring.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestDemo {
@Test
public void test(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//从容器中获取Bean实例
//方式一:根据类型从容器中获取Bean实例
//方式二:根据Bean id从容器中获取实例
UserService userService1 = applicationContext.getBean(UserService.class);
UserService userService2 = (UserService) applicationContext.getBean("userService");
userService1.saveUser();
userService2.saveUser();
}
}
bean标签详解
bean标签作用:
Spring创建实例对象
默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功
bean标签属性:
id:给对象在容器中提供一个唯一标识,用于获取对象
class:指定类的全限定类名,用于反射创建对象,默认情况下调用无参构造函数
scope:指定对象是单例还是多例
singleton :默认值,单例的(在整个容器中只有一个对象)
prototype :多例
request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么globalSession 相当于 session
init-method:指定类中的初始化方法名称
destroy-method:指定类中销毁方法名称。比如DataSource的配置中一般需要指定destroy-method=“close”
实例化bean的三种方式
第一种:使用默认无参构造函数(重点)
在默认情况下:它会根据默认无参构造函数来创建类对象
如果 bean 中没有默认无参构造函数,将会创建失败
第二种:静态工厂(了解)
模拟一个静态工厂,创建业务层实现类
import cn.zjut.spring.service.UserService;
import cn.zjut.spring.service.impl.UserServiceImpl;
public class StaticFactory {
public static UserService createUserService(){
return new UserServiceImpl();
}
}
使用 StaticFactory 类中的静态方法 createUserService 创建对象,并存入 Spring 容器
id 属性:指定 bean 的 id,用于从容器中获取
class 属性:指定静态工厂的全限定类名
factory-method 属性:指定生产对象的静态方法
<bean id="userService" class="cn.zjut.spring.factory.StaticFactory" factory-method="createUserService"></bean>
第三种:实例工厂(了解)
模拟一个实例工厂,创建业务层实现类
此工厂创建对象,必须现有工厂实例对象,再调用方法
import cn.zjut.spring.service.UserService;
import cn.zjut.spring.service.impl.UserServiceImpl;
public class InstanceFactory {
public UserService createUserService(){
return new UserServiceImpl();
}
}
先把工厂的创建交给 spring 来管理
然后在使用工厂的 bean 来调用里面的方法
<bean id="instanceFactory" class="cn.zjut.spring.factory.InstanceFactory"></bean>
<bean id="userService" factory-bean="instanceFactory" factory-method="createUserService"></bean>
Spring DI(依赖注入)
依赖指的就是Bean实例中的属性
属性分为:简单类型(8种基本类型和String类型)的属性、POJO类型的属性、集合数组类型的属性
依赖注入:Dependency Injection,它是 Spring 框架核心 IoC 的具体实现
依赖注入的方式(基于XML)
构造函数注入
顾名思义,就是使用类中的构造函数,给成员变量赋值,赋值的操作而是通过配置的方式实现,让 Spring 框架来注入
具体代码:
import cn.zjut.spring.service.UserService;
public class UserServiceImpl implements UserService {
private int id;
private String name;
public UserServiceImpl() {
}
public UserServiceImpl(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public void saveUser() {
System.out.println("保存用户:id为"+id+",name为"+name);
}
}
使用构造函数的方式,给 service 中的属性传值要求:类中需要提供一个对应参数列表的构造函数
涉及的标签:constructor-arg
index:指定参数在构造函数参数列表的索引位置
name:指定参数在构造函数中的名称
value:它能赋的值是基本数据类型和 String 类型
ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
<bean id="userService" class="cn.zjut.spring.service.impl.UserServiceImpl">
<constructor-arg name="id" value="1"/>
<constructor-arg name="name" value="zhangsan"/>
</bean>
set方法注入
set方法注入又分为手动装配方式注入和自动装配方式注入
手动装配方式(XML方式):bean标签的子标签property,需要在类中指定set方法
自动装配方式(注解方式):@Autowired注解、@Resource注解
@Autowired:一部分功能是查找实例,从Spring容器中根据类型(java类)获取对应的实例,另一部分功能就是赋值,将找到的实例,装配给另一个实例的属性值(注意事项:一个java类型在同一个spring容器中,只能有一个实例)
@Resource:一部分功能是查找实例,从spring容器中根据Bean的名称(bean标签的名称)获取对应的实例,另一部分功能就是赋值,将找到的实例,装配给另一个实例的属性值
使用p名称空间注入数据(本质上还是调用set方法)
需要先引入 p 名称空间
在schema的名称空间中加入该行:xmlns:p="http://www.springframework.org/schema/p"
使用p名称空间的语法
p:属性名 = ""
p:属性名-ref = ""
<bean id="car" class="cn.zjut.spring.pojo.Car"></bean>
<bean id="person" class="cn.zjut.spring.pojo.Person" p:pname="张三" p:car-ref="car"></bean>
依赖注入不同类型的属性(基于XML)
简单类型(value)
<bean id="userService" class="cn.zjut.spring.service.impl.UserServiceImpl">
<property name="id" value="1"/>
<property name="name" value="zhangsan"/>
</bean>
引用类型(ref)
<bean id="userDao" class="cn.zjut.spring.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="cn.zjut.spring.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
集合类型(数组)
如果是数组或者List集合,注入配置文件的方式是一样的
<bean id="collectionBean" class="cn.zjut.spring.bean.CollectionBean">
<property name="arrs" >
<!--如果集合内是简单类型,使用value子标签,如果是POJO类型,则使用bean标签-->
<list>
<value>张三</value>
<value>李四</value>
<bean></bean>
</list>
</property>
</bean>
如果是Set集合,注入的配置文件方式如下 :
<property name="sets">
<set>
<value>哈哈</value>
<value>嘻嘻</value>
<value>呵呵</value>
</set>
</property>
如果是Map集合,注入的配置方式如下:
<property name="map">
<map>
<entry key="张三" value="28"/>
<entry key="李四" value="22"/>
<entry key="赵五" value="20"/>
</map>
</property>
如果是Properties集合的方式,注入的配置如下:
<property name="pro">
<props>
<prop key="uname">root</prop>
<prop key="pass">1234</prop>
</props>
</property>
Spring IoC和DI基于注解使用
基于注解的 IoC 配置,即注解配置和 xml 配置要实现的功能都是一样的,都是要降低程序间的耦合,只是配置的形式不一样
IoC注解使用
第一步:spring配置文件中,配置context:component-scan标签
<?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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描注解,扫描包中带注解的类-->
<context:component-scan base-package="cn.zjut.spring.service"/>
</beans>
第二步:类上面加上注解@Component,或者其衍生注解@Controller、@Service、@Repository
import org.springframework.stereotype.Service;
import cn.zjut.spring.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Override
public void saveUser() {
System.out.println("保存用户");
}
}
常用注解
IoC注解(创建对象)
相当于:<bean id="" class="">
@Component注解
作用: 把类让交由Spring 管理,相当于在 xml 中配置一个 bean
属性:value:指定 Bean id
如果不指定 value 属性,默认 Bean id 是当前类的类名,首字母小写。
@Controller、@Service、@Repository注解
这三个注解都是针对@Component的衍生注解
作用及属性都是一模一样的,只不过是提供了更加明确的语义化
@Controller:一般用于表现层的注解
@Service:一般用于业务层的注解
@Repository:一般用于持久层的注解
DI注解(依赖注入)
相当于:<property name="" ref="">
@Autowired
默认按类型装配(byType)
这个注解是Spring自带的
默认情况下要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false)
如果想使用名称装配可以结合@Qualifier注解进行使用
@Qualifier
在自动按照类型注入的基础之上,再按照 Bean id 注入
它在给字段注入时不能独立使用,必须和@Autowire 一起使用;但是给方法参数注入时,可以独立使用
@Resource
默认按照名称(byName)进行装配,名称可以通过name属性进行指定
这个注解属于J2EE
如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,当找不到与名称匹配的Bean时才按照类型进行装配
但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配
推荐使用@Resource注解,因为这个注解是属于J2EE的,减少了与Spring的耦合,这样代码看起就比较优雅
相当于:<property name="" value="">
@Value
给基本类型和String类型注入值
可以使用占位符获取属性文件中的值
@Value(“${name}”)//name是properties文件中的key
private String name;
改变作用范围
@Scope
相当于<bean id="" class="" scope="">
作用:指定 Bean 的作用范围
属性:
value:指定范围的值,取值:singleton prototype request session globalsession
和生命周期相关
相当于:<bean id="" class="" init-method="" destroy-method="" />
@PostConstruct和@PreDestroy
注解和XML配置对比
注解的优势:配置简单,维护方便(找到类,就相当于找到了对应的配置)
XML 的优势:修改时,不用改源码,不涉及重新编译和部署
Spring 管理 Bean 方式的比较:
Spring的纯注解配置
待改造的问题
注解扫描配置(能不能去掉)
非自定义的Bean配置(比如:SqlSessionFactory和BasicDataSource配置)
去掉XML后,如何创建ApplicationContext
新的注解
@Configuration
从Spring3.0开始,@Configuration用于定义配置类,可替换xml配置文件
相当于<beans>根标签
配置类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器
属性:
value:用于指定配置类的字节码
示例代码
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
//Spring容器初始化时,会调用配置类的无参构造函数
public SpringConfig(){
System.out.println("容器初始化配置类");
}
}
import cn.zjut.spring.config.SpringConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestDemo {
@Test
public void test(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
}
}
@Bean
@Bean标注在方法上(返回某个实例的方法),等价于Spring配置文件中的<bean>
作用为:注册bean对象
主要用来配置非自定义的bean,比如DruidDataSource、SqlSessionFactory
属性:
name:给当前@Bean 注解方法创建的对象指定一个名称(即 Bean id)
如果不指定,默认与标注的方法名相同
@Bean注解默认作用域为单例singleton作用域,可通过@Scope(“prototype”)设置作用域
示例代码:
import cn.zjut.spring.service.UserService;
import cn.zjut.spring.service.impl.UserServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
public SpringConfig(){
System.out.println("容器初始化配置类");
}
@Bean
public UserService userService(){
return new UserServiceImpl();
}
}
import cn.zjut.spring.config.SpringConfig;
import cn.zjut.spring.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestDemo {
@Test
public void test(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService1 = applicationContext.getBean(UserService.class);
UserService userService2 = (UserService) applicationContext.getBean("userService");
userService1.saveUser();
userService2.saveUser();
}
}
@ComponentScan
相当于context:component-scan标签
组件扫描器,扫描@Component、@Controller、@Service、@Repository注解的类
该注解是编写在类上面的,一般配合@Configuration注解一起使用
属性:
basePackages:用于指定要扫描的包
value:和basePackages作用一样
示例代码
import org.springframework.stereotype.Service;
import cn.zjut.spring.service.UserService;
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public void saveUser() {
System.out.println("保存用户");
}
}
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "cn.zjut.spring.service")
public class SpringConfig {
public SpringConfig(){
System.out.println("容器初始化配置类");
}
}
@PropertySource
加载properties配置文件
编写在类上面
相当于context:property-placeholder标签
属性:
value[]:用于指定properties文件路径,如果在类路径下,需要写上classpath
示例代码
@Configuration
@PropertySource(“classpath:jdbc.properties”)
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
-
- 创建一个数据源,并存入 Spring 容器中
- @return
*/
@Bean(name="dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver);
ds.setJdbcUrl(url); ds.setUser(username); ds.setPassword(password); return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
properties文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring
jdbc.username=root
jdbc.password=root
@Import
用来组合多个配置类
相当于spring配置文件中的import标签
在引入其他配置类时,可以不用再写@Configuration 注解
属性:
value:用来指定其他配置类的字节码文件
示例代码:
@Configuration
@ComponentScan(basePackages = "cn.zjut.spring")
@Import({ JdbcConfig.class})
public class SpringConfiguration {
}
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig{
}
通过注解获取容器
Java应用(AnnotationConfigApplicationContext)
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
userService.saveUser();
Web应用(AnnotationConfigWebApplicationContext)
<web-app>
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.
support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
cn.zjut.spring.test.SpringConfiguration
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
</web-app>
Spring 分模块开发
表现层:spring配置文件,只想管理表现层的Bean
业务层:spring配置文件,只想管理业务层的Bean,并且进行事务控制
持久层:spring配置文件,只想管理持久层的Bean,并且还有需要管理数据源的Bean
为了方便管理项目中不同层的Bean对象,一般都是将一个Spring配置文件,分解为多个spring配置文件
分解之后的spring配置文件如何一起被加载
一种就是同时指定多个配置文件的地址一起加载
另一种就是:定义一个import.xml文件,通过import标签将其他多个sSring配置文件导入到该文件中,tomcat启动时只需要加载import.xml就可以
Spring整合Junit
具体实现
第一步:添加依赖
添加spring-test包即可
第二步:通过@RunWith注解,指定Spring的运行器
Spring的运行器是SpringJunit4ClassRunner
第三步:通过@ContextConfiguration注解,指定Spring运行器需要的配置文件路径
第四步:通过@Autowired注解给测试类中的变量注入数据
Spring AOP原理分析
AOP介绍
在软件行业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
AOP最早由AOP联盟的组织提出的,制定了一套规范.Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
AOP的作用及优势
作用:
AOP采取横向抽取机制,取代传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
在程序运行期间,不修改源码对已有方法进行增强
将业务逻辑和系统处理的代码(关闭连接、事务管理、操作日志记录)解耦
优势:
减少重复代码
提高开发效率
维护方便
AOP相关术语
Joinpoint(连接点) -- 所谓连接点是指那些可以点。在Spring中,这些点指的是方法,因为Sring只支持方法类型的连接点
Pointcut(切入点) -- 所谓切入点是指要对哪些Joinpoint进行拦截的定义
Advice(通知/增强) -- 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
Introduction(引介) -- 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field
Target(目标对象) -- 代理的目标对象
Weaving(织入) -- 是指把增强应用到目标对象来创建新的代理对象的过程
Proxy(代理) -- 一个类被AOP织入增强后,就产生一个结果代理类
Aspect(切面) -- 是切入点和通知的结合,以后自己来编写和配置的
Advisor(通知器、顾问) --和Aspect很相似
AOP实现之AspectJ
AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器)
可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程初级工程师,上手和使用都非常容易
了解AspectJ应用到java代码的过程(这个过程称为织入),对于织入这个概念,可以简单理解为aspect(切面)应用到目标函数(类)的过程
对于这个过程,一般分为动态织入和静态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术
ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类
Spring之Spring AOP实现原理分析
Spring AOP是通过动态代理技术实现的,而动态代理是基于反射设计的
动态代理技术的实现方式有两种:基于接口的JDK动态代理和基于继承的CGLib动态代理
JDK动态代理
目标对象必须实现接口
public static UserService getProxy(UserService userService){
//newProxyInstance
//第一个参数类目标对象类加载器
//第二个参数目标对象接口
//第三个参数代理对象执行处理器
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始记录日志");
Object object = method.invoke(userService, args);
System.out.println("结束记录日志");
return object;
}
});
return userServiceProxy;
}
CGLib动态代理
目标对象不需要实现接口
底层是通过继承目标对象产生代理子对象(代理子对象中继承了目标对象的方法,并可以对该方法进行增强)
public static UserService getProxyByCglib(UserService userService){
//创建增强器
Enhancer enhancer = new Enhancer();
//设置需要增强的类对象
enhancer.setSuperclass(userService.getClass());
//设置回调函数
enhancer.setCallback(new MethodInterceptor() {
//methodProxy:代理之后的对象方法引用
//object:代理对象
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("记录开始时间");
Object o = methodProxy.invokeSuper(object, args);
System.out.println("记录结束时间");
return o;
}
});
//获取代理对象
UserService service = (UserService) enhancer.create();
return service;
}
使用(了解)
其使用ProxyFactoryBean创建
使用<aop:advisor>定义通知器的方式实现AOP,则需要通知类实现Advice接口
增强(通知)的类型有:
前置通知:org.springframework.aop.MethodBeforeAdvice
后置通知:org.springframework.aop.AfterReturningAdvice
环绕通知:org.aopalliance.intercept.MethodInterceptor
异常通知:org.springframework.aop.ThrowsAdvice
Spring 基于AspectJ的AOP使用
开发阶段
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做
在配置文件中,声明切入点与通知间的关系,即切面。AOP 编程人员来做
运行阶段(Spring 框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行
编写目标类
编写接口和实现类(目标对象)
UserService接口
UserServiceImpl实现类
配置目标类,将目标类交给spring IoC容器管理
<bean id="service" class="cn.zjut.spring.service.impl.UserServiceImpl"></bean>
基于AspectJ的XML实现
添加依赖
<!-- 基于AspectJ的aop依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
编写通知(增强类,一个普通的类)
public class MyAdvice {
public void log(){
System.out.println("记录日志");
}
}
配置通知,将通知类交给spring IoC容器管理
<bean id="myAdvice" class="cn.zjut.spring.advice.MyAdvice"></bean>
配置AOP 切面
<!--AOP配置-->
<aop:config>
<aop:aspect ref="myAdvice">
<!--
method:指定增强方法,即指定通知类增强功能的方法
pointcut:切入点,需要执行表达式来指定
-->
<!--前置通知-->
<aop:before method="log" pointcut="execution(* *..*.UserServiceImpl.saveUser())"/>
</aop:aspect>
</aop:config>
编写测试类
import cn.zjut.spring.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestDemo {
@Autowired
private UserService userService;
@Test
public void test(){
userService.saveUser();
}
}
切入点表达式
切入点表达式的格式:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
execution:必须要
修饰符:可省略
返回值类型:必须要,但是可以使用*通配符
包名:
多级包之间使用.分割
包名可以使用*代替,多级包名可以使用多个*代替
如果想省略中间的包名可以使用 ..
类名:
可以使用*代替
也可以写成*DaoImpl
方法名:
也可以使用*好代替
也可以写成add*
参数:
参数使用*代替
如果有多个参数,可以使用 ..代替
通知类型
通知类型(五种):前置通知、后置通知、最终通知、环绕通知、异常抛出通知
前置通知:
执行时机:目标对象方法之前执行通知
配置文件:<aop:before method="before" pointcut-ref="myPointcut"/>
应用场景:方法开始时可以进行校验
后置通知:
执行时机:目标对象方法之后执行通知,有异常则不执行了
配置文件:<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
应用场景:可以修改方法的返回值
最终通知:
执行时机:目标对象方法之后执行通知,有没有异常都会执行
配置文件:<aop:after method="after" pointcut-ref="myPointcut"/>
应用场景:例如像释放资源
环绕通知:
执行时机:目标对象方法之前和之后都会执行
配置文件:<aop:around method="around" pointcut-ref="myPointcut"/>
应用场景:事务、统计代码执行时机
异常抛出通知:
执行时机:在抛出异常后通知
配置文件:<aop:after-throwing method=" afterThrowing " pointcut- ref="myPointcut"/>
应用场景:包装异常
测试代码
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAdvice {
public void log1(){
System.out.println("前置通知");
}
public void log2(){
System.out.println("后置通知");
}
public void log3(){
System.out.println("最终通知");
}
public void log4(ProceedingJoinPoint joinpoint){
System.out.println("环绕通知前");
try {
joinpoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕通知后");
}
public void log5(){
System.out.println("异常通知");
}
}
<!--AOP配置-->
<aop:config>
<aop:aspect ref="myAdvice">
<!--
method:指定增强方法,即指定通知类增强功能的方法
pointcut:切入点,需要执行表达式来指定
-->
<!--前置通知-->
<aop:before method="log1" pointcut="execution(* *..*.UserServiceImpl.saveUser())"/>
<!--后置通知-->
<aop:after method="log2" pointcut="execution(* *..*.UserServiceImpl.saveUser())"/>
<!--最终通知-->
<aop:after-returning method="log3" pointcut="execution(* *..*.UserServiceImpl.saveUser())"/>
<!--环绕通知-->
<aop:around method="log4" pointcut="execution(* *..*.UserServiceImpl.saveUser())"/>
<!--异常通知-->
<aop:after-throwing method="log5" pointcut="execution(* *..*.UserServiceImpl.saveUser())"/>
</aop:aspect>
</aop:config>
基于AspectJ的注解实现
实现步骤
编写切面类(注意不是通知类,因为该类中可以指定切入点)
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component("myAspect")
@Aspect
public class MyAspect {
@Before("execution(* *..*.UserServiceImpl.*(..))")
public void log(){
System.out.println("前置通知");
}
}
配置切面类
<!--开启注解扫描注解,扫描包中带注解的类-->
<context:component-scan base-package="cn.zjut.spring"/>
开启AOP自动代理
<!--AOP基于注解的配置,自动代理-->
<aop:aspectj-autoproxy/>
环绕通知注解配置
@Around
@Around("execution(* *..*.UserServiceImpl.*(..))")
public Object around(ProceedingJoinPoint joinPoint){
//方法返回值
Object retValue = null;
try {
//获取方法执行所需参数
Object[] args = joinPoint.getArgs();
//执行方法获取返回值
retValue = joinPoint.proceed(args);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return retValue;
}
定义通用切入点
使用@PointCut注解在切面类中定义一个通用的切入点,其他通知可以引用该切入点
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component("myAspect")
@Aspect
public class MyAspect {
@Before("MyAspect.pointCut()")
public void log(){
System.out.println("前置通知");
}
@Around("MyAspect.pointCut()")
public Object around(ProceedingJoinPoint joinPoint){
//方法返回值
Object retValue = null;
try {
//获取方法执行所需参数
Object[] args = joinPoint.getArgs();
//执行方法获取返回值
retValue = joinPoint.proceed(args);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return retValue;
}
@Pointcut("execution(* *..*.UserServiceImpl.*(..))")
public void pointCut(){
}
}
不使用XML的配置方式
@Configuration
@ComponentScan(basePackages="com.zjut.spring")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
AOP源码分析
流程入口(AopNamespaceHandler类)
Spring应用之Spring JDBC实现
JdbcTemplate类使用
导入Jar包:MySQL数据库的驱动包、Spring-jdbc.jar、Spring-tx.jar
编写测试代码
@Test
public void test(){
// 创建连接池,先使用Spring框架内置的连接池
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///bank");
dataSource.setUsername("root");
dataSource.setPassword("1234");
// 创建模板类
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
// 完成数据的添加
jdbcTemplate.update("insert into account values (null,?,?)", "test",10000);
}
Spring管理JdbcTemplate
步骤一:Spring管理内置的连接池
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/bank"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</bean>
步骤二:Spring管理模板类
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
步骤三:编写测试程序
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestDemo {
@Resource(name = "jdbcTemplate")
private JdbcTemplate jdbcTemplate;
@Test
public void test(){
jdbcTemplate.update("insert into account values (null,?,?)", "hebi",19038);
}
}
Spring管理第三方DataSource
管理DBCP连接池
先引入DBCP的2个jar包com.springsource.org.apache.commons.dbcp-1.2.2.osgi.jar、com.springsource.org.apache.commons.pool-1.5.3.jar
编写配置文件
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/bank"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</bean>
管理C3P0连接池
导入com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar
编写配置文件
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/bank"/>
<property name="user" value="root"/>
<property name="password" value="1234"/>
</bean>
使用JdbcTemplate完成增删改查操作
import cn.zjut.spring.pojo.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestDemo {
@Resource(name = "jdbcTemplate")
private JdbcTemplate jdbcTemplate;
//插入操作
@Test
public void test1() {
jdbcTemplate.update("insert into account values (null,?,?)", "luola", 25038);
}
//修改操作
@Test
public void test2() {
jdbcTemplate.update("update account set `name` = ?, `money` = ? where `id` = ?", "wugua", 33038, 16);
}
//删除操作
@Test
public void test3() {
jdbcTemplate.update("delete from account where `id` = ?", 16);
}
//查询一条记录
@Test
public void test4() {
Account account = jdbcTemplate.queryForObject("select * from account where id = ?", new BeanMapper(), 1);
System.out.println(account);
}
//查询多条记录
@Test
public void test5() {
List<Account> list = jdbcTemplate.query("select * from account", new BeanMapper());
for (Account account : list) {
System.out.println(account);
}
}
}
class BeanMapper implements RowMapper<Account> {
public Account mapRow(ResultSet rs, int arg1) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getInt("money"));
return account;
}
}
Spring DAO开发之JdbcDaoSupport
案例设计
编写转账案例(包括业务层和持久层)
编写DAO时引入JdbcDaoSupport的使用
实现
创建工程,导入Jar包
创建对应的包结构和类
public interface AccountDao {
public void updateMoney(String name, int money);
public int queryMoney(String name);
}
import cn.zjut.spring.dao.AccountDao;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import java.sql.ResultSet;
import java.sql.SQLException;
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void updateMoney(String name, int money) {
this.getJdbcTemplate().update("update account set money = ? where `name` = ?",money,name);
}
@Override
public int queryMoney(String name) {
return this.getJdbcTemplate().queryForObject("select money from account where `name` = ?",new IntegerMapper(),name);
}
}
class IntegerMapper implements RowMapper<Integer>{
@Override
public Integer mapRow(ResultSet resultSet, int i) throws SQLException {
return resultSet.getInt("money");
}
}
public interface AccountService {
public void transer(String from, String to, int money);
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transer(String from, String to, int money) {
int fromMoney = accountDao.queryMoney(from);
accountDao.updateMoney(from, fromMoney - money);
int toMoney = accountDao.queryMoney(to);
accountDao.updateMoney(to, toMoney + money);
}
}
引入Spring的配置文件,将类配置到Spring中
<?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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="cn.zjut.spring.service"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/bank"/>
<property name="user" value="root"/>
<property name="password" value="1234"/>
</bean>
<bean id="accountDao" class="cn.zjut.spring.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
编写测试程序
import cn.zjut.spring.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestDemo {
@Autowired
private AccountService accountService;
@Test
public void test(){
accountService.transer("wanger","lisi",100);
}
}
Spring应用之事务支持
事务介绍
事务:指的是逻辑上一组操作,组成这个事务的各个执行单元,要么一起成功,要么一起失败!
事务的特性(ACID)
原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚
一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态
隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离
持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作
事务并发问题(隔离性导致)
在事务的并发操作中可能会出现一些问题:
脏读:一个事务读取到另一个事务未提交的数据
不可重复读:一个事务因读取到另一个事务已提交的数据。导致对同一条记录读取两次以上的结果不一致。update操作
幻读:一个事务因读取到另一个事务已提交的数据。导致对同一张表读取两次以上的结果不一致。insert、delete操作
事务隔离级别
为了避免上面出现的几种情况,在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同
四种隔离级别:
Read uncommitted (读未提交):最低级别,任何情况都无法保证
Read committed (读已提交):可避免脏读的发生
Repeatable read (可重复读):可避免脏读、不可重复读的发生
Serializable (串行化):可避免脏读、不可重复读、幻读的发生
默认隔离级别
大多数数据库的默认隔离级别是Read committed,比如Oracle、DB2等,MySQL数据库的默认隔离级别是Repeatable read
注意事项:
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
Spring框架的事务管理相关的类和API
Spring并不直接管理事务,而是提供了多种事务管理器,将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。 Spring事务管理器的接口是PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情
PlatformTransactionManager接口 -- 平台事务管理器.(真正管理事务的类)。该接口有具体的实现类,根据不同的持久层框架,需要选择不同的实现类!
TransactionDefinition接口 -- 事务定义信息.(事务的隔离级别,传播行为,超时,只读)
TransactionStatus接口 -- 事务的状态(是否新事务、是否已提交、是否有保存点、是否回滚)
总结:上述对象之间的关系:平台事务管理器真正管理事务对象.根据事务定义的信息TransactionDefinition 进行事务管理,在管理事务中产生一些状态.将状态记录到TransactionStatus中
PlatformTransactionManager接口中实现类和常用的方法
接口的实现类
如果使用的Spring的JDBC模板或者MyBatis(IBatis)框架,需要选择DataSourceTransactionManager实现类
如果使用的是Hibernate的框架,需要选择HibernateTransactionManager实现类
该接口的常用方法
void commit(TransactionStatus status)
TransactionStatus getTransaction(TransactionDefinition definition)
void rollback(TransactionStatus status)
TransactionDefinition
事务隔离级别的常量
static int ISOLATION_DEFAULT -- 采用数据库的默认隔离级别
static int ISOLATION_READ_UNCOMMITTED
static int ISOLATION_READ_COMMITTED
static int ISOLATION_REPEATABLE_READ
static int ISOLATION_SERIALIZABLE
事务的传播行为常量(不用设置,使用默认值)
事务的传播行为:解决的是业务层之间的方法调用!!
PROPAGATION_REQUIRED(默认值) -- A中有事务,使用A中的事务.如果没有,B就会开启一个新的事务,将A包含进来.(保证A,B在同一个事务中),默认值!!
PROPAGATION_SUPPORTS -- A中有事务,使用A中的事务.如果A中没有事务.那么B也不使用事务
PROPAGATION_MANDATORY -- A中有事务,使用A中的事务.如果A没有事务.抛出异常
PROPAGATION_REQUIRES_NEW -- A中有事务,将A中的事务挂起.B创建一个新的事务.(保证A,B没有在一个事务中)
PROPAGATION_NOT_SUPPORTED -- A中有事务,将A中的事务挂起
PROPAGATION_NEVER -- A中有事务,抛出异常
PROPAGATION_NESTED -- 嵌套事务.当A执行之后,就会在这个位置设置一个保存点.如果B没有问题.执行通过.如果B出现异常,运行客户根据需求回滚(选择回滚到保存点或者是最初始状态)
Spring框架事务管理的分类
Spring的编程式事务管理(不推荐使用)
通过手动编写代码的方式完成事务的管理(不推荐)
Spring的声明式事务管理(底层采用AOP的技术)
通过一段配置的方式完成事务的管理
编程式事务管理(了解)
说明:Spring为了简化事务管理的代码:提供了模板类 TransactionTemplate,所以手动编程的方式来管理事务,只需要使用该模板类即可!!
手动编程方式的具体步骤如下:
步骤一:配置一个事务管理器,Spring使用PlatformTransactionManager接口来管理事务,所以需要使用到它的实现类
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
步骤二:配置事务管理的模板
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
步骤三:在需要进行事务管理的类中,注入事务管理的模板
@Resource(name = "transactionTemplate")
private TransactionTemplate transactionTemplate;
步骤四:在业务层使用模板管理事务:
import cn.zjut.spring.dao.AccountDao;
import cn.zjut.spring.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Resource;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Resource(name = "transactionTemplate")
private TransactionTemplate transactionTemplate;
@Override
public void transer(String from, String to, int money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
int fromMoney = accountDao.queryMoney(from);
accountDao.updateMoney(from, fromMoney - money);
int i = 1 / 0;
int toMoney = accountDao.queryMoney(to);
accountDao.updateMoney(to, toMoney + money);
}
});
}
}
声明式事务管理(重点)
声明式事务管理又分成两种方式
基于AspectJ的XML方式(重点掌握)
基于AspectJ的注解方式(重点掌握)
事务管理之基于AspectJ的XML方式(重点掌握)
使用
案例:上述转账
配置事务管理的AOP
平台事务管理器:DataSourceTransactionManager
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
事务通知:<tx:advice id=”” transaction-manager=””/>
<!--事务通知-->
<!--tx:advice对应的处理器类是TransactionInterceptor(实现了MethodInterceptor)-->
<!--TransactionInterceptor实现事务是通过transaction-manage属性指定的值进行事务管理-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务相关属性-->
<tx:attributes>
<!--增删改使用REQUIRED事务传播行为-->
<!--查询使用read-only属性-->
<tx:method name="transfer*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
AOP配置:<aop:config><aop:advisor advice-ref=”” pointcut=””/></aop:config>
<aop:config>
<!--aop:advisor使用Spring AOP实现的-->
<!--Spring已经实现了该增强功能,Spring通过实现MethodInterceptor接口的方式实现-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* *..*.*ServiceImpl.*(..))"/>
</aop:config>
事务管理之基于AspectJ的注解方式(重点掌握)
service类上或者方法上加注解:
类上加@Transactional:表示该类中所有的方法都被事务管理
方法上加@Transactional:表示只有该方法被事务管理
开启事务注解:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>