什么是spring
spring的ioc使用
spring的依赖注入
spring的注解
基于xml的简单数据库操作(使用Dbutils)
基于注解的简单数据库操作
spring的aop
spring基于xml配置的aop
spring基于注解的aop
JdbcTemplate
数据源
事务
基于xml的声明式事务的配置
基于编程式事务控制
什么是spring
Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为为核心,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多技术,还能整合众多的第三方框架和类库,
作用
ioc
降低依赖,方便解耦,且只能降低程序的耦合,其他的干不了
过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免编程过程中的过度耦合
包含了依赖注入(DI)和依赖查找(DL)
aop(面向切面编程)
对于声明式事务的支持(后面会说)
方便测试
方便了集成其他的框架
耦合性(Coupling),也叫耦合度,程序间的依赖,类之间的依赖,方法间的依赖,是对模块间关联程度的度量,耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间联系越多,其耦合性越强,划分模块的一个准则就是高内聚低耦合。
此前我们连接数据库是,加载数据库的驱动使用的是Class.forName(“驱动全类名”)
这里使用是反射,但是如果把jar删了,此时的程序编译是不会出问题的,只要在运行时会出问题,这里我们就看出来来,它们和jar包的关系,看起来并不耦合,只是在运行程序时体现出了关系,我们的spring ioc 的解耦就是通过类似的方式实现的,底层是反射
spring的ioc使用
首先导入jar包
spring必须的几个jar包
spring-aop
spring-beans
spring-context
spring-core
spring-expression
maven工程 加入 坐标
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
如果不是maven工程,直接将包加入到类路径就可以了
这里我们使用的是maven工程,在pom.xml文件里使用坐标的方式来导入坐标,
在类路径下创建一个xml文件,名字随意,不要使用中文,我使用的是bean.xml
然后导入约束,根据需要导入相应的约束,这里提供一个地址,可以在这里查看相对
应的约束
https://docs.spring.io/spring/docs/current/spring-framework-reference/
创建Bean的三种方式,
1.使用默认构造函数,在spring的配置文件中使用bean标签,配以id和
class属性之后,且没有其他标签时。
采用的就是默认构造函数创建bean对象,如果此类中没有默认的构造函数,则对象
无法创建,也就是无参构造函数,
如<bean id="studentDao" class="com.dao.Student"></bean>
id是在spring的IOC容器里面的唯一标识符,也是就是对象名,class要全类名,
该类就是把自己创建对象的权力交给了容器,让容器来创建,
这样子来理解控制反转会相对于来说更清晰
2.使用普通工厂的普通方法创建对象,并存入spring容器
<bean id="fa" class="com.it.fact"/>
<bean id="f" factory-bean="fa" factory-method="getFact"/>
使用这种方法,必须先要创建工厂类对象
在来创建工厂返回值中的类对象
factory-method是指定工厂的方法返回值做为对象
public class fact {
public Student getFact(){
return new Student();
}
}
3.使用工厂的静态方法创建对象,使用某个类的静态方法来创建对象并存入
spring容器
<bean id="facts" class="com.it.facts" factory-method="getfacts"></bean>
该创建对象的方式和第二种方式有点像,只不过创建对象的方法是静态方法
我们知道,想要调用一个普通类的静态方法,无须创建该类对象,直接调用方法即
可,这里也是一样,
public class facts {
public static Student getfacts(){
return new Student();
}
}
其实还可以使用注解来创建bean,后面会说
bean的作用范围
bean标签的scope属性:
singleton :单例(默认的)
prototype:多例
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环
境时,他就是session
bean对象的生命周期
bean标签属性:
scope属性取值 :
singleton :单例,默认值
prototype: 多例
request: 作用于web应用的范围
session:作用于web的会话范围
global-session:作用于集群环境的会话范围(全局),如果不是集群,则就是session
单例对象: 当容器创建时对象出生,容器还在,对象就在,容器销毁,对象消亡,单例
对象的生命周期和容器相同
多例对象:当使用对象时spring框架为我们创建,当容器消亡后,我们的bean也依然没有销
毁,只要在使用过程中就不会消亡,但spring并不知道,什么时候不需要使用
了, 那么它是怎么销毁的呢,其实是通过Java的垃圾回收机制回收的
其中 多例是延迟加载,读取文件时不会加载,当使用时才会加载,而单例是立即加
载,所以伴随这容器的消亡而销毁
例
<bean id="seriver" class="com.it.seriver"
scope="singleton" init-method="init" destroy-method="destroy"></bean>
这里我们将创建的bean设置为单例,
init-method设置创建对象时初始化的方法
destroy-method设置对象销毁的方法
public class seriver {
public seriver(){
System.out.println("对象创建");
}
public void init(){
System.out.println("对象初始化");
}
public void destroy(){
System.out.println("对象销毁");
}
}
这时我们来测试一下
首先我们要读取bean.xml的信息
ApplicationContext context=new
ClassPathXmlApplicationContext("bean.xml")
我们这里只是读取了配置文件的信息,其他的什么都没有做
然后运行一下
这个时候控制台打印了
对象创建
对象初始化
这里就可以看出来,单例模式是在配置文件读取后就创建了
但是,我们发现销毁方法并没有执行,这是怎么回事,
那是 ,因为由于可能还没有进行销毁,这个容器就没了
所以,我们可以手动销毁 ,也就是手动关闭容器
由于 ApplicationContext接口没有close方法,所以我们要使用它的实现类
ClassPathXmlApplicationContext来接收,再来调用close方法
就可以实现手动销毁容器了
当我们将单例改成多例时,也就是将某个bean的scope设置成prototype时,然后我们调用了一下colse发现并没有打印出对象销毁的信息,甚至连对象初始化的信息也没有,由此可知,多例对象也是延迟加载,当我们要使用的时侯,才会创建对象,就算容器销毁了,对象也还在,因为换句话说,它压根就不存在,销毁不了
细节说一下:
BeanFactory 和 ApplicationContext 的区别
BeanFactory 才是 Spring 容器中的顶层接口。
ApplicationContext 是它的子接口
他们的区别在于创建对象的时间点不一样
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。,立即加载 ,适用于单例对象
BeanFactory:什么时候使用什么时候创建对象。延迟加载, 适用于多例对象
关于ApplicationContext的三个实现类:
ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件
FileSystemXmlApplicationContext:
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置(但要有访问权限)。
AnnotationConfigApplicationContext:
当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取
注解。注意,这里的读取注解是读取配置类的
spring的依赖注入(DI)
分为三种
可分为三类
1.基本类型和string类型
2.其他bean类型(在配置文件中或注解过的bean)
3.复杂类型,集合类型
注入的方式
构造方法注入
set方法注入
使用注解
1.构造器注入
使用标签:constructor-arg,在bean的标签内部使用
其实还有其他的标签属性:
type: 用于指定要注入的数据的数据类型,也就是构造函数的参数的谋个类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引的位置从0开始
name:用于指定构造函数中参数的名称
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean,也就是除了基本类型和string和集合类型的数据类型,只要是sprong ioc管理的对象都可以
优势:创造对象时,必须要有参数,
缺点:想要创建该对象,必须要把把所有的参数都传过去
如.
<bean id="student" class="com.Student">
<constructor-arg name="age" value="17"></constructor-arg>
<constructor-arg name="data" ref="data"></constructor-arg>这里就是注入下面创建的bean
</bean>
<bean id="data" class="java.util.Date">
</bean>
2.set注入(首先要有set方法)
<bean id="student2" class="com.Student2">
<property name="age" value="16"></property>
</bean>
这里使用的是无参构造函数来创建的对象
优势:创建对象时没有明确限制,可以直接使用默认构造函数
缺点:如果谋个成员有值,则获取对象有可能set方法没有执行
集合属性的注入(也要提供set方法)
<bean id="student3" class="com.Student3">
<property name="list" >
<list>
<value>ss</value>
<value>aa</value>
</list>
</property>
<property name="array">
<array>
<value>ss</value>
<value>cc</value>
</array>
</property>
<property name="set">
<set>
<value>aa</value>
<value>bb</value>
</set>
</property>
其中set,array(数组),list标签可以相互使用,没有区别
下面是map
<property name="map">
<map>
<entry key="a" value="aaa"></entry>
<entry key="b">
<value>bbb</value>
</entry>
</map>
</property>
</bean>
注意:spring ioc容器是 map结构的
3.使用注解
spring的注解
1.创建对象
作用和bean标签的实现功能时相同的
@Component:将当前类对象创建存入spring容器中
属性: value ;指定bean的id,不写时,默认的id是类名,首字母小写
@Controller :一般表现层
@Service:一般业务层
@Repository :一般持久层
这四种的属性作用一摸一样,后面三种是为了更好的区分三层
2.数据注入
和property标签时相同的作用
@Autowired:自动按照类型注入,直接跳过ioc的key,去寻找要注入的类型value,注意,ioc容器是map结构的,
只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就
可以注入成功可以出现在变量上,方法上,和类上, set方法不是必须的
细节: 当有多个相同属性的bean对象时,首先会通过变量名来注入,寻找相同的名称的bean对象,如果没有,且没有指定,则会报错,
这时如果我们不想修改变量名,可以使用这么一个注解 @Qualifier ,他有一个value属性,来指定bean对象的名称
@Qualifier:
作用:在按照类中注入的基础上再按照名称注入,它在类成员注入时不能单独使用,但是再给
方法参数注入时可以单独使用
属性:value:用于指定注入bean的id
@Resource:
作用 :直接按照bean的id注入,他可以单独使用,比较方便
属性: name: 用于指定bean的id
以上三个标签注入都只能注入其他的bean类型的数据,不能实现String和基本类型的注入,
注意:集合类型只能通过xml文件才能注入,注解不支持
基本类型和String类型使用注解的注入
@Value:
作用:用于注入基本数据类型和string类型
属性: value:用于指定数值的值,他可以使用spring中SpEL(也就是Spring的EL表达式
写法 : ${表达式} 。
注解改变bean的作用范围
和bean标签的scope属性一样
@ Scope
作用:指定bean的作用范围
属性:value:指定范围的取值,常用取值:singleton, prototype ,对应的是单例和多例,默认是单例
生命周期:
和bean标签的init-method,destroy-method作用一样
@PreDestroy: 用于指定销毁方法
@PostConstruct: 用于指定初始化方式
写在对应要设置的方法上面
当然,这里我们要注意一下,由于我们使用了注解的方式来创建bean对象,所以在配置文件里要扫描这些定义的类所在的包
首先我们要导入context的约束,
然后再配置如
<context:component-scan base-package=“com.it”></context:component-scan>
当有多个不同的包需要扫描时,中间使用逗号隔开就可以了。
基于xml的简单数据库操作
接下来我们来实现一下账户的CRUD
此次我们会使用到Dbutils开源工具类库
DbUtils(org.apache.commons.dbutils.DbUtils)是Apache组织提供的一个对JDBC进行简单封装的开源
工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能
我们这里使用 DbUtils的QueryRunner类,它是其核心类之一
我们的QuertRunner是作为持久层的解决方案,
接下来我们来一个举例,
首先再pom.xml文件里导入坐标(jar包)
spring-context
commons-dbutils,刚才介绍要使用的
mysql-connector-java 数据库的驱动包,这里我选的是mysql的驱动包
c3p0 我们此次使用的是该jar包里提供的数据源,所以包要导入
本次还需要两个包,这两个jar包是为了我们的测试可以使用注解的方式来读取配置类或配置文件
junit 4.1.2
spring-text 5.0.2.RELEASE
首先创建一个实体类Account,setter(),getter(),toString()都得有
创建持久层的接口
public List<Account> findAllAccount();
public Account findAccountByid(Integer id) ;
public void saveAccount(Account account) ; 我们就用这三个方法吧,
创建实现子类AccountDaoimp
public class AccountDaoimp implements IAccountDao{
private QueryRunner runner; //由于要再该实现类里使用,所以我们提供set方法,方便数据注入
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public List<Account> findAllAccount() {
try{
return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
//所有的quert的返回值都是泛型的,Account.class是指封装到哪个类类型里面
这里由于返回的是List类型,所以使用BeanListHandler
} catch(Exception e){
throw new RuntimeException(e);
}
}
public Account findAccountByid(Integer id) {
try {
return runner.query("select * from account where id= ?", new BeanHandler<Account>(Account.class), id);
由于查询的返回值不是List,所以不能使用上面的BeanListHandler,要使用 BeanHandler,id是参数
}catch(Exception e){
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try {
runner.update("insert into account(id,name)values(?,?)", account.getId(), account.getName());
这里我们使用的是update方法,来插入一条数据,
}catch(Exception e){
throw new RuntimeException(e);
}
}
}
注意:查询使用的是query方法,增删改使用的是update方法,
接下来我们创建业务层
接口IAccountService
List<Account> findAllAccount();
Account findAccountByid(Integer id);
void saveAccount(Account account);
实现子类
AccountServiceImpl
public class AccountServiceImpl implements IAccountService {
private IAccountDao IAccountdao; //数据层接口属性
public void setIAccountdao(IAccountDao IAccountdao) {
this.IAccountdao = IAccountdao;
} 由于业务层要调用持久层的方法,所以我们在这里要第一一个属性,并且提供set方法
public List<Account> findAllAccount() {
return IAccountdao.findAllAccount();
}
public Account findAccountByid(Integer id) {
return IAccountdao.findAccountByid(id);
}
public void saveAccount(Account account) {
IAccountdao.saveAccount(account);
}
}
在后来,我们来配置一下bean.xml配置文件的信息
首先我们将业务层的bean对象创建一下
<bean id="accountservice" class="com.nanchang.service.imp.AccountServiceImpl">
<!--注入Dao-->
<property name="IAccountdao" ref="accountdao"></property>
</bean>
此时它又依赖于持久层的注入
持久层注入
<bean id="accountdao" class="com.nanchang.dao.imp.AccountDaoimp">
<!--注入QuertRunnrt-->
<property name="runner" ref="runner"></property>
</bean>
它又依赖于QuertRunnrt
配置QuertRunnrt
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--设置为多例,-->
<!--注入数据源, 注意,没有set方法,只能使用构造方法注入-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
这里的name有两个参数可选,我们只要选择ds就可以了,指定数据源
这里的构造方法的参数也是QueryRunner继承抽象父类AbstractQueryRunner的
</bean>
它又依赖于数据源的注入
配置数据源
由于在到jar包坐标时,说了,我们使用的是c3p0里面的数据源,这里使用的是
ComboPooledDataSource
bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/nanchang?characterEncoding=utf-
8"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
好了,配置好xml文件后,我们开始写测试类了
定义一个测试类AccountText
其实我们可以像之前一样,使用
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml")
来读取配置文件的信息
但这里我们也可以进行简化一下,使用注解的方法来完成
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations ="classpath:bean.xml")
//这里是加载xml配置文件的,后面会讲加载配置类的
注意在这里,要想使用这两个注解,必须是spring-text和junit兼容
这里的 spring-text5.0.2的,junit必须的是4.12及以上的才可以
public class AccountText{
由于我们是测试,所以我们就直接调用业务层的方法就可以了,
当然,我们学了spring后,不使用传统的new来创建对象了
而是使用注入的方式来做
@Autowired 由于只要一个该属性的bean对象创建,所以可以使用该标签注入
private IAccountService accountservice=null;
@Test 这里使用的是junit包的test
public void findAllAccount(){
System.out.println(accountservice.findAllAccount());
}
@Test
public void findAccountByid(){
System.out.println(accountservice.findAccountByid(1));
}
@Test
public void saveAccount(){
Account a=new Account();
a.setId(4);
a.setName("小小");
accountservice.saveAccount(a);
}
}
然后我们运行一下三个方法,发现都没有问题,测试通过了
其实上面的数据库信息我们是已经写死了的,也就是说,如果一旦数据库的对应信息修改了,我们要直接修改xml文件,这样是很不好的
所有我们可以使用一个外部文件来装这些数据库信息,我们修改也就只是修改外部文件, 我们定义一个后缀为properties的文件,存放在resources下面,建包放都可以
如
jdbc.driverclass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/nanchang?characterEncoding=utf-8
jdbc.username=root
jdbc.passeord=123456
然后再xml文件引用这些信息
首先我们要导入context的约束
然后再xml里面添加一句
<context:property-placeholder location="classpath:jdbc.properties"></context:property-
placeholder> classpath表示再类路径里,后面的是文件路径,如果有包,要把包名写上
再数据源里面引用就这样子引用就可以了
<property name="driverClass" value="${jdbc.driverclass}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property> 这里我们只写两个示例
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
基于注解的简单数据库操作
部分注解
此次我们并非是全部是注解,而是部分注解,后面,我们会讲解全注解该怎么注解
首先我们在持久层定义该注解,
@Component("accountdao")
public class AccountDaoimp implements IAccountDao {
@Resource(name="runner") 这里我们要注入属性,其他的不变
private QueryRunner runner;
....}
接下来是业务层
@Component("accountservice")
public class AccountServiceImpl implements IAccountService {
@Resource(name="accountdao") 注入持久层属性
private IAccountDao IAccountdao;
.....}
此时在xml中给持久层和业务层定义的bean就可以删了,
但是,删了后,我们只是在类前面加了一个注解而已,spring并不知我在哪里添加
了该注解,所以我们要指定spring去那些包里面去扫描
<context:component-scan base-package="com.nanchang"> 那些注解的类所在的包,当有多个包时,我们使用逗号将它们分隔开
</context:component-scan>
此时的xml文件里还有QueryRunner 和数据源信息了
运行程序,依然可以正常运行
----------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------
基于全注解的配置
首先我们之前配置的持久层和业务层的注解不需要修改
我们现在是要把xml文件里面剩余的信息给提取出来
#此时我们创建一个包,里面创建一个类如SpringConfigurstion
@Configuration //定义该类为配置类
@ComponentScan(value = "com.nanchang") 要扫描的包,当只有一个属性是,可以不写属性名,
当有多个时,需要使用{}括起来
@PropertySource("classpath:jdbcConfig.properties") //引用外部的数据库信息
public class SpringConfigurstion {
之前我们说了,基本类型和String使用value注入 ,还可以使用spring的EL表达式
@Value("${jdbc.Driver}")
private String driverclass; 数据源要使用到的信息,我们来定义一下
Value("${jdbc.url}")
private String url;
@Value("${jdbc.user}")
private String user;
@Value("${jdbc.password}")
private String password; 因为上面我们引入了外部文件,所以加载后就可以获得值了
我们要有一个QueryRunner对像,定义一个方法,返回值是QueryRunner的
如
@Bean(name="runner") 我们要给他一个id,不设置名称,默认id是方法名
@Scope("prototype") 我们将它设置为多例
public QueryRunner createQueryRunnr(@Qualifier("datasource1") DataSource
datasource){
return new QueryRunner(datasource);这里我们将数据源传到这里,使用构
造创建
}
这里的@Qualifier("datasource1")注解,将指定的数据源注入我们说了,在
类上使用时不能单独使用,但在给方法参数注入时,可以单独使用
如果我们不使用该注解的话,当有很多的数据源 我们的参数名就也要跟着改
动,这显然是不好,所以我们就是要这种方式注入,
注意:当只有一个数据源时,无所谓写不写@Qualifier,参数名同不同都无所谓
但当有多个数据源时,要么参数名要和想用的数据源名相同,要么就是使用
@Qualifier注解, 其实这不单单说的是数据源的问题,这指的是所有的ben对象都是如此的
接下来我们要创建数据源对象
也是创建一个方法,返回一个数据源对象
@Bean("datasource1")
public DataSource createDataSource2(){
try {
//因为我们使用的是该数据源,所以我们new一个并在下面进行相应的赋值
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(Driver);
ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf-
8");
ds.setUser(user);
ds.setPassword(password);
return ds;
}catch(Exception e){
throw new RuntimeException();
}
}
}
其实我们还可以设置父子配置类,
也就是多个配置类建立关系,使用了@Import注解
@Import(jdbcConfig.class) //加载子配置类,这样就是父子关系的配置类,
使用了import的就是父配置类,被引用的就是子配置类,当然也可以不引用,但在
AnnotationConfigApplicationContext的参数里要加入该配置类,如果被父配置类引用的话,参数配置了父类,子类配置类就可以不写了
然后再测试类里面,我们来测试一下
此时我们不在是读取xml文件了,而是读取配置类
ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfigurstion.class);
//该构造方法的参数是可变参数,参数的内容就是寻找使用了@Configuration标签的类
这里我们也可以使用注解的方式来读取配置类
@RunWith(SpringJUnit4ClassRunner.class) //这里将junit原有的main给换了,括号里面的写法要是
字节码文件的形式,由于式Spring提供的,所以里面有读取容器的操作
@ContextConfiguration(classes =SpringConfigurstion.class ) 配置类.class
之前我们也使用了该标签来读取配置文件信息,这里我们来读取配置类,
只不过要把ContextConfiguration里面的参数改一下
spring的aop
在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式
aop(面向切面编程):通过预编译的方式和运行期动态代理实现功能
简单来说说就是抽取重复的代码块,在需要执行的时候使用动态代理技术,在不修
改源码的基础上,对方法进行加强
作用:
在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码
提高开发效率
维护方便
如事务的控制,因为我们是在业务层进行事务的提交和回滚,那么每一个调用方法都要进行重复的操
作,这显然是不理智的,虽然可以实现事务,但整个业务层都变的臃肿了
动态代理的特点
字节码随用随创建,随用随加载。
它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。
装饰者模式就是静态代理的一种体现。
aop相关术语
Joinpoint(连接点):所谓连接点是指那些被拦截到的点
Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类
动态地添加一些方法或 Field。
Target(目标对象):代理的目标对象。
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,
而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面):是切入点和通知(引介)的结合。
spring中基于xml的aop配置步骤
导入aspectjweaver.jar包,还有一些其余的spring必备jar包
我们的切入点表达式需要使用到这个包,
使用aop配置这个jar包就ok了
1.把通知bean交给spring来管理
2.使用aop:config标签表明开始aop的配置
3.使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一的标识
ref属性:是指通知类bean的ID
4.在aop:aspect标签的内部使用对应标签来配置通知的类型
我们现在配置在指定的切入点方法执行之前执行,是前置通知
aop:before:表示配置前置通知.
mnethod:用于指定通知类中哪个方法作为前置通知
pointcut:用于指定切入点表达式,该表达式的含义是对业务层的那些方法加强
切入点表达式的写法:
关键字:execution(表达式)
表达式: 访问修饰符 返回值 包名...包名..类名..方法名(参数列表)
返回值可以使用通配符表示,* ,表示返回任意值
包名可以使用通配符,表示任意包,有几个写几个点
还可以使用 .. 表示当前包及其子包
类名和方法名都可以使用*来实现通配
参数:
基本类型直接写名称 int,引用类型写包名.类名的方法 如java.lang.string(全限定类名)
可以使用通配符表示任意类型
可以使用..表示有无参数均可,有参数可以是任意类型
spring基于xml配置的aop
好了,接下来我们来操作一下(注意,代码是没有意义的,只是为了演示功能)
定义一个类我们叫它为通知类
public class tongzhi {
//前置通知
public void Beforeadvice(){
System.out.println("调用切入点方法前执行");
}
//后置通知
public void AfterReturning(){
System.out.println("调用切入点方法后执行");
}
//异常通知
public void AfterThrowing(){
System.out.println("切入点方法发生异常后执行");
}
//最终通知
public void After(){
System.out.println("最终通知:不管切入点方法是否异常都会在最后执行");
}
}
然后定义一个接口,但为什么我们要定义接口呢,那是因为我们使用的是动态代理,其
中一种方式是需要类必须实现一个及以上的接口的,所以我们定义一个接口,
public interface IAccount {
public void save();
public int update();
public int dalete(int i);
}
实现类
public class Accountimpl implements IAccount {
public void save() {
System.out.println("保存方法");
}
public int update() {
System.out.println("修改方法");
return 1;
}
public int dalete(int i) {
System.out.println("删除方法");
return 2;
}
}
开始xml的配置
首先一个将创建的两个类注入到容器里,构造注入
<bean id="accountimpl" class="com.service.Accountimpl"></bean>
<bean id="tz" class="com.utils.tongzhi"></bean>
接着
****注意 这里我们需要加入aop的约束
<!--配置aop-->
<aop:config>
<aop:pointcut id="id" expression="execution(* com.service.*.*(..))"/>
<!--此时将切入点表达式放在切面的外面-->
<!--配置切面-->
<aop:aspect id="tzadvice" ref="tz"> 引用通知类
<!--配置通知的型,并且建立通知方法和切入点方法的关联-->
<aop:before method="Beforeadvice" pointcut="execution(* com.service.*.*(..))"></aop:before>
前置通知:通知类的Beforeadvice方法,
<aop:after-returning method="AfterReturning" pointcut-ref="id"></aop:after-returning>
<!--通过 pointcut-ref来引用id配置好的切入点表达式-->
<aop:after-throwing method="AfterThrowing" pointcut-ref="id" ></aop:after-throwing>
<aop:after method="After" pointcut-ref="id"></aop:after>
</aop:aspect>
</aop:config>
测试的时候,我们只要调用这些方法,相应的执行这些通知
上面我们写的是前置,后置,最终,异常通知,
其实还有一个环绕通知
<aop:config>
<aop:pointcut id="id" expression="execution(* com.service.*.*(..))"/>
<!--配置切面-->
<aop:aspect ref="tz">
<aop:around method="aroundadvice" pointcut-ref="id"></aop:around><!--环绕通知-->
around环绕通知
</aop:aspect>
</aop:config>
来看看通知类的环绕通知方法是怎么写的
public Object aroundadvice(ProceedingJoinPoint pj){ 该类是spectjweaver包提供的类
Object value=null;
try{
Object[] args=pj.getArgs();//得到方法执行的参数
System.out.println("环绕通知的前置通知");
value=pj.proceed(args); 调用方法执行 ,写在这个前面就是前置通知,后面就是后置通知
System.out.println("环绕通知的后置通知");
return value;
}catch (Throwable t){
System.out.println("环绕通知的异常通知");和后置通知两个只会执行其中一个
throw new RuntimeException(t);
}finally {
System.out.println("环绕通知的最终通知"); 不管会不会成功执行,都会执行的通知
}
}
spring基于注解的aop
我们将上面的代码进行一下修改
我们的通知类
@Component("tz")
@Aspect //表明是一个切面类(哎斯百可特)
public class tongzhi {
@Pointcut("execution(* com.service.*.*(..))") //在此处定义切入点表达式
private void gets(){}
前置
@Before(“execution(* com.service.*.*(..))”)这里可以设置切入点表达式
@Before("gets()")也可以直接引用设置好的表达式
public void Beforeadvice(){
System.out.println("调用切入点方法前执行");
@AfterReturning("gets()") 后置
@AfterThrowing("gets()")异常
@After("gets()") 最终
添加在各自的通知里,这里就不写了
}
}
//但是使用aop的注解,
被加强的方法的后置通知,和最终通知被系统执行的时候顺序会变化,所以使用的时候这里我们要
注意一下
但如果就想使用注解的方式来使用aop的话,我们可以使用环绕通知,它的通知执行顺序是不会有问题
的,因为都是我们手动写好顺序的
//环绕通知
@Around("gets()")
public Object aroundadvice(ProceedingJoinPoint pj){//(呸 shei ding )
Object value=null;
try{
Object[] args=pj.getArgs();//得到方法执行的参数
System.out.println("环绕通知的前置通知");
value=pj.proceed(args);
System.out.println("环绕通知的后置通知");
return value;
}catch (Throwable t){
System.out.println("环绕通知的异常通知");
throw new RuntimeException(t);
}finally {
System.out.println("环绕通知的最终通知");
}
}
这个时候我们在xml配置文件里面配置的通知类,切面,切入点就可以删了
只添加扫描的包,还有一个,就是要开启spring对aop注解的支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
这里是在配置文件里开启的,当我们以后使用配置类的时候
@EnableAspectJAutoProxy 该注解也是开启spring对aop注解的支持
记得要加入约束
然后测试就ok了
JdbcTemplate
JdbcTemplate主要提供以下五类方法:
execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;
batchUpdate方法用于执行批处理相关语句;
query方法及queryForXXX方法:用于执行查询相关语句;
call方法:用于执行存储过程、函数相关语句。
这是spring提供的,在spring-jdbc包里,对原始 Jdbc API 对象的简单封装,spring 框架为我们提供了很多
的操作模板类
使用JdbcTemplate,
必须要有spring-jdbc-5.0.2.RELEASE.jar和spring-tx-5.0.2.RELEASE.jar(这个是和事务相关的)
这是jdbctemplate的构造方法
public JdbcTemplate() {
}
public JdbcTemplate(DataSource dataSource) {
this.setDataSource(dataSource);
this.afterPropertiesSet();
}
public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
this.setDataSource(dataSource);
this.setLazyInit(lazyInit);
this.afterPropertiesSet();
}
我们可以使用含有数据源参数的构造,
这时我们创建一个类jdbcsoupper
public class jdbcsoup {
private JdbcTemplate jdbctemplate;
public void setJdbcTemplate(JdbcTemplate jdbctemplate) {
this.jdbctemplate = jdbctemplate;
}
在xml里面,我们要对它进行对象的创建,别忘了,加上数据源属性的注入
<bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
但是我们为什么要这样重新在一个,这是为了我们在有很多的实体类,很多的持久层时,方便其他
的调用,
现在我们的情况就是,我们要在持久层里就来定义一个
private JdbcTemplate jdbctemplate;属性,并且提高set方法注入
并且我们也要注入属性
<bean id="accountimpl" class="com.nanchang.dao.impl.Accountimpl">
<property name="jdbctemplate" ref="jdbctemplate"></property>
</bean>
然后再来看看我们的持久层中执行的sql语句
例
List<Account> account=jdbctemplate.query("select * from accounts where id=?",new
BeanPropertyRowMapper<Account>(Account.class),id);
这里的jdbctemplate就是我们注入的属性
注意返回的是集合类型,list
new BeanPropertyRowMapper<Account> 定义返回的类型并且要限定泛型,
例,保存操作
public void saveaccount(Account account) {
jdbctemplate.update("insert into
accounts(name,money)values(?,?)",account.getName(),account.getMoney());
}
//查询一行一列,要使用queryForObject方法
public int selectcount(long i) {
Long l=jdbctemplate.queryForObject("select count(*) from accounts where
money>?",Long.class,i);
return l.intValue(); //这里穿的参数是是什么就是什么类型的字节码
}
但是spring-jdbc里面已经帮我们提供了jdbctemplate,我们只要在我们的持久层里让实现类继承JdbcDaoSupport(抽象类)
如
public class Accountimpl extends JdbcDaoSupport implements IAccount{....}
我们进入JdbcDaoSupport的源码看一下,有一个jdbctemplate属性,由于我们继
承了该类,所以我们也要配置该属性,而Jdbctemplate类里也要配置数据源对
象,所以配置就应该该是
<bean id="accountimpl" class="com.nanchang.dao.impl.Accountimpl">
<property name="jdbcTemplate" ref="jdbctemplate"></property>
</bean>
<bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
这样子继承该抽象类的原因是因为当有多个持久层时,就要写多次jdbc模板属性,很不方便,因此这样简化
数据源这里就不写了。。。
然后我们在持久层的调用sql语句的方法那里改一下,
如:
List<Account> account=getJdbcTemplate().query("select * from accounts where
name=?",new BeanPropertyRowMapper<Account>(Account.class),name);
我们把JdbcTemplate换成了getJdbcTemplate(),和之前一样的效果,那是因为我
们继承JdbcDaoSupport,里面提供了该方法,返回的也是jdbctemplate,
*数据源*
1. c3p0
我们之前使用的数据源有ComboPooledDataSource,
com.mchange.v2.c3p0.ComboPooledDataSource全限定类名
是第三方c3p0jar包的类
数据库信息是通过set方式注入的,我们也可以去查看源码,
2.dbcp
org.apache.commons.dbcp.BasicDataSource全限定类名
需要使用到第三方commons-pool.jar和commons-dbcp.jar
3.spring-jdbc
spring自带的数据源,在spring-jdbc包中,
DriverManagerDataSource,
org.springframework.jdbc.datasource.DriverManagerDataSource全限定类名
也都可以设置固定的数据库信息,也都可以引用外部的文件,如properties
<context:property-placeholder location="classpath:jdbc.properties"/>引用外部文件
事务
JavaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理
解决方案
spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式
实现
Spring 中事务控制的 API 介绍
1. PlatformTransactionManager 此接口受窘事务的管理器,
该接口提供事务操作的方法,包括由3个具体的操作
*** 获取事务状态信息
TransactionStatus getTransaction(TransactionDefinition definition)
***提交事务
void commit(TransactionStatus status)
***回滚事务
void rollback(TransactionStatus status)
在开发中都是使用它的实现类
如
org.springframework.jdbc.datasource.DataSourceTransactionManager
使用 Spring JDBC 或 iBatis 进行持久化数据时使用
2.事务的定义信息对象
***获取事务对象名称
String getName()
***获取事务隔离级别
int getlsolationLevel()
***获取事务的传播行为
int getPropagationBehavior()
***获取事务超时时间
int getTimeout()
***获取事务是否只读
boolean isReadOnly()
读写型事务:增加,删除,修改开启事务
只读事务:执行查询时,也会开启事务
3.事务的隔离级别
事务隔离级别反映事务提交并发访问时的处理态度
--ISOLATION_DEFAULT
默认级别,归属下列某一种
--ISOLATION_READ_UNCOMMITTED
可以读取未提交数据
--ISOLATION_READ_COMMITTED
只能读取已提交数据,解决脏读问题(oracle默认级别)
--ISOLATION_REPEATABLE_READ
是否读取其他事务提交修改后的数据,解决不可重复读问题(mysql默
认级别)
--ISOLATION_SERIALIZABLE
是否读取其他事务提交添加后的数据,解决幻读的问题
补充:
脏读:
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,
而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,
然后使用了这个数据。也就是说读取的是一个未提交的数据,叫做脏读
不可重复读:
是指在一个事物里,对相同的数据进行多次读取,此时另一个事物恰巧修改了该数据,
而第一个事物再次读取的时候,确是和第一次读取的数据时不一样的
避免,当只有 cud,操作完后,在进行读事物
幻读:
是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,
这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,
这种修改是向表中插入一行新数据。
那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,
就好象发生了幻觉一样。
解决办法:
如果在操作事务完成数据处理之前,任何其他事务都不可以添加新数据,则可避免该问题
不可重复读的重点在于,是修改的数据,两次读取的数据不一致
幻读的重点在于;数据的增加或删除
4.事务的传播行为
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务
中,加入到这个事务中。一般的选择(默认值)
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行
(没有事务)
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当
前事务挂起
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,
则执行 REQUIRED 类似的操作。
超时时间
默认值是-1,没有超时限制,如果有,以秒未单位进行设置
是否是只读事务
建议查询时设置为只读
5.TransactionStatus
该接口提供的是事务具体的运行状态
描述了某个时间点上事务对象的状态信息,包含6个具体的操作
--刷新事务
void flush()
--获取是否存在存储点
boolean hasSavepoint()
--获取事务是否完成
boolean isCompleted()
--获取事务是否为新事物
boolean isNewTransaction()
--获取事务是否回滚
boolean isRollbackOnly()
--设置事务回滚
void setRollbackOnly()
基于xml的声明式事务的配置
需要导入jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
由于还用到了aop所以
<dependency> 也需要导入
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
spring中基于XML的声明式事务控制配置步骤
1、配置事务管理器
2、配置事务的通知
此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
3、配置AOP中的通用切入点表达式
4、建立事务通知和切入点表达式的对应关系
5、配置事务的属性
是在事务的通知tx:advice标签的内部
<!--spring中基于xml的声明式事务控制配置具体实现-->
<!--配置事务管理器-->
<bean id="manager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
里面有一个数据源属性,我们需要注入,
数据源
<bean id="datasource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.passeord}"></property>
</bean>
<tx:advice id="txadvice" transaction-manager="manager"> 记得要导入约束
<!--配置事务的通知引用事务管理器-->
<!--配置事务的属性-->
isolation:用于指定事务的隔离级别,默认值式DEFAULT,表示使用数据库的默认隔离级别
propagation:用于指定事务的传播行为,默认值是REOUIRED,表示一定会有事务,增删改
的选择,查询方法可以选择SUPPORTS
read-only:用于指定事务是否只读,只是查询方法才能设置为true,默认值是false表示不只
读
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时,如果指定了数值,以秒为
单位
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回
滚,没有默认值,表示任何异常都回滚
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回
滚,没有默认值,表示任何异常回滚
<tx:attributes> 除查询外的操作
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
<!--用于我们的查询,因为有返回值-->
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<!--配置切入点表达式--> 配置我们的业务层方法上
<aop:pointcut id="pt1" expression="execution(* com.nanchang.service.impl.*.*(..))"/>
<!--建立切入点表达式和事务通知的关系-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
使用注解的方式配置事务
首先bean.xml里面的事务管理器和数据源不要动
<!--配置事务管理器-->
<bean id="manager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
然后在业务层上使用@Transactional注解
该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口
然后需要在web.xml文件里
<!-- 开启 spring 对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
如果不使用xml文件的话
在配置类上使用@EnableTransactionManagement 表示开启注解对事物的支持
全注解的方式
@Configuration //表示是一个配置类
@ComponentScan({"com.nanchang","com.config"}) //表示需要扫描的包
@Import({JdbcConfig.class,TransactionManage.class})
//表示读取子配置类的信息,是以字节码的形式读取的
@PropertySource("jdbc.properties") //读取外部文件的数据库的配置信息
@EnableTransactionManagement //开启spring对注解事务的支持
public class SpringConfig {
}
该配置类JdbcConfig就配置一些数据源和jdbctemplate模板
TransactionManage类配置事务管理器
@Bean("manager")
public PlatformTransactionManager createPlatformTransactionManager(DataSource
datasource){ //这里的
System.out.println("1234");
return new DataSourceTransactionManager(datasource);
}
注意:当配置了多个数据源时,要保证模板和事务的数据源是同一个,如果不是同一个,也不会
报错,只是相当于没有配置事务一样,是很不安全的,所以这里要注意下
业务层则和之前的一样要使用@Transactional
基于编程式事务控制
首先在bean.xml文件里配置数据源,
持久层和业务层配置一下
事务管理器
<bean id="manager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--配置事务模板对象 -->
<bean id="transactionTemplate1"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="manager"></property> <!--事务管理器对象-->
</bean>
将模板注入到业务层中
<bean id="accountservice" class="com.nanchang.service.impl.Accountserviceimpl">
<property name="accountdao" ref="accountimpl"></property>
<property name="transactionTemplate" ref="transactionTemplate1"></property>
</bean>
当然,我们之前就要自写好 transactionTemplate的set方法,方便注入
这写都弄好后
在我们业务层方法中
使用我们定义好的属性transactionTemplate去调用execute方法,该方法有一个参数
TransactionCallback<T> ,它是一个接口,要么使用它的子类,要么自定义,这里,一般我们
使用的是匿名内部类
transactionTemplate.execute(new TransactionCallback<Object>() {
public Object doInTransaction(TransactionStatus transactionStatus) { 实现接口方法
这里就是写我们要调用的持久层的方法及其一些其他操作
}
});
}
大概就是这么个流程,这就是编程式事务控制,
但是,要注意,编程式事务处理,业务层的所有方法要有事务都必须这样做
使用该transactionTemplate类型的属性去调用execute方法,然后创建匿名内部类,
实现方法,调用持久层方法,