Spring学习笔记_韩顺平
Spring学习笔记_韩顺平
[toc]
1 Spring基本介绍
1.1 Spring学习的核心内容
基本介绍:Spring是一个管理框架的框架,可以理解成就是一个超大的容器
说明:spring是一个管理框架的框架,因此他可以以各种组合的方式来集成Struts2,SpringMVC,Hibernate、MyBaties,
为了让大家灵活掌握各种方式,加深对Spring框架的特点,我们将会给大家演示不同的整合方式。
在实际开发中,完全可能是SS/SH/SSH的方式。
1.2 Spring的相关概念
1.Spring是可以整合其他框架的框架(是管理框架的框架)
2.Spring有两个核心概念:IOC 和 AOP
3.IOC(控制反转)
传统的编程:
程序--读取--->环境(配置信息)----->业务操作
Spring设计理念:
程序 <----- 环境(创建好对象)
4.DI,依赖注入,可以理解为IOC的另外叫法
5.==Spring最大的价值,就是可以接管Struts2,MyBatis,通过配置,给程序提供需要使用的Action/Service/Dao对象,==
==这个是核心价值所在,也是IOC的具体体现。==
2 Spring快速入门
案例说明:通过Spring的IOC容器【可以理解成就是beans.xml文件】创建对象,并给对象属性赋值。
开发步骤:(看老师演示一遍)
通过IOC容器配置一个Monster对象(bean),并且通过Java程序来获取这个bean,并输出信息。
步骤: 安装一把Spring开发插件
。开发Spring的IOC容器beans.xml,该文件放在src目录
点击 src->new-->spring bean config -->名字 beans.xml --->勾选 beans命名空间
3.1 第1步 新建项目
File-->New-->Project...
3.2 第2步 选择Spring
在弹出的界面中,选择Spring,勾选“Spring”,
勾选“Create empty spring-config.xml”,创建空的spring bean文件,
选择“download”,
点击“Next”
3.3 第3步 输入项目名称
在弹出的界面中,输入项目名称,
选择项目路径,
点击“Finish”
3.4 第4步 开发Monster.java
package com.tangguanlin.bean;
* 说明:
* 作者:汤观林
* 日期:2022年03月06日 22时
public class Monster {
private Integer id;
private String nickname;
private String skill;
public Monster() {
System.out.println("Monster被加载...");
public Monster(Integer id, String nickname, String skill) {
this.id = id;
this.nickname = nickname;
this.skill = skill;
public Integer getId() {
return id;
public void setId(Integer id) {
this.id = id;
public String getNickname() {
return nickname;
public void setNickname(String nickname) {
this.nickname = nickname;
public String getSkill() {
return skill;
public void setSkill(String skill) {
this.skill = skill;
@Override
public String toString() {
return "Monster{" +
"id=" + id +
", nickname='" + nickname + '\'' +
", skill='" + skill + '\'' +
3.5 第5步 配置bean文件
beans.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">
<!--配置一个Monster bean -->
<bean id="monster01" class="com.tangguanlin.bean.Monster">
<property name="id" value="100"></property>
<property name="nickname" value="牛魔王"></property>
<property name="skill" value="芭蕉扇"></property>
</bean>
</beans>
3.6 第6步 测试类IOCTest
IOCTest.java
package com.tangguanlin.controller;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
* 说明:从beans.xml容器中获取一个monster
* 作者:汤观林
* 日期:2022年03月06日 23时
public class IOCTest {
public static void main(String[] args) {
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取monster[通过id获取monster]
Object bean = applicationContext.getBean("monster01");
System.out.println(bean);
项目结构:
运行结果:
Monster被加载...
Monster{id=100, nickname='牛魔王', skill='芭蕉扇'}
3 Spring配置bean
说明:本章讲解的内容就是IOC容器中如何配置bean的各种方法,
以及如何给配置的bean注入属性值。
为什么这样就把所有的bean加载了,到底怎么加载的?
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
3.1 通过类型类获取bean
前面快速入门案例是通过id来获取bean,还可以通过类型来获取bean对象。
通过Spring的IOC容器,获取一个bean对象
获取bean的方式:按类型
前提条件:beans.xml只有一个该类型的bean对象
案例演示:
package com.tangguanlin.controller;
import com.tangguanlin.bean.Monster;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
* 说明:通过类型类获取bean
* 作者:汤观林
* 日期:2022年03月06日 23时
public class IOCTest {
public static void main(String[] args) {
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//通过类型获取bean
//前提条件:beans.xml只有一个该类型的bean对象
Object bean = applicationContext.getBean(Monster.class);
System.out.println(bean);
【细节说明】
1.按类型来获取bean,要求IOC容器中的同一个类的bean只有一个,否则会抛出异常
2.这种方式的应用场景:比如XxxAction,或者XxxService在一个线程中只需要一个对象实例的情况,单例
3.2 通过构造器配置bean
指定构造器来获取bean对象
通过index来指定参数位置
Monster.java
public Monster(Integer id, String nickname, String skill) {
this.id = id;
this.nickname = nickname;
this.skill = skill;
System.out.println("Monster被加载3333...");
beans.xml
<!--指定构造器类创建bean-->
<bean id="monster02" class="com.tangguanlin.bean.Monster">
<constructor-arg index="0" value="200" />
<constructor-arg index="1" value="白骨精" />
<constructor-arg index="2" value="吃人" />
</bean>
获取bean:IOCTest.java
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//通过类型获取bean
Object bean = applicationContext.getBean("monster02");
System.out.println(bean);
运行结果:
Monster被加载3333...
Monster{id=200, nickname='白骨精', skill='吃人'}
3.3 通过p名称空间配置bean
在spring的IOC容器,可以通过p名称空间来配置bean对象。
【细节说明】
1.需要增加一个空间名称的定义
引入p名称空间标签
Monster.java
public Monster(Integer id, String nickname, String skill) {
this.id = id;
this.nickname = nickname;
this.skill = skill;
System.out.println("Monster被加载3333...");
beans.xml
<!--使用p名称空间配置bean-->
<!--引入p名称空间标签-->
<bean id="monster04" class="com.tangguanlin.bean.Monster"
p:id="400"
p:nickname="蜘蛛精"
p:skill="吐口水"
</bean>
获取bean:IOCTest.java
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//使用p名称空间配置bean
Object bean = applicationContext.getBean("monster04");
System.out.println(bean);
运行结果:
Monster{id=400, nickname='蜘蛛精', skill='吐口水'}
3.4 bean对象引用其他bean对象
在spring的IOC容器,可以通过ref来实现bean对象的相互引用
Master.java
public Master(String name, Monster monster) {
this.name = name;
this.monster = monster;
System.out.println("Master被加载 有参构造。。。");
Monster.java
public Monster(Integer id, String nickname, String skill) {
this.id = id;
this.nickname = nickname;
this.skill = skill;
System.out.println("Monster被加载3333...");
beans.xml
<!--bean对象引用其他bean对象-->
<bean id="master01" class="com.tangguanlin.bean.Master">
<property name="name" value="太上老君" />
<property name="monster" ref="monster01" />
</bean>
获取bean:IOCTest.java
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//bean对象引用其他bean对象
Object bean = applicationContext.getBean("master01");
System.out.println(bean);
运行结果:
Master{name='太上老君', monster=Monster{id=100, nickname='牛魔王', skill='芭蕉扇'}}
3.5 bean对象配置内部bean对象
在Spring的IOC容器,可以直接配置内部bean对象
内部的bean可以不配置id,这样其他的bean就无法使用这个内部bean。
Master.java
public class Master {
private String name; //主人名称
private Monster monster; //养的妖怪
private Monster monster2;
Monster.java
public Monster(Integer id, String nickname, String skill) {
this.id = id;
this.nickname = nickname;
this.skill = skill;
System.out.println("Monster被加载3333...");
beans.xml
<!--bean对象引用内部bean对象-->
<bean id="master02" class="com.tangguanlin.bean.Master">
<property name="name" value="地藏王" />
<property name="monster" ref="monster02" />
<property name="monster2" >
<bean class="com.tangguanlin.bean.Monster">
<property name="id" value="500" />
<property name="nickname" value="谛听" />
<property name="skill" value="顺风耳" />
</bean>
</property>
</bean>
获取bean:IOCTest.java
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//bean对象引用内部bean对象
Object bean = applicationContext.getBean("master02");
System.out.println(bean);
运行结果:
Master{name='地藏王', monster=Monster{id=200, nickname='白骨精', skill='吃人'},
monster2=Monster{id=500, nickname='谛听', skill='顺风耳'}}
3.6 给bean对象的集合类型属性赋值
在Spring的IOC容器,看看如何给bean对象的集合类型属性赋值
给bean对象的List,Map,Properties集合属性赋值
主要看一下List/Map/Properties三种集合的使用
3.6.1 List集合属性
Master.java
public class Master {
private String name; //主人名称
private Monster monster; //养的妖怪
private Monster monster2;
//将master养的妖怪放入到List集合进行管理
private List<Monster> monsterList;
Monster.java
public class Monster {
private Integer id;
private String nickname;
private String skill;
beans.xml
<!--bean对象含有List集合属性-->
<bean id="master03" class="com.tangguanlin.bean.Master">
<property name="name" value="地藏王" />
<property name="monster" ref="monster02"/>
<property name="monster2" ref="monster01" />
<property name="monsterList">
<ref bean="monster04"></ref>
<ref bean="monster03" />
</list>
</property>
</bean>
测试类 IOCTest.java
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//bean对象含有List集合属性
Object bean = applicationContext.getBean("master03");
System.out.println(bean);
运行结果:
Master{name='地藏王', monster=Monster{id=200, nickname='白骨精', skill='吃人'},
monster2=Monster{id=100, nickname='牛魔王', skill='芭蕉扇'},
monsterList=[Monster{id=400, nickname='蜘蛛精', skill='吐口水'},
Monster{id=300, nickname='牛魔王3', skill='芭蕉扇3'}]}
3.6.2 Map集合属性
Master.java
public class Master {
private String name; //主人名称
private Monster monster; //养的妖怪
private Monster monster2;
//将master养的妖怪放入到List集合进行管理
private List<Monster> monsterList;
//将master养的怪物放入到map集合进行管理
private Map<String,Monster> map;
Monster.java
public class Monster {
private Integer id;
private String nickname;
private String skill;
beans.xml
<!--bean对象含有map集合属性-->
<bean id="master04" class="com.tangguanlin.bean.Master">
<property name="name" value="地藏王~" />
<property name="monster" ref="monster02" />
<property name="monster2" ref="monster01" />
<property name="map">
<entry>
<key><value>monstermap1</value></key>
<ref bean="monster03"></ref>
</entry>
<entry>
<key><value>monstermap2</value></key>
<ref bean="monster04"></ref>
</entry>
</property>
</bean>
测试类 IOCTest.java
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//bean对象含有map集合属性
Object bean = applicationContext.getBean("master04");
System.out.println(bean);
运行结果:
Master{name='地藏王~', monster=Monster{id=200, nickname='白骨精', skill='吃人'},
monster2=Monster{id=100, nickname='牛魔王', skill='芭蕉扇'},
monsterList=null,
map={monstermap1=Monster{id=300, nickname='牛魔王3', skill='芭蕉扇3'},
monstermap2=Monster{id=400, nickname='蜘蛛精', skill='吐口水'}}}
3.6.3 Properties集合属性
【细节说明】
1.说明一下Properties集合的特点
(1) 这个Properties是Hashtable的子类,是key-value的形式
(2) key是String,而value也是String
Master.java
public class Master {
private String name; //主人名称
private Monster monster; //养的妖怪
private Monster monster2;
//将master养的妖怪放入到List集合进行管理
private List<Monster> monsterList;
//将master养的怪物放入到map集合进行管理
private Map<String,Monster> map;
//创建一个properties属性(集合)
private Properties properties;
Monster.java
public class Monster {
private Integer id;
private String nickname;
private String skill;
beans.xml
<!--bean对象含有properties集合属性-->
<bean id="master05" class="com.tangguanlin.bean.Master">
<property name="name" value="地藏王~" />
<property name="monster" ref="monster02" />
<property name="monster2" ref="monster01" />
<property name="properties">
<props>
<prop key="username">tangguanlin2006</prop>
<prop key="password">123</prop>
</props>
</property>
</bean>
测试类:IOCTest.java
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//bean对象含有Properties集合属性
Master master = (Master)applicationContext.getBean("master05");
Properties properties = master.getProperties();
for(String key:properties.stringPropertyNames()){
System.out.println(key + " = "+ properties.getProperty(key));
运行结果:
password = 123
username = tangguanlin2006
3.7 通过util名称空间创建list集合
在spring的IOC容器,可以通过util名称空间创建list集合
不需要在对象中定义list集合
【细节说明】
1.需要在beans.xml中加入util名称空间才可以使用,引入util名称空间的方式和引入p名称空间一样的。
beans.xml
<!--通过util名称空间创建list集合-->
<util:list id="myList">
<value>list值1-string</value>
<value>list值2-string</value>
</util:list>
测试类:IOCTest.java
//通过util名称空间创建list集合
List<String> myList = (List<String>)applicationContext.getBean("myList");
for(String str:myList){
System.out.println("value="+str);
运行结果:
value=list值1-string
value=list值2-string
3.8 级联属性赋值(了解)
在Spring的IOC容器,可以直接给对象属性的属性赋值
【细节说明】
1.这个在实际开发中不是很多,大家了解即可
2.就是类似于对象-->get一个对象-->get对象的属性的值,比如:a-->getB()-->getName()
Boy.java
public class Boy {
private String name;
private Dog dog;
Dog.java
public class Dog {
private String name;
beans.xml
<!--级联属性赋值-->
<bean id="myDog" class="com.tangguanlin.bean.Dog" />
<bean id="myBoy" class="com.tangguanlin.bean.Boy" >
<property name="name" value="tom" />
<property name="dog" ref="myDog" />
<!--级联属性赋值-->
<property name="dog.name" value="哈趴狗" />
</bean>
测试类:IOCTest.java
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Boy myBoy = (Boy)applicationContext.getBean("myBoy");
System.out.println(myBoy);
运行结果:
Boy{name='tom', dog=Dog{name='哈趴狗'}}
3.9 通过静态工厂获取bean对象(了解)
在spring的IOC容器,可以通过静态工厂获取bean对象
spring的本意是 任何取值都可以通过bean的方式获取到
创建静态工厂类MyStaticFactory.java
package com.tangguanlin.bean;
import java.util.HashMap;
* 说明:静态工厂类
* 作者:汤观林
* 日期:2022年03月11日 22时
public class MyStaticFactory {
private static HashMap<String,Monster> map;
static {
map = new HashMap<String,Monster>();
map.put("monsterkey01",new Monster(700,"小鱼怪","喝水"));
map.put("monsterkey02",new Monster(800,"大鱼怪","喝很多水"));
public static Monster getMonster(String key){
return map.get(key);
beans.xml
<!--通过静态工厂获取bean-->
<bean id="myMonster" class="com.tangguanlin.bean.MyStaticFactory" factory-method="getMonster">
<constructor-arg value="monsterkey01"></constructor-arg>
</bean>
测试类:IOCTest.java
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//通过静态工厂获取bean对象
Monster myMonster = (Monster)applicationContext.getBean("myMonster");
System.out.println(myMonster);
运行结果:
Monster{id=700, nickname='小鱼怪', skill='喝水'}
3.10 通过实例工厂获取bean对象
在spring的IOC容器,可以通过实例工厂获取bean对象
创建实例工厂类 MyInstanceFactory.java
package com.tangguanlin.bean;
import java.util.HashMap;
* 说明:实例工厂类
* 作者:汤观林
* 日期:2022年03月11日 22时
public class MyInstanceFactory {
HashMap<String,Monster> map = new HashMap<String,Monster>();
map.put("monsterkey01",new Monster(900,"~小鱼怪~","喝水"));
map.put("monsterkey02",new Monster(1000,"~大鱼怪~","喝很多水"));
public Monster getMonster(String key){
return map.get(key);
beans.xml
<!--通过实例工厂获取bean对象-->
<bean id="myInstanceFactory" class="com.tangguanlin.bean.MyInstanceFactory" />
<!--从myInstanceFactory获取-->
<bean id="myMonster2" factory-bean="myInstanceFactory" factory-method="getMonster">
<constructor-arg value="monsterkey01" />
</bean>
测试类:IOCTest.java
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//通过实例工厂获取bean对象
Monster myMonster2 = (Monster)applicationContext.getBean("myMonster2");
System.out.println(myMonster2);
运行结果:
Monster{id=900, nickname='~小鱼怪~', skill='喝水'}
3.11 通过FactoryBean获取bean对象
在spring的IOC容器,通过FactoryBean获取bean对象==(重点/后面要用)==
Spring提供的,spring的一个默认实现,现成的获取bean的工具类。
我们需要开发一个MyFactoryBean类,需要实现一个接口FactoryBean
创建FactoryBean对象 MyFactoryBean.java
package com.tangguanlin.bean;
import org.springframework.beans.factory.FactoryBean;
import java.util.HashMap;
import java.util.Map;
import java.util.PrimitiveIterator;
* 说明:通过FactoryBean获取bean对象
* 作者:汤观林
* 日期:2022年03月21日 22时
public class MyFactoryBean implements FactoryBean<Monster> {
private String keyval;
private Map<String,Monster> map;
map = new HashMap<String,Monster>();
map.put("monsterkey01",new Monster(900,"~小猫怪~","抓老鼠"));
map.put("monsterkey02",new Monster(1000,"~大猫怪~","抓很多老鼠"));
//就是返回的bean
@Override
public Monster getObject() throws Exception {
System.out.println("返回对象");
return map.get(keyval);
//返回的bean对象的类型
@Override
public Class<?> getObjectType() {
return Monster.class;
//是否以单例的方式返回
@Override
public boolean isSingleton() {
return true;
//设置哪个key对应的monster
public void setKeyval(String keyval) {
System.out.println("setKeyval="+keyval);
this.keyval = keyval;
beans.xml
<!--通过FactoryBean获取bean对象-->
<bean id="myFactoryBean" class="com.tangguanlin.bean.MyFactoryBean">
<property name="keyval" value="monsterkey01" />
</bean>
测试类:IOCTest.java
//通过Factory获取bean对象
Monster myFactoryBean = (Monster)applicationContext.getBean("myFactoryBean");
System.out.println(myFactoryBean);
运行结果:
setKeyval=monsterkey01
Monster{id=900, nickname='~小猫怪~', skill='抓老鼠'}
3.12 bean配置信息重用(继承)
在Spring的IOC容器,提供了一种继承的方式来实现bean配置信息的重用。
【细节说明】
1.这里的继承和oop的继承不是一个概念,而是数据重用
在IOC容器中,通过继承来实现信息重用,但是注意,这里的继承只是信息重用,而不是我们Java面向对象的OOP继承
2.Spring容器提供了两种形式来实现配置信息重用
3.如果我们把某个bean【比如monster01】设置为abstract=‘true’,这个bean只能被继承,而不能实例化了。
Monster.java
public class Monster {
private Integer id;
private String nickname;
private String skill;
beans.xml
<!--如果我们希望某个bean是只用于继承,本身不能被实例化,则将这个bean设置为 abstract="true" -->
<bean id="monster20" class="com.tangguanlin.bean.Monster" abstract="true">
<property name="id" value="20"></property>
<property name="nickname" value="牛魔王20"></property>
<property name="skill" value="芭蕉扇20"></property>
</bean>
<!--希望创建一个monster对象,他的值和monster01一样-->
<bean id="myMonster01" class="com.tangguanlin.bean.Monster" parent="monster20">
<property name="id" value="101" />
</bean>
测试类:IOCTest.java
//通过继承获取bean对象
Monster myMonster01 = (Monster)applicationContext.getBean("myMonster01");
System.out.println(myMonster01);
运行结果:
Monster{id=101, nickname='牛魔王20', skill='芭蕉扇20'}
3.13 bean创建的顺序问题
案例说明:
1.在spring的IOC容器,在默认情况下是按照配置的顺序来创建的。
2.如果确实有依赖关系,你可以通过调整顺序或者使用depends-on来说明
比如:
<bean id="monster01" class="com.tangguanlin.bean.Monster">
<property name="id" value="100"></property>
<property name="nickname" value="牛魔王"></property>
<property name="skill" value="芭蕉扇"></property>
</bean>
<bean id="monster03" class="com.tangguanlin.bean.Monster">
<property name="id" value="300"></property>
<property name="nickname" value="牛魔王3"></property>
<property name="skill" value="芭蕉扇3"></property>
</bean>
会先创建monster01这个bean对象,然后再创建monster03这个bean对象。
但是,如果这样配置:==monster01依赖monster03==
<bean id="monster01" class="com.tangguanlin.bean.Monster" depends-on="monster03">
<property name="id" value="100"></property>
<property name="nickname" value="牛魔王"></property>
<property name="skill" value="芭蕉扇"></property>
</bean>
<bean id="monster03" class="com.tangguanlin.bean.Monster">
<property name="id" value="300"></property>
<property name="nickname" value="牛魔王3"></property>
<property name="skill" value="芭蕉扇3"></property>
</bean>
就会先创建monster03这个bean对象,再创建monster01这个bean。
3.14 bean对象的单例和多实例(重点)
【案例说明】
在spring的IOC容器,在默认情况下是按照单例创建的,即配置一个bean对象后,IOC容器只会创建一个bean实例。
如果我们希望IOC容器配置的某个bean对象,是以多个实例形式创建的则可以通过配置scope="prototype"来指定
【细节说明】
1.默认是单例,当 设置为多实例机制
2.设置为scope="prototype"后,该bean是在getBean时才创建。
3.14.1 默认单例
beans.xml
<!--如果我们希望某个bean是只用于继承,本身不能被实例化,则将这个bean设置为 abstract="true" -->
<bean id="monster20" class="com.tangguanlin.bean.Monster" abstract="true">
<property name="id" value="20"></property>
<property name="nickname" value="牛魔王20"></property>
<property name="skill" value="芭蕉扇20"></property>
</bean>
<!--希望创建一个monster对象,他的值和monster01一样-->
<bean id="myMonster01" class="com.tangguanlin.bean.Monster" parent="monster20">
<property name="id" value="101" />
</bean>
测试类:IOCTest.java
//通过继承获取bean对象
Monster myMonster01 = (Monster)applicationContext.getBean("myMonster01");
System.out.println(myMonster01+" hashcode="+myMonster01.hashCode());
Monster myMonster02 = (Monster)applicationContext.getBean("myMonster01");
System.out.println(myMonster02+" hashcode="+myMonster02.hashCode());
运行结果:
Monster{id=101, nickname='牛魔王20', skill='芭蕉扇20'} hashcode=99451533
Monster{id=101, nickname='牛魔王20', skill='芭蕉扇20'} hashcode=99451533
3.14.2 改成多实例
beans.xml
<!--如果我们希望某个bean是只用于继承,本身不能被实例化,则将这个bean设置为 abstract="true" -->
<bean id="monster20" class="com.tangguanlin.bean.Monster" abstract="true">
<property name="id" value="20"></property>
<property name="nickname" value="牛魔王20"></property>
<property name="skill" value="芭蕉扇20"></property>
</bean>
<!--希望创建一个monster对象,他的值和monster01一样-->
<bean id="myMonster01" class="com.tangguanlin.bean.Monster" parent="monster20" scope="prototype">
<property name="id" value="101" />
</bean>
测试类:IOCTest.java
//通过继承获取bean对象
Monster myMonster01 = (Monster)applicationContext.getBean("myMonster01");
System.out.println(myMonster01+" hashcode="+myMonster01.hashCode());
Monster myMonster02 = (Monster)applicationContext.getBean("myMonster01");
System.out.println(myMonster02+" hashcode="+myMonster02.hashCode());
运行结果:
Monster被加载...
Monster{id=101, nickname='牛魔王20', skill='芭蕉扇20'} hashcode=84739718
Monster被加载...
Monster{id=101, nickname='牛魔王20', skill='芭蕉扇20'} hashcode=2050835901
3.15 配置有生命周期的bean
【案例说明】
在spring的IOC容器,配置有生命周期的bean,即创建/初始化/销毁
【细节说明】
1.创建就是调用bean的构造方法,
初始化就是init-method方法(名字是程序员来确定的)
2.销毁方法destroy-method就是当关闭容器时,才会被销毁
Dog.java
package com.tangguanlin.bean;
import java.sql.SQLOutput;
* 说明:
* 作者:汤观林
* 日期:2022年03月11日 16时
public class Dog {
private String name;
public Dog() {
public Dog(String name) {
this.name = name;
//初始化
public void init(){
System.out.println("小猫被初始化,给他取名"+name);
public void play(){
System.out.println(this.name + "快乐的玩耍");
public void destory(){
System.out.println(this.name +"活了100岁,安息...");
public String getName() {
return name;
public void setName(String name) {
this.name = name;
beans.xml
<!--bean的生命周期-->
<bean id="dog1" class="com.tangguanlin.bean.Dog" init-method="init" destroy-method="destory">
<property name="name" value="小狗1" />
</bean>
测试类:IOCTest.java
//bean的生命周期
Dog dog1 = (Dog)applicationContext.getBean("dog1");
dog1.play();
//关闭容器
ConfigurableApplicationContext cac = (ConfigurableApplicationContext) applicationContext;
cac.close();
运行结果:
小猫被初始化,给他取名小狗1
小狗1快乐的玩耍
小狗1活了100岁,安息...
3.16 配置bean的后置处理器
【案例说明】
在spring的IOC容器,可以配置bean的后置处理器,该处理器会在bean初始化方法调用前和初始化方法后被调用
(程序员可以在后置处理器中编写自己的代码)
【细节说明】
1.后置处理器,需要实现BeanPostProcessor接口
2.后置处理器,也是一个bean对象,如果希望它正常工作,需要在IOC容器中配置,
这样每一个bean被初始化的前后,都会调用程序员编写的代码(该bean在配置时指定了初始化方法)
Dog.java
package com.tangguanlin.bean;
* 说明:
* 作者:汤观林
* 日期:2022年03月11日 16时
public class Dog {
private String name;
public Dog() {
public Dog(String name) {
this.name = name;
//初始化
public void init(){
System.out.println("小猫被初始化,给他取名"+name);
public void play(){
System.out.println(this.name + "快乐的玩耍");
public void destory(){
System.out.println(this.name +"活了100岁,安息...");
public String getName() {
return name;
public void setName(String name) {
this.name = name;
MyBeanPostProcessor.java 后置处理器
package com.tangguanlin.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
* 说明:bean后置处理器
* 作者:汤观林
* 日期:2022年03月28日 15时
public class MyBeanPostProcessor implements BeanPostProcessor {
//初始化之前
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化方法前被调用 "+bean.getClass());
return bean;
//初始化之后
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化方法后被调用"+bean.getClass());
return bean;
beans.xml
<!--bean的生命周期-->
<bean id="dog1" class="com.tangguanlin.bean.Dog" init-method="init" destroy-method="destory">
<property name="name" value="小狗1" />
</bean>
<!--配置后置处理器-->
<bean id="myBeanPostProcessor" class="com.tangguanlin.bean.MyBeanPostProcessor" />
测试类:IOCTest.java
//后置处理器
Dog dog1 = (Dog)applicationContext.getBean("dog1");
dog1.play();
运行结果:
初始化方法前被调用 class com.tangguanlin.bean.Dog
小猫被初始化,给他取名小狗1
初始化方法后被调用class com.tangguanlin.bean.Dog
小狗1快乐的玩耍
3.17 通过属性文件给bean注入值
【案例说明】
在spring的IOC容器,通过属性文件给bean注入值
my.properties
monster.id=1000
monster.nickname=hulijing
monster.skill=meirenji
【细节说明】
1.通过这样的方式,属性文件要求是 ==前缀.属性名=属性值==
2.后面在项目开发中,可以通过这样的方式来创建一个数据源bean
Monster.java
public class Monster {
private Integer id;
private String nickname;
private String skill;
src/my.properties
monster.id=1000
monster.nickname=hulijing
monster.skill=meirenji
beans.xml
<!--引入我们的bean.properties文件引入到context命名空间-->
<context:property-placeholder location="classpath:my.properties"/>
<!--通过外部的一个属性文件初始化一个monster-->
<bean id="monster1000" class="com.tangguanlin.bean.Monster">
<property name="id" value="${monster.id}" />
<property name="nickname" value="${monster.nickname}" />
<property name="skill" value="${monster.skill}" />
</bean>
测试类:IOCTest.java
//通过外部的一个属性文件初始化一个monster
Monster monster1000=(Monster)applicationContext.getBean("monster1000");
System.out.println(monster1000);
运行结果:
Monster{id=1000, nickname='hulijing', skill='meirenji'}
3.18 基于xml的bean的自动装配(了解)
【案例说明】
在spring的IOC容器,可以实现自动装配bean
【细节说明】
1.我们的自动装配主要是使用byType和byName
2.这个知识点作为了解即可,后面我们主要还是基于注解的方式来玩
OrderAction.java
package com.tangguanlin.bean;
* 说明:
* 作者:汤观林
* 日期:2022年03月28日 21时
public class OrderAction {
private OrderService orderService;
public OrderAction() {
public void save(){
orderService.save();
public OrderService getOrderService() {
return orderService;
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
OrderService.java
package com.tangguanlin.bean;
* 说明:
* 作者:汤观林
* 日期:2022年03月28日 21时
public class OrderService {
private OrderDao orderDao;
public OrderService() {
public void save(){
orderDao.save();
public OrderDao getOrderDao() {
return orderDao;
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
OrderDao.java
package com.tangguanlin.bean;
* 说明:
* 作者:汤观林
* 日期:2022年03月28日 21时
public class OrderDao {
public void save(){
System.out.println("save一个对象");
3.18.1 传统方式
beans.xml
<!--基于传统的action-service-dao的引用关系-->
<bean id="orderDao" class="com.tangguanlin.bean.OrderDao"></bean>
<bean id="orderService" class="com.tangguanlin.bean.OrderService">
<property name="orderDao" ref="orderDao" />
</bean>
<bean id="orderAction" class="com.tangguanlin.bean.OrderAction">
<property name="orderService" ref="orderService" />
</bean>
3.18.2 按类型自动装配
beans.xml
<!--按照类型完成自动装配-->
<!--当ioc创建bean时,如果发现该bean有autowire="byType",就会在ioc容器中匹配一个同类型的bean给它的属性[只能找到一个才运行]-->
<bean id="orderDao" class="com.tangguanlin.bean.OrderDao" />
<bean id="orderService" autowire="byType" class="com.tangguanlin.bean.OrderService" />
<bean id="orderAction" autowire="byType" class="com.tangguanlin.bean.OrderAction" />
3.18.3 按名字自动装配
按名字自动装配原理:
beans.xml
<!--按照名称完成自动装配-->
<!--当ioc创建bean时,如果发现该bean有autowire="byName",就会在ioc容器中匹配一个同名称的bean给它的属性-->
<bean id="orderDao" class="com.tangguanlin.bean.OrderDao" />
<bean id="orderService" autowire="byName" class="com.tangguanlin.bean.OrderService" />
<bean id="orderAction" autowire="byName" class="com.tangguanlin.bean.OrderAction" />
测试类:IOCTest.java
//测试Action-Service-Dao
OrderAction orderAction=(OrderAction)applicationContext.getBean("orderAction");
orderAction.save();
运行结果:
save一个对象
3.19 使用spring EL注入值
使用spring el表达式完成各种使用
3.19.1 基本介绍
Spring Expression Language,Spring表达式语言,简称SpEL。
支持运行时查询并可以操作对象图。
和JSP页面上的EL表达式、Struts2中用到的OGNL表达式一样,
spring EL根据JavaBean风格的getXXX()、setXXX()方法定义的属性访问对象图,完全符合我们熟悉的操作习惯。
3.19.2 基本语法
spring EL使用#{...} 作为定界符,所有在大括号中的字符都将被认为是spring EL表达式。
说明:spring EL表达式,知道怎么使用即可
基本语法: ==#{表达式}==
#{monster04.nickname}
3.19.3 案例演示
演示一下spring EL常见的使用方式
主要用于给bean的属性赋值。
SpringELBean.java
package com.tangguanlin.bean;
* 说明:
* 作者:汤观林
* 日期:2022年03月28日 23时
public class SpringELBean {
//在IOC容器中,使用spring EL表达式给属性赋值
private String beanName;
private String monsterName;
private Monster monster;
private String result; //叫声
private String bookName;
public SpringELBean() {
public String getSum(double num1,double num2){
return "结果"+(num1+num2);
//静态方法
public static String readBook(String bookname){
return "读的书是"+bookname;
public String getBeanName() {
return beanName;
public void setBeanName(String beanName) {
this.beanName = beanName;
public String getMonsterName() {
return monsterName;
public void setMonsterName(String monsterName) {
this.monsterName = monsterName;
public Monster getMonster() {
return monster;
public void setMonster(Monster monster) {
this.monster = monster;
public String getResult() {
return result;
public void setResult(String result) {
this.result = result;
public String getBookName() {
return bookName;
public void setBookName(String bookName) {
this.bookName = bookName;
beans.xml
<!--spring EL表达式的使用案例-->
<bean id="springELBean" class="com.tangguanlin.bean.SpringELBean" >
<!--字面量赋值-->
<property name="beanName" value="#{'泰牛程序员'}" />
<!--使用其他bean的属性赋值-->
<property name="monsterName" value="#{monster04.nickname}" />
<!--直接使用另外一个bean-->
<property name="monster" value="#{monster01}" />
<!--使用一个方法的返回值赋值-->
<property name="result" value="#{springELBean.getSum(10.3,4.5)}" />
<!--使用一个静态方法的返回值赋值-->
<property name="bookName" value="#{T (com.tangguanlin.bean.SpringELBean).readBook('天龙八部')}" />
</bean>
测试类:IOCTest.java
//spring EL的使用
SpringELBean springELBean = (SpringELBean)applicationContext.getBean("springELBean");
System.out.println(springELBean.getBeanName());
System.out.println(springELBean.getMonsterName());
System.out.println(springELBean.getMonster());
System.out.println(springELBean.getResult());
System.out.println(springELBean.getBookName());
运行结果:
泰牛程序员
Monster{id=100, nickname='牛魔王', skill='芭蕉扇'}
结果14.8
读的书是天龙八部
4 基于注解的方式配置bean
基于注解的方式配置bean--通过类型获取
4.1 通过类型来获取
4.1.1 基本介绍
基于注解的方式配置bean,主要是项目开发中的组件,比如:Action、Service、和Dao
原理:
4.1.2 组件注解的形式
@Component 表示当前注解标识的是一个组件
@Controller 表示当前注解标识的是一个控制器,通常用于Action类
@Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于Service类
@Repository 表示当前注解表示的是一个持久化层的类,通常用于Dao类
@Scope(value="prototype") 多实例,不写就是单实例
PS:需要导入一个包 spring-aop-4.0.0.RELEASE.jar
说明:spring并不区分Controller,Service,Repository类型,只要遇到这几个注解就生成bean,
那这样区分的好处是:
1.给程序员看的,见名知意
2.扫描的时候,可以根据类型来配置扫描的范围
【细节说明】
1.必须在spring配置文件中指定“自动扫描的包”,IOC容器才能够检测到当前项目中哪些类被标识了注解,
注意到导入context名称空间
<!--打开IOC基于注解的配置-->
context:component-scan:表示弃用IOC基于注解功能
base-package="com.tangguanlin.bean":表示我们去扫描com.tangguanlin.bean包下面的有注解的类
如果我们希望能够扫描com.tangguanlin.bean包的所有子包,则标准写法是com.tangguanlin.bean.*
<context:component-scan base-package="com.tangguanlin.component" />
<!--关于base-package说明:
1.如果我们这样配置,base-package="com.tangguanlin.aop",就会扫描com.tangguanlin.aop包和子包,
我们可以直接认为你就是希望扫描本包
2.base-package="com.tangguanlin.aop.*",就是只会扫描com.tangguanlin.aop下的子包,而不会扫描本包
3.base-package="com.tangguanlin.aop.**",就是本包和子包都扫描
2.Spring的IOC容器不能检测一个使用了@Controller注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件,其他的@service@Repository也是一样的道理[也就是说spring的IOC容器只要检测到注解就会生成对象,但是这个数据的含义spring不会识别,注解是给程序员编程方便看的]
3.resource-pattern="User*.class"表示只扫描满足条件的类
表示只扫描整个包下的形式是Order*的有注解的.class类
<context:component-scan base-package="com.tangguanlin.component" resource-pattern="Order*.class" />
4.指定排除哪些注解类:exclude-filter标签
<context:component-scan base-package="com.tangguanlin.component" >
<!--如何排除不希望扫描的哪些注解类,比如排除com.tangguanlin.component下面的@Controller类
1.type="annotation":表示根据注解的方式排除
2.context:exclude-filter:表示排除哪些类不扫描
3.expression="org.springframework.stereotype.Controller":要排除的注解的全类名
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
5.指定扫描哪些注解类:include-filter标签
如果我们希望只是扫描某个包下面的哪些注解,可以使用include-filter标签来指定
比如:我们只让com.tangguanlin.component下的@Component被扫描
<!--use-default-filters="false":不再使用默认的过滤机制-->
<context:component-scan base-package="com.tangguanlin.component" use-default-filters="false" >
<!--指定扫描哪些注解类:include-filter标签类,比如指定扫描com.tangguanlin.component下面的@Controller类
1.context:include-filter 表示只扫描指定的注解的类
2.type="annotation":表示根据注解的方式扫描
3.expression="org.springframework.stereotype.Controller":只扫描的注解的全类名
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
4.1.3 快速入门案例
使用注解的方式配置Action、Service、Respository、Componect
beans.xml
<!--打开IOC基于注解的配置-->
context:component-scan:表示弃用IOC基于注解功能
base-package="com.tangguanlin.bean":表示我们去扫描com.tangguanlin.bean包下面的有注解的类
如果我们希望能够扫描com.tangguanlin.bean包的所有子包,则标准写法是com.tangguanlin.bean.*
<context:component-scan base-package="com.tangguanlin.component" />
MyComponect.java
package com.tangguanlin.component;
import org.springframework.stereotype.Component;
* 说明:表示MyComponect是一个组件,可以被IOC容器扫描,并创建一个bean对象
* 作者:汤观林
* 日期:2022年03月29日 23时
@Component
public class MyComponect {
MyOrderAction.java
package com.tangguanlin.component;
import org.springframework.stereotype.Controller;
* 说明:控制层注解
* 作者:汤观林
* 日期:2022年03月29日 23时
@Controller
public class MyOrderAction {
MyOrderService.java
package com.tangguanlin.component;
import org.springframework.stereotype.Service;
* 说明:service层注解
* 作者:汤观林
* 日期:2022年03月29日 23时
@Service
public class MyOrderService {
MyOrderDao.java
package com.tangguanlin.component;
import org.springframework.stereotype.Repository;
* 说明:Dao层注解
* 作者:汤观林
* 日期:2022年03月29日 23时
@Repository
public class MyOrderDao {
测试类:IOCTest.java
//基于注解的方式配置bean_按类型
MyComponect myComponect = applicationContext.getBean(MyComponect.class);
System.out.println(myComponect);
MyOrderAction myOrderAction = applicationContext.getBean(MyOrderAction.class);
System.out.println(myOrderAction);
MyOrderService myOrderService = applicationContext.getBean(MyOrderService.class);
System.out.println(myOrderService);
MyOrderDao myOrderDao = applicationContext.getBean(MyOrderDao.class);
System.out.println(myOrderDao);
运行结果:
com.tangguanlin.component.MyComponect@647e447
com.tangguanlin.component.MyOrderAction@41fbdac4
com.tangguanlin.component.MyOrderService@3c407114
com.tangguanlin.component.MyOrderDao@35ef1869
4.2 通过id来获取
【说明】
1. 默认情况:标记注解后,类名首字母小写作为id的值
2. 可以使用注解的value属性指定id值,并且value可以省略
当我们指定id后,在获取bean时,就应该使用id的方式获取bean
@Controller(value="userAction")
@Controller("userAction")
3. 当然我们也可以给@Service@Repository @Component对应的bean取值
【案例演示】
MyComponect.java
@Component(value="myComponect2")
public class MyComponect {
public MyComponect() {
System.out.println("MyComponect构造方法");
测试类:IOCTest.java
//基于注解的方式配置bean_按类型
MyComponect myComponect = (MyComponect)applicationContext.getBean("myComponect2");
System.out.println(myComponect);
运行结果:
MyComponect构造方法
com.tangguanlin.component.MyComponect@27ce24aa
4.3 @AutoWired自动装配
这个用的更多一点
在spring的ioc容器中,也可以实现自动装配。使用的注解是:@AutoWired,
一般来说是用于对一个对象的属性进行自动转配时,使用该注解。
【案例演示】
以Action/Service/Dao几个组件来进行案例演示说明,这里我就演示
UserAction和UserService的两级自动组装来说明:
UserAction.java
@Controller
public class UserAction {
@Autowired
private UserService userService;
public void testing(){
userService.sayHello();
UserService.java
@Service
public class UserService {
public void sayHello(){
System.out.println("say hello");
测试类IOCTest
public class IOCTest {
public static void main(String[] args) {
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserAction userAction = applicationContext.getBean(UserAction.class);
userAction.testing();
运行结果:
say hello
【细节说明】
1.自动装配的默认机制:
(1)在IOC容器中查找待装配的组件的类型,如果有唯一的bean装配,则使用这个bean进行装配
(2)如待装配的类型对应的bean在IOC容器中有多个,则使用待装配的属性的属性名作为id值再进行查找,
找到就装配,找不到就抛异常。
2.如果IOC容器中有多个相同类型的bean,可以通过@Qualifier("指定id")注解明确指定要装配的bean的id
3.还可以把@Qualifier注解直接写在函数的形参上,这种方式在springMVC使用的多
@Controller
public class UserAction {
@Qualifier(value = "userService") //相同类型多个情况下,指定具体的id
private UserService userService;
public void testing(){
userService.sayHello();
4.4 泛型依赖注入(了解)
【基本介绍】
为了更好的管理有继承和相互依赖的bean的自动装配,spring还提供一种基于泛型依赖的注入机制。
【说明】
1.传统方法就是直接将PhoneDao/BookDao自动装配到PhoneService/Bookservice中
2.我们现在使用基于泛型的依赖注入,因为我们只是一级继承关系,所以看起来比原来的要麻烦些,
但是后面如果继承关系复杂,就会有很大的优越性(大家体会)。
【代码】
Book.java
public class Book {
Phone.java
public class Phone {
BaseService.java
public class BaseService<T> {
@Autowired
private BaseDao<T> baseDao;
public void save(){
baseDao.save();
BookService.java
@Service
public class BookService extends BaseService<Book> {
PhoneService.java
@Service
public class PhoneService extends BaseService<Phone> {
BaseDao.java
public abstract class BaseDao<T> {
public abstract void save();
BookDao.java
@Repository
public class BookDao extends BaseDao<Book> {
@Override
public void save() {
System.out.println("bookDao save");
PhoneDao.java
@Repository
public class PhoneDao extends BaseDao<Phone> {
@Override
public void save() {
System.out.println("phoneDao save()");
测试类IOCTest
public class IOCTest {
public static void main(String[] args) {
//加载spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//基于泛型依赖的bean装配
BookService bookService = applicationContext.getBean(BookService.class);
bookService.save();
运行结果:
bookDao save
5 AOP面向切面编程
5.1 看一个需求
有一个SmartAnimal接口,可以完成对简单的加法和减法,要求在执行加法getSum()和减法getSub()时,
输出执行前,执行过程,执行后的日志输出,请思考如何实现。
5.2 传统方法
传统的解决思路,在==各个方法==的[前,执行过程,后]输出日志
代码:
SmartAnimalable接口
public interface SmartAnimalable {
float getSum(float i,float j);
float getSub(float i,float j);
SmartDog.java
public class SmartDog implements SmartAnimalable {
@Override
public float getSum(float i, float j) {
//使用动态代理调用下面的日志就不写了
System.out.println("日志--方法--getSum方法开始--参数:"+i+","+j);
float result = i+j;
System.out.println("方法内部打印:result="+result);
//使用动态代理调用下面的日志就不写了
System.out.println("日志--方法名--getSum方法结束--结果:result="+result);
return result;
@Override
public float getSub(float i, float j) {
//使用动态代理调用下面的日志就不写了
System.out.println("日志--方法--getSub方法开始--参数:"+i+","+j);
float result = i-j;
System.out.println("方法内部打印:result="+result);
//使用动态代理调用下面的日志就不写了
System.out.println("日志--方法名--getSub方法结束--结果:result="+result);
return result;
AOPTest测试类
public class AOPTest {
public static void main(String[] args) {
SmartAnimalable smartAnimalable = new SmartDog();
float sum = smartAnimalable.getSum(10, 34);
float sub = smartAnimalable.getSub(89, 103);
运行结果
日志--方法--getSum方法开始--参数:5.0,3.0
方法内部打印:result=8.0
日志--方法名--getSum方法结束--结果:result=8.0
日志--方法--getSub方法开始--参数:5.0,3.0
方法内部打印:result=2.0
日志--方法名--getSub方法结束--结果:result=2.0
思考这个解决方案缺陷:
1.优点实现简单直接
2.缺点就是日志代码维护不方便,代码复用性差---每个方法都要写
5.3 使用动态代理
解决思路:
1.使用动态代理来更好的处理日志记录问题(其他比如封装函数,或者类的继承在这里都不是特别合适)
思路:我们写一个动态代理类,可以获取到SmartDog的动态代理对象,然后调用它。
代码实现:--日志记录优化
1.开发一个MyProxyProvider类,使用动态代理来完成。
SmartAnimalable.java
public class SmartDog implements SmartAnimalable {
@Override
public float getSum(float i, float j) {
float result = i+j;
return result;
@Override
public float getSub(float i, float j) {
float result = i-j;
return result;
MyProxyProvider.java
package com.tangguanlin.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
* 说明:动态代理实现日志打印[前--执行--后]
* 作者:汤观林
* 日期:2022年05月22日 17时
public class MyProxyProvider {
private SmartAnimalable target_obj;
public MyProxyProvider(SmartAnimalable target_obj) {
this.target_obj = target_obj;
public SmartAnimalable getProxy() {
//1.获取类加载对象
ClassLoader classLoader = target_obj.getClass().getClassLoader();
//2.获取接口类型数组
Class<?>[] interfaces = target_obj.getClass().getInterfaces();
//3.获取InvocationHandler对象
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
String methodName = method.getName();
try {
//1.在调用目标方法之前打印"方法开始日志"
System.out.println("日志--方法名:" + methodName + "--方法开始--参数:" + Arrays.asList(args));
//2.调用目标方法并接收返回值
result = method.invoke(target_obj, args);
//3.在目标方法结束后打印"方法结束"日志
System.out.println("日志--方法名:" + methodName + "--方法结束--结果:result=" + result);
} catch (Exception e) {
e.printStackTrace();
//4.如果目标方法抛出异常,打印"方法异常"日志
System.out.println("日志--方法名:" + methodName + "--方法抛出异常--异常类型:" + e.getClass().getName());
} finally {
//5.在finally中打印"方法最终结束"日志
System.out.println("日志--方法名:" + methodName + "--方法最终结束");
//6.返回目标方法的返回值
return result;
SmartAnimalable proxy = (SmartAnimalable) Proxy.newProxyInstance(classLoader, interfaces, handler);
return proxy;
使用动态代理类AOPTest
public class AOPTest {
public static void main(String[] args) {
//使用动态代理方式
SmartAnimalable smartDog = new SmartDog();
MyProxyProvider provider = new MyProxyProvider(smartDog);
smartDog = provider.getProxy();
//这时就会去调用动态代理对象的invoke方法
smartDog.getSum(10,34);
smartDog.getSub(89,103);
运行结果:
日志--方法名:getSum--方法开始--参数:[10.0, 34.0]
日志--方法名:getSum--方法结束--结果:result=44.0
日志--方法名:getSum--方法最终结束
日志--方法名:getSub--方法开始--参数:[89.0, 103.0]
日志--方法名:getSub--方法结束--结果:result=-14.0
日志--方法名:getSub--方法最终结束
对这种方式的优点:
1.优点:可以非常好的完成日志的管理,代码的维护性很好,复用性搞
2.缺点:不太好理解,复杂度高
5.4 AOP基本介绍
1.AOP的全称(aspect oriented programming),面向切面编程
2.一张示意图说明AOP的相关概念
3.aop的实现方式
(1)基于动态代理的方式[内置aop实现]
(2)使用框架aspectj来实现
4.AOP的相关概念--看一下示意图来说明
(1)日志
(2)连接点(切入点):切面类可以通过连接点,获取到目标方法信息
(3)通知(5种)
(4)切入表达式
5.5 AOP的实现方式
1.基于动态代理的方式【内置aop实现】
2.使用框架aspectj来实现
5.6 AOP的基本编程说明
1.需要引入核心的aspect包,后面案例会具体说明
2.在切面类中声明通知方法
前置通知: @Before 在目标类的目标方法前执行
返回通知: @AfterReturning 返回通知,目标类的目标方法执行完毕后,就执行的代码
异常通知: @AfterThrowing 当执行的目标方法发生异常时,被调用
后置通知: @After
环绕通知: @Around 一个环绕通知, 包含前面4个通知的作用
3.五种通知和前面写的动态代理类方法的对应关系
说明1:环绕通知可以完成另外4个通知的所有的事情
说明2:执行流程说明
5.7 AOP快速入门案例
【案例演示】
我们使用AOP编程的方式,来实现手写的动态代理案例效果,就以上一个案例为例来讲解。
【具体步骤】
1.开发好我们的目标类(SmartAnimalable.java SmartDog.java)
2.在IOC容器中,开启基于注解的AOP功能
<!--需要在ioc容器开启基于注解的AOP功能-->
<aop:aspectj-autoproxy />
3.开发切面类
SmartAnimalable.java
public interface SmartAnimalable {
float getSum(float i,float j);
float getSub(float i,float j);
SmartDog.java
@Component
public class SmartDog implements SmartAnimalable {
@Override
public float getSum(float i, float j) {
float result = i+j;
System.out.println("方法内部打印:result="+result);
return result;
@Override
public float getSub(float i, float j) {
float result = i-j;
System.out.println("方法内部打印:result="+result);
return result;
切面类SmartAspect.java
package com.tangguanlin.aop;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
* 说明:切面类
* 作者:汤观林
* 日期:2022年05月22日 21时
//@Aspect表示是一个切面类
//@Component 表示一个组件,会被扫描
@Aspect
@Component
public class SmartAspect {
* 开发一个前置通知,在目标类的目标方法前执行
* @Before 前置通知
* execution 关键字不要改
* value= 值后面写的是切入表达式 execution( public float com.tangguanlin.aop.SmartDog.getSum(float, float))
* 对哪个类的哪个方法进行切面
* public float com.tangguanlin.aop.SmartDog.*(..) 表达式作用是 SmartDog类的所有方法都要切
* 特别强调: public float 访问修饰符合返回值要有
//@Before(value = "execution( public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
@Before(value = "execution( public float com.tangguanlin.aop.SmartDog.*(..))")
public void showStartLog(){
System.out.println("前置通知");
* @AfterReturning 返回通知,目标类的目标方法执行完毕后,就执行的代码
//@AfterReturning(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
@AfterReturning(value="execution(public float com.tangguanlin.aop.SmartDog.*(..))")
public void showSuccessLog(){
System.out.println("返回通知");
* @AfterThrowing 当执行的目标方法发生异常时,被调用
//@AfterThrowing(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
@AfterThrowing(value="execution(public float com.tangguanlin.aop.SmartDog.*(..))")
public void showExceptionLog(){
System.out.println("异常通知");
* @After 最终通知
//@After(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
@After(value="execution(public float com.tangguanlin.aop.SmartDog.*(..))")
public void showFinallyLog(){
System.out.println("最终通知通知");
测试类AOPTest:
public class AOPTest {
public static void main(String[] args) {
//AOP方式
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
SmartAnimalable smartAnimalable = applicationContext.getBean(SmartAnimalable.class);
smartAnimalable.getSum(35,51);
smartAnimalable.getSub(100,50);
运行结果:
前置通知
方法内部打印:result=86.0
最终通知通知
方法内部打印:result=50.0
最终通知通知
5.8 AOP细节
1.对切入表达式的基本使用
@Before(value = "execution( public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
2.切入表达式的更多配置,比如模糊配置
//对SmartDog类的所有方法都切一刀
@Before(value = "execution( public float com.tangguanlin.aop.SmartDog.*(..))")
//对项目中的所有类的所有方法都进行切一刀 执行切面的方法
@Before(value = "execution( * *.*(..))")
3.切入点表达式
5.9 JoinPoint连接点
JoinPoint:切点 用到砍的地方
通过JoinPoint获取调用函数的签名
看一个需求:任何在调用前置消息对应的方法时,得到方法名,参数信息。
5.9.1 joinPoint.getArgs()
通过连接点得到目标方法的参数
Object[] args = joinPoint.getArgs();
5.9.2 joinPoint.getSignature()
获取连接点(切点)的签名
Signature signature = joinPoint.getSignature();
5.9.3 signature.getName()
通过连接点得到目标方法的名称
//得到函数的签名
Signature signature = joinPoint.getSignature();
//得到目标方法
String name = signature.getName();
5.9.4 joinPoint.getTarget()
通过连接点得到 目标类的类名
//得到目标类
Object target = joinPoint.getTarget();
String targetClass = target.getClass().getName(); //com.tangguanlin.aop.SmartDog
5.9.5 案例演示
说明:在调用getSum的前置通知获取到调用方法的签名
@Aspect
@Component
public class SmartAspect {
@Before(value = "execution( public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
public void showStartLog(JoinPoint joinPoint){
//得到函数的签名
Signature signature = joinPoint.getSignature();
//得到目标类
Object target = joinPoint.getTarget();
String targetClass = target.getClass().getName();
System.out.println("目标类="+targetClass);
//得到目标方法
String name = signature.getName();
System.out.println("name="+name);
//获取函数的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知 目标类"+targetClass+" 目标方法的名称"+name+" 参数"+ Arrays.asList(args));
运行结果:
前置通知 目标类com.tangguanlin.aop.SmartDog 目标方法的名称getSum 参数[35.0, 51.0]
5.10 获取目标方法执行的结果
增加一个字段:returning = "res"
举例说明:
业务类 SmartDog.java
@Component
public class SmartDog implements SmartAnimalable {
@Override
public float getSum(float i, float j) {
float result = i+j;
System.out.println("方法内部打印:result="+result);
return result;
切面类SmartAspect.java
@Aspect
@Component
public class SmartAspect {
* @AfterReturning 返回通知,目标类的目标方法执行完毕后,就执行的代码
* 获取目标方法执行的结果
@AfterReturning(value="execution(public float com.tangguanlin.aop.SmartDog.*(..))",returning = "res")
public void showSuccessLog(JoinPoint joinPoint,Object res){
System.out.println("返回通知 res="+res);
测试类AOPTest.java
public class AOPTest {
public static void main(String[] args) {
//AOP方式
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
SmartAnimalable smartAnimalable = applicationContext.getBean(SmartAnimalable.class);
smartAnimalable.getSum(35,51);
smartAnimalable.getSub(100,50);
运行结果:
方法内部打印:result=86.0
最终通知通知
返回通知 res=86.0
5.11 获取目标方法的异常信息
增加一个字段:throwing = "myExcepion"
业务类SmartDog.java
@Component
public class SmartDog implements SmartAnimalable {
@Override
public float getSum(float i, float j) {
//使用动态代理调用下面的日志就不写了
//System.out.println("日志--方法--getSum方法开始--参数:"+i+","+j);
float result = i+j;
float r2 = 3/0;
System.out.println("方法内部打印:result="+result);
//使用动态代理调用下面的日志就不写了
//System.out.println("日志--方法名--getSum方法结束--结果:result="+result);
return result;
切面类SmartAspect.java
@Aspect
@Component
public class SmartAspect {
* @AfterThrowing 当执行的目标方法发生异常时,被调用
* 获取目标方法中的异常信息
@AfterThrowing(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))",
throwing = "myExcepion")
public void showExceptionLog(JoinPoint joinPoint,Throwable myExcepion){
System.out.println("异常通知"+"异常:"+myExcepion.getMessage());
测试类AOPTest.java
public class AOPTest {
public static void main(String[] args) {
//AOP方式
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
SmartAnimalable smartAnimalable = applicationContext.getBean(SmartAnimalable.class);
smartAnimalable.getSum(35,51);
smartAnimalable.getSub(100,50);
运行结果:
异常通知异常:/ by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.tangguanlin.aop.SmartDog.getSum(SmartDog.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
k.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at com.tangguanlin.aop.AOPTest.main(AOPTest.java:34)
5.12 环绕通知(四合一)
环绕通知可以完成其他四个通知要做的事情
【看一个需求】
如何使用环绕通知完成其他四个通知的功能。
【案例演示】
业务类SmartDog.java
@Component
public class SmartDog implements SmartAnimalable {
@Override
public float getSum(float i, float j) {
float result = i+j;
//float r2 = 3/0;
System.out.println("方法内部打印:result="+result);
return result;
@Override
public float getSub(float i, float j) {
float result = i-j;
System.out.println("方法内部打印:result="+result);
return result;
切面类SmartAroundAspect.java
@Component
@Aspect
public class SmartAroundAspect {
@Around(value = "execution(public float com.tangguanlin.aop.SmartDog.getSum(float ,float ))")
public Object doAround(ProceedingJoinPoint joinPoint){
Object result = null;
//获取目标方法的签名
Signature signature = joinPoint.getSignature();
//获取目标方法的名称
String name = signature.getName();
//获取目标方法的参数
Object[] args = joinPoint.getArgs();
//获取目标类的类名
String targetClass = joinPoint.getTarget().getClass().getName();
try {
//完成前置通知的事情
System.out.println("前置通知 目标类"+targetClass+" 目标方法的名称"+name+" 参数"+ Arrays.asList(args));
//调用目标方法
result = joinPoint.proceed();
//完成 返回通知的事情
System.out.println("返回通知 result="+result);
}catch (Throwable myExcepion){
//完成异常通知的事情
System.out.println("异常通知"+"异常:"+myExcepion.getMessage());
}finally {
//完成最终通知的事情
System.out.println("最终通知");
return result;
测试类AOPTest.java
public class AOPTest {
public static void main(String[] args) {
//AOP方式
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
SmartAnimalable smartAnimalable = applicationContext.getBean(SmartAnimalable.class);
smartAnimalable.getSum(35,51);
smartAnimalable.getSub(100,50);
运行结果
前置通知 目标类com.tangguanlin.aop.SmartDog 目标方法的名称getSum 参数[35.0, 51.0]
方法内部打印:result=86.0
返回通知 result=86.0
【细节】
1.环绕通知做的事情,和动态代理做的事情非常的类似
框架使用+框架怎么实现的
学编程本质---->学习框架,写项目的过程【提示对编程和设计思想】----->多写,多看【从量变到质变】
自己要写一篇很优秀的文字,自己就要看很多优秀的文字,
自己要写很优秀的代码,自己就要看很多优秀的代码。
这种水平上升是潜移默化的。当你的量到一定程度的时候,你会发现,以前觉得很难的东西,现在看简单了。
5.13 切入表达式的重用
说明一个问题:
前面我们使用的切入表达式是分别写在自己切面方法的上面,这样切面表达式就会重复,利用率不高。
因此Spring提供切入表达式重用的机制。
案例:
定义一个切入表达式,在需要的地方直接使用即可。
定义语法:
@Aspect
@Component
public class SmartAspect {
//定义自己的切入表达式 ----可以重用,类似于变量:myPointCut
@Pointcut(value = "execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
public void myPointCut(){
使用:
@Aspect
@Component
public class SmartAspect {
@Before(value = "myPointCut()")
public void showStartLog(JoinPoint joinPoint){
//得到函数的签名
Signature signature = joinPoint.getSignature();
//得到目标类
Object target = joinPoint.getTarget();
String targetClass = target.getClass().getName();
System.out.println("目标类="+targetClass);
//得到目标方法
String name = signature.getName();
System.out.println("name="+name);
//获取函数的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知 目标类"+targetClass+" 目标方法的名称"+name+" 参数"+ Arrays.asList(args));
5.14 切面优先级
作用在同一个方法的多个切面的优先级
如果同一个函数,有多个切面在同一个切入点切入,那么执行的优先级如何控制
【 基本语法】
@Order(val ue=n) 如果n值越小,优先级越高
遵循栈的方式
@Aspect
@Component
@Order(value = 2) //这里设置优先级 优先级更低
public class SmartAspect {
@Aspect
@Component
@Order(value = 1) //这里设置优先级 优先级更高
public class SmartAspect2 {
【案例】
切面1 SmartAspect.java 优先级为2 最低
package com.tangguanlin.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
* 说明:切面类1
* 作者:汤观林
* 日期:2022年05月22日 21时
//@Aspect表示是一个切面类
//@Component 表示一个组件,会被扫描
@Aspect
@Component
@Order(value = 2)
public class SmartAspect {
@Before(value = "execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
public void showStartLog(JoinPoint joinPoint){
//得到函数的签名
Signature signature = joinPoint.getSignature();
//得到目标类
Object target = joinPoint.getTarget();
String targetClass = target.getClass().getName();
//得到目标方法
String name = signature.getName();
//获取函数的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知SmartAspect 目标类"+targetClass+" 目标方法的名称"+name+" 参数"+ Arrays.asList(args));
* @AfterReturning 返回通知,目标类的目标方法执行完毕后,就执行的代码
* 获取目标方法执行的结果
@AfterReturning(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))",returning = "res")
public void showSuccessLog(JoinPoint joinPoint,Object res){
System.out.println("返回通知SmartAspect res="+res);
* @AfterThrowing 当执行的目标方法发生异常时,被调用
* 获取目标方法中的异常信息
@AfterThrowing(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))",throwing = "myExcepion")
public void showExceptionLog(JoinPoint joinPoint,Throwable myExcepion){
System.out.println("异常通知SmartAspect"+"异常:"+myExcepion.getMessage());
* @After 最终通知
@After(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
public void showFinallyLog(){
System.out.println("最终通知SmartAspect");
切面2 SmartAspect2.java 优先级为1 最高
package com.tangguanlin.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
* 说明:切面类
* 作者:汤观林
* 日期:2022年05月22日 21时
//@Aspect表示是一个切面类
//@Component 表示一个组件,会被扫描
@Aspect
@Component
@Order(value = 1)
public class SmartAspect2 {
@Before(value = "execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
public void showStartLog(JoinPoint joinPoint){
//得到函数的签名
Signature signature = joinPoint.getSignature();
//得到目标类
Object target = joinPoint.getTarget();
String targetClass = target.getClass().getName();
//System.out.println("目标类="+targetClass);
//得到目标方法
String name = signature.getName();
//System.out.println("name="+name);
//获取函数的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知SmartAspect2 目标类"+targetClass+" 目标方法的名称"+name+" 参数"+ Arrays.asList(args));
* @AfterReturning 返回通知,目标类的目标方法执行完毕后,就执行的代码
* 获取目标方法执行的结果
@AfterReturning(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))",returning = "res")
public void showSuccessLog(JoinPoint joinPoint,Object res){
System.out.println("返回通知SmartAspect2 res="+res);
* @AfterThrowing 当执行的目标方法发生异常时,被调用
* 获取目标方法中的异常信息
@AfterThrowing(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))",throwing = "myExcepion")
public void showExceptionLog(JoinPoint joinPoint,Throwable myExcepion){
System.out.println("异常通知SmartAspect2"+"异常:"+myExcepion.getMessage());
* @After 最终通知
@After(value="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))")
public void showFinallyLog(){
System.out.println("最终通知SmartAspect2");
测试类AOPTest.java
public class AOPTest {
public static void main(String[] args) {
//AOP方式
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
SmartAnimalable smartAnimalable = applicationContext.getBean(SmartAnimalable.class);
smartAnimalable.getSum(35,51);
smartAnimalable.getSub(100,50);
运行结果:
前置通知SmartAspect2 目标类com.tangguanlin.aop.SmartDog 目标方法的名称getSum 参数[35.0, 51.0] --切面2的前置
前置通知SmartAspect 目标类com.tangguanlin.aop.SmartDog 目标方法的名称getSum 参数[35.0, 51.0] --切面1的前置
方法内部打印:result=86.0
最终通知SmartAspect --切面1的最终通知
返回通知SmartAspect res=86.0 --切面1的返回通知 ----遵循栈的规则
最终通知SmartAspect2 --切面2的最终通知
返回通知SmartAspect2 res=86.0 --切面2的返回通知
5.15 基于XML配置AOP
【基本说明】
前面我们是通过注解的方式来配置AOP的,在Spring中,我们也可以通过xml的方式来配置AOP。
【案例】
我们使用xml配置方式来完成前面的案例,请大家注意观察。
步骤1:关闭aop基于注解的AOP功能
<!--需要在ioc容器开启基于注解的AOP功能
<aop:aspectj-autoproxy />
步骤2:
编写一个普通类(SmartXMLAspect.java),有四个方法【前置,通知,异常,最终】的方法,就是普通的方法
package com.tangguanlin.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import java.util.Arrays;
* 说明:基于XML的切面类
* 作者:汤观林
* 日期:2022年05月22日 21时
//这就是一个普通的类
public class SmartXMLAspect {
public void showStartLog(JoinPoint joinPoint){
//得到函数的签名
Signature signature = joinPoint.getSignature();
//得到目标类
Object target = joinPoint.getTarget();
String targetClass = target.getClass().getName();
//System.out.println("目标类="+targetClass);
//得到目标方法
String name = signature.getName();
//System.out.println("name="+name);
//获取函数的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知SmartXMLAspect 目标类"+targetClass+" 目标方法的名称"+name+" 参数"+ Arrays.asList(args));
public void showSuccessLog(JoinPoint joinPoint,Object res){
System.out.println("返回通知SmartXMLAspect res="+res);
public void showExceptionLog(JoinPoint joinPoint,Throwable myExcepion){
System.out.println("异常通知SmartXMLAspect"+"异常:"+myExcepion.getMessage());
public void showFinallyLog(){
System.out.println("最终通知SmartXMLAspect");
步骤3:
在beans.xml中配置,让这个普通的类((SmartXMLAspect.java)成为一个切面类
<!--配置一个SmartXMLAspect bean -->
<bean id="smartXMLAspect" class="com.tangguanlin.aop.SmartXMLAspect" />
步骤4:配置切面和切入表达式
<!--配置切面和切入表达式-->
<aop:config>
<!--配置了一个统一的切入点-->
<aop:pointcut id="myPoint"
expression="execution(public float com.tangguanlin.aop.SmartDog.getSum(float, float))"/>
<!--配置切面-->
<aop:aspect ref="smartXMLAspect" order="1">
<!--前置通知-->
<aop:before method="showStartLog" pointcut-ref="myPoint" />
<!--返回通知-->
<aop:after-returning method="showSuccessLog" pointcut-ref="myPoint" returning="res" />
<!--异常通知-->
<aop:after-throwing method="showExceptionLog" pointcut-ref="myPoint" throwing="myExcepion" />
<!--最终通知-->
<aop:after method="showFinallyLog" pointcut-ref="myPoint" />
<!--环绕通知-->
<!--<aop:around method="showFinallyLog" /> -->
</aop:aspect>
</aop:config>
运行结果:
前置通知SmartXMLAspect 目标类com.tangguanlin.aop.SmartDog 目标方法的名称getSum 参数[35.0, 51.0]
方法内部打印:result=86.0
返回通知SmartXMLAspect res=86.0
最终通知SmartXMLAspect
6 JdbcTemplate
看一个实际需求:
如果程序员就希望使用spring框架来做项目,不使用hibernate等其他持久层框架,那么spring框架如何处理对数据库的操作呢?
方案1. 使用同学前面做项目自己开发的JdbcUtils类
方案2. 其实spring提供了一个操作数据库(表)功能强大的类JdbcTemplate
我们可以同IOC容器来配置一个jdbcTemplate对象,使用它来完成对数据库表的各种操作。
6.1 配置数据源
【基本说明】
1.通过Spring可以配置数据源,从而完成对数据库的操作
2.jdbcTemplate是spring提供的访问数据库的技术。可以将JDBC的常用操作封装为模板方法
因为JdbcTemplate类需要一个数据源,因此我们需要先配置一个数据源(bean),提供给JdbcTemplate使用。
【案例演示】
我们使用spring的方式来完成JdbcTemplate配置和使用
1.先在IOC容器中配置dataSource数据源
1.1 创建SpringJdbcTemplate
1.2 引入jar
1.3 测试表
1.4 编写ac.xml
1.5 Junit测试程序.getConnection()
步骤1:
引入需要的包:
mchange-commons-java-0.2.3.4.jar c3p0辅助包
c3p0-0.9.2.1.jar
mysql-connector-java-5.1.38.jar
步骤2:
新建jdbc.propertis配置文件,放在类路径下
配置文件多的情况下,也可以创建源码包Sources包,本质仍然是类路径,只是Idea做了一个文件的映射管理
jdbc.user=root
jdbc.passowrd=123456
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jdbcTemplate
步骤3:
在beans.xml中配置数据源
<!--引入jdbc.properties配置文件-->
<context:property-placeholder location="classpath:jdbc.properties" />
<!--配置数据源dataSource-->
<bean id="dataSourece" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.passowrd}"/>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
步骤4:
测试类JdbcTemplateTest.java
package com.tangguanlin.jdbcTemplate;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
* 说明:JdbcTemplate测试类
* 作者:汤观林
* 日期:2022年05月28日 16时
public class JdbcTemplateTest {
ApplicationContext applicationContext = null;
* 初始化加载beans IOC容器
@Before
public void init(){
applicationContext = new ClassPathXmlApplicationContext("beans.xml");
* 获取数据源
@Test
public void getDataSource() throws SQLException {
DataSource dataSource = applicationContext.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
运行结果:
com.mchange.v2.c3p0.impl.NewProxyConnection@6f6a7463
6.2 将数据源分配给JdbcTemplate bean
创建数据源的目的,就是给JdbcTemplate的的对象使用(bean),下面看看具体的操作
配置一个JdbcTemplate bean并注入id="dataSourece"
beans.xml
<!--配置一个JdbcTemplate bean并注入id="dataSourece"-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSourece" />
</bean>
6.3 获取jdbcTemplate bean
获取jdbcTemplate bean并完成操作
创建数据库表 monster
create table monster(
id int primary key,
name varchar(64) not null default '',
skill varchar(64) not null default ''
初始化数据
insert into monster values(100,'青牛怪','吐火');
insert into monster values(200,'黄袍怪','吐烟');
insert into monster values(300,'蜘蛛怪','吐丝');
测试类JdbcTemplateTest
public class JdbcTemplateTest {
//获取jdbcTemplate bean并完成操作
@Test
public void insertOne(){
//获取IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
//编写sql
String sql = "insert into monster values(400,'小妖怪','玩耍')";
jdbcTemplate.execute(sql);
运行结果:
数据插入成功
6.4 jdbcTemplate相关方法
jdbcTemplate的相关方法
6.4.1 jdbcTemplate.execute(sql)
执行sql语句
@Test
public void delete(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
//编写sql,这时你的select语句返回的字段,需要和你的类的属性相互匹配
String sql = "delete from monster where id in (200,300) ";
jdbcTemplate.execute(sql);
6.4.2 queryForObject(sql, RowMapper, Object... args)
查询一条记录,按指定类型返回
//查询单个对象
public Monster queryMonsterById(int id){
String sql = "select id,name,skill from monster where id= ?";
RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
Monster monster = jdbcTemplate.queryForObject(sql,rowMapper,id);
return monster;
6.4.3 queryForObject(sql, Class , Object... args)
查询一条记录,按指定类型返回,Class只能用于基本数据类型
查询一行一列,查询记录条数
//jdbcTemplate查询一列的值 查询id=100的怪物的名字
//查询返回共多少个妖怪
@Test
public void queryOneRowOneColl(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
//编写sql,这时你的select语句返回的字段,需要和你的类的属性相互匹配
String sql = "select name from monster where id = 100";
String name = jdbcTemplate.queryForObject(sql, String.class);
System.out.println(name);
String sql2 = "select count(*) from monster";
Integer count = jdbcTemplate.queryForObject(sql2, int.class);
System.out.println(count);
6.4.4 query(sql, RowMapper, Object... args)
查询多条数据,按指定类型返回
//查询列表
public List<Monster> queryMonsterList(){
String sql = "select id,name,skill from monster";
RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper);
return monsterList;
6.4.5 int update(String sql, Object... args)
修改一条数据(添加,修改,删除)
//修改
public void update(Monster monster){
String sql = "update monster set skill = ? where id= ? ";
jdbcTemplate.update(sql,monster.getSkill(),monster.getId());
6.4.6 batchUpdate(sql,List batchArgs)
添加多条数据
//批量添加数据
@Test
public void insertMany(){
//获取IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
//编写sql
String sql = "insert into monster values(?,?,?)";
//构建数组
List<Object[]> parameterList = new ArrayList<Object[]>();
parameterList.add(new Object[]{500,"白蛇精","变美女"});
parameterList.add(new Object[]{600,"青蛇精","变丑女"});
jdbcTemplate.batchUpdate(sql, parameterList);
6.5 jdbcTemplate添加单个数据
添加一个新的monster
测试类JdbcTemplateTest
public class JdbcTemplateTest {
//添加一条记录
@Test
public void insertOne(){
//获取IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
//编写sql
String sql = "insert into monster values(400,'小妖怪','玩耍')";
jdbcTemplate.execute(sql);
运行结果:
数据插入成功
6.6 jdbcTemplate添加多个数据
添加多个新的monster
测试类JdbcTemplateTest
public class JdbcTemplateTest {
//批量添加数据
@Test
public void insertMany(){
//获取IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
//编写sql
String sql = "insert into monster values(?,?,?)";
//构建数组
List<Object[]> parameterList = new ArrayList<Object[]>();
parameterList.add(new Object[]{500,"白蛇精","变美女"});
parameterList.add(new Object[]{600,"青蛇精","变丑女"});
jdbcTemplate.batchUpdate(sql, parameterList);
6.7 jdbcTemplate修改数据
修改一个monster的skill
public class JdbcTemplateTest {
//修改一条数据
@Test
public void updateOne(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
String sql = "update monster set skill = ? where id = ? ";
int i = jdbcTemplate.update(sql, "吐口水", 200);
if(i>0){
System.out.println("jdbcTemplate修改成功!");
运行结果:
6.8 jdbcTemplate查询一条数据
查询id=100的monster并封装到Monster实体对象
public class JdbcTemplateTest {
//查询一条数据,并封装到实体对象
@Test
public void queryOne(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
//编写sql,这时你的select语句返回的字段,需要和你的类的属性相互匹配
String sql = "select id,name,skill from monster where id = ? ";
//创建一个RowMapper接口-->帮助你讲查询的结果封装到对象中
RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
Monster monster = jdbcTemplate.queryForObject(sql,rowMapper,100);
System.out.println(monster);
运行结果:
Monster{id=100, name='青牛怪', skill='吐火'}
6.9 jdbcTemplate查询多条数据
查询id>=200的monster并封装到Monster实体对象
public class JdbcTemplateTest {
//查询多条数据,并封装成指定的类型 List<对象>
@Test
public void queryMany(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
//编写sql,这时你的select语句返回的字段,需要和你的类的属性相互匹配
String sql = "select id,name,skill from monster where id > ? ";
//创建一个RowMapper接口-->帮助你讲查询的结果封装到对象中
RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
//查询数据List
List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper, 200);
System.out.println(Arrays.asList(monsterList));
运行结果:
[[Monster{id=300, name='蜘蛛怪', skill='吐丝'},
Monster{id=400, name='小妖怪', skill='玩耍'},
Monster{id=500, name='白蛇精', skill='变美女'},
Monster{id=600, name='青蛇精', skill='变丑女'}]
6.10 jdbcTemplate查询一列的值
查询id=100的怪物的名字
查询返回共多少个妖怪
public class JdbcTemplateTest {
//jdbcTemplate查询一列的值 查询id=100的怪物的名字
//查询返回共多少个妖怪
@Test
public void queryOneRowOneColl(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
//编写sql,这时你的select语句返回的字段,需要和你的类的属性相互匹配
String sql = "select name from monster where id = 100";
String name = jdbcTemplate.queryForObject(sql, String.class);
System.out.println(name);
String sql2 = "select count(*) from monster";
Integer count = jdbcTemplate.queryForObject(sql2, int.class);
System.out.println(count);
运行结果:
青牛怪
6.11 jdbcTemplate删除数据
public class JdbcTemplateTest {
//删除一条数据
@Test
public void delete(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
//编写sql,这时你的select语句返回的字段,需要和你的类的属性相互匹配
String sql = "delete from monster where id in (200,300) ";
jdbcTemplate.execute(sql);
运行结果:
删除了2条记录
6.12 jdbcTemplate封装成Dao层
6.12.1 jdbcTemplate配置beans.xml
<!--引入jdbc.properties配置文件-->
<context:property-placeholder location="classpath:config/jdbc.properties" />
<!--配置数据源dataSource-->
<bean id="dataSourece" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.passowrd}"/>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
<!--配置一个JdbcTemplate bean并注入id="dataSourece"-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSourece" />
</bean>
6.12.2 Controller层MonsterController
@Controller
public class MonsterController {
@Autowired
private MonsterService monsterService;
//查询单个对象
public Monster queryMonsterById(int id){
return monsterService.queryMonsterById(id);
//查询对象列表
public List<Monster> queryMonsterList(){
return monsterService.queryMonsterList();
//添加对象
public void save(Monster monster){
monsterService.save(monster);
//修改对象
public void update(Monster monster){
monsterService.update(monster);
//删除对象
public void deleteById(int id){
monsterService.deleteById(id);
6.12.3 Service层MonsterService
@Service
public class MonsterService {
@Autowired
private MonsterDao monsterDao;
//查询单个对象
public Monster queryMonsterById(int id){
return monsterDao.queryMonsterById(id);
//查询对象列表
public List<Monster> queryMonsterList(){
return monsterDao.queryMonsterList();
//添加对象
public void save(Monster monster){
monsterDao.save(monster);
//修改对象
public void update(Monster monster){
monsterDao.update(monster);
//删除对象
public void deleteById(int id){
monsterDao.deleteById(id);
6.12.4 Dao层MonsterDao
@Repository
public class MonsterDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//查询单个对象
public Monster queryMonsterById(int id){
String sql = "select id,name,skill from monster where id= ?";
RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
Monster monster = jdbcTemplate.queryForObject(sql,rowMapper,id);
return monster;
//查询列表
public List<Monster> queryMonsterList(){
String sql = "select id,name,skill from monster";
RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper);
return monsterList;
public void save(Monster monster){
String sql = "insert into monster values(?,?,?)";
jdbcTemplate.update(sql,monster.getId(),monster.getName(),monster.getSkill());
public void update(Monster monster){
String sql = "update monster set skill = ? where id= ? ";
jdbcTemplate.update(sql,monster.getSkill(),monster.getId());
public void deleteById(int id){
String sql = "delete from monster where id = ?";
jdbcTemplate.update(sql,id);
6.12.5 测试类JdbcTemplateTest
public class JdbcTemplateTest {
//封装成dao_查询单个对象
@Test
public void daoQueryOne(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
MonsterController monsterController = applicationContext.getBean(MonsterController.class);
Monster monster = monsterController.queryMonsterById(400);
System.out.println(monster);
//封装成dao_查询对象列表
@Test
public void daoQueryMany(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
MonsterController monsterController = applicationContext.getBean(MonsterController.class);
List<Monster> monsterList = monsterController.queryMonsterList();
System.out.println(Arrays.asList(monsterList));
//封装成dao_添加对象
@Test
public void daoSave(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
MonsterController monsterController = applicationContext.getBean(MonsterController.class);
monsterController.save(new Monster(900,"大虾","夹子功"));
//封装成dao_修改对象
@Test
public void daoUpdate(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
MonsterController monsterController = applicationContext.getBean(MonsterController.class);
Monster monster = new Monster(400, "", "泼水");
monsterController.update(monster);
//封装成dao_删除对象
@Test
public void deleteById(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
MonsterController monsterController = applicationContext.getBean(MonsterController.class);
monsterController.deleteById(400);
7 声明式事务
事务的4个特性:原子性:不管操作多少个都是一个整体
一致性:数据一致性
隔离性:不同事务对数据的隔离程度不同 [读未提交、读已提交、可重复读、可以串行化]
持久性:一旦提交,就入库
事务在哪里会用到? 账户减额和转账,这2步要通过一个整体来完成。
当我们处理一个比较复杂的业务需求时,比如银行转账,比如淘宝网上支付,涉及到多个表的问题。有时候表还不在同一个系统,需要调接口得到响应后再操作本地表,跨系统场景 。
以前都是按照编程式事务来做的。
7.1 事务分类
1.编程式事务
在编码中直接控制,一般是硬编码。看一段示意代码:
示意代码:
//到数据库验证
Connection connection = null;
ResultSet rs = null;
PreparedStatement ps = null;
try {
//1.加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
//2.得到连接
connection = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:orcl","scott","admin");
//3.创建preparedSatement
ps = connection.prepareStatement("select * from users where id=? and password=?");
//给?赋值
ps.setObject(1,userId);
ps.setObject(2,password);
//4.执行操作
rs = ps.executeQuery();
connection.commit();
//5.拿到结果集
if(rs.next()){
//进来,说明该用户合法
//跳转到下一个页面
//resp.sendRedirect("/UserManager/mainJsp?userId="+userId);
request.getRequestDispatcher("/mainJsp").forward(request,response);
}else{
//resp.sendRedirect("/UserManager/loginJsp");
request.setAttribute("errorInfo","用户Id或者密码有误");
request.getRequestDispatcher("/loginJsp").forward(request,response);
}catch (Exception e){
connection.rollback();
}finally {
//关闭资源
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
特点:
(1).比较直接,也比较好理解,成功就提交,不成功就回滚
(2).灵活度不够,而且功能也不够强大
2.声明式事务
即我们可以通过注解的方式来说明哪些方法是事务控制的,并且可以指定事务的隔离级别。
【举例说明】
我们需要去处理用户购买商品的业务逻辑:
分析:当一个用户要去购买商品应该包含三个步骤:
1.通过商品id获取价格
2.购买商品(某人购买商品,修改用户的余额)
3.修改库存表
其实大家可以看到,这时,我们需要涉及到三张表:商品表,用户表,商品库存表。应该使用事务处理。
解决方法:
1.使用传统的编程事务来处理,将代码写到一起【缺点:代码冗余,不利于扩展,优点:简单,好理解】
2.使用spring的声明式事务处理,可以将上面三个子步骤分别写成一个方法,然后统一管理【这个是我们spring很牛的地方,在开发使用的很多【优点:无代码冗余,效率高,扩展方便,缺点:理解困难】
声明式事务示意图VS编程式事务示意图
GoodsService.java 购物Service
@事务
public class GoodsService{
public void buyGoods(int nums){
float price =GoodsDao.queryById(); //1.根据id查询价格
float totalPrice = price*nums;
UserDaoupdateBalance(idtotalPrice); //2.对购物用户的账号扣款
GoodsDaoupdateAmount(id,nums); //3.修改库存量
GoodsDao.java 商品Dao
public class GoodsDao{
//根据id查询价格
public float queryById(id){
//....
//修改库存量
public void updateAmount(id,num){
//....
UserDao.java 用户Dao
public class UserDao{
//对购物用户的账号扣款
public void updateBalance(id,money){
//....
7.2 声明式事务环境搭建
创建3张表
-- 用户账户表
create table user_account(
user_id int primary key,
user_name varchar(32) not null default '',
money double not null default 0
-- 初始化用户账户表
insert into user_account values (100,'张三',1000);
insert into user_account values (200,'李四',2000);
-- 商品表
create table goods(
goods_id int primary key,
goods_name varchar(32) not null default '',
price double not null default 0.0
-- 初始化商品表
insert into goods values(100,'小风扇',10.00);
insert into goods values(200,'小台灯',12.00);
insert into goods values(300,'可口可乐',3.00);
-- 库存量表
create table goods_amount(
goods_id int primary key,
goods_num int default 0
-- 初始化库存量表
insert into goods_amount values(100,200);
insert into goods_amount values(200,20);
insert into goods_amount values(300,13);
7.3 使用声明式事务
使用声明式事务完成一个用户购买商品的业务处理
加入事务,控制用户购买某商品的数据一致性
【代码】
GoodsService.java
@Service
public class GoodsService {
@Autowired
private GoodsDao goodsDao;
@Autowired
private UserDao userDao;
//当我们在这里加入了一个 @Transactional注解后,那么buyGoods就是被事务控制了
@Transactional
public void buyGoods(int userId,int goodsId,int num){
//1.查询价格
float price = goodsDao.queryPriceById(goodsId);
//付款总数
float totalPrice = price*num;
//2.修改余额 --付款总数
userDao.updateBalance(userId,totalPrice);
//3.修改库存量
goodsDao.updateAmount(goodsId,num);
GoodsDao.java
@Repository
public class GoodsDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//根据id获取商品的价格
public float queryPriceById(int goodsId){
String sql = "select price from goods where goods_id = ?";
Float price = jdbcTemplate.queryForObject(sql, Float.class, goodsId);
return price;
//修改库存量
public void updateAmount(int goodsId,int goodsNum){
String sql = "update goods_amount set1 goods_num = goods_num - ? where goods_id = ?";
int i = jdbcTemplate.update(sql, goodsNum, goodsId);
if(i>0){
System.out.println("修改商品"+goodsId+"库存量成功!");
UserDao.java
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//修改某个用户的余额
public void updateBalance(int userId,float money){
String sql = "update user_account set money = money - ? where user_id = ?";
int i = jdbcTemplate.update(sql, money, userId);
if(i>0){
System.out.println("修改"+userId+"的余额成功!");
beans.xml
<!--配置事务管理器-->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourece" />
</bean>
<!--开启基于注解的声明式事务功能-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
测试类TransactionTest
public class TransactionTest {
//购买商品
@Test
public void transaction04(){
//获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//获取jdbcTemplete容器
GoodsService goodsService = applicationContext.getBean(GoodsService.class);
//100号用户买了100号商品 5个
goodsService.buyGoods(100,100,5);
声明式事务步骤:
步骤1:在beans.xml中配置事务管理器
<!--配置事务管理器-->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourece" />
</bean>
步骤2:在beans.xml中开启事务功能
<!--开启基于注解的声明式事务功能-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
步骤3:在方法上加@Transactional注解
//当我们在这里加入了一个 @Transactional注解后,那么buyGoods就是被事务控制了
@Transactional
public void buyGoods(int userId,int goodsId,int num){
//1.查询价格
float price = goodsDao.queryPriceById(goodsId);
//付款总数
float totalPrice = price*num;
//2.修改余额 --付款总数
userDao.updateBalance(userId,totalPrice);
//3.修改库存量
goodsDao.updateAmount(goodsId,num);
7.4 事务的传播机制
事务的传播机制基本说明:
当有多个事务并存时,如何控制?
比如用户去购买两次商品(使用不同的方法),每个方法都是一个事务,
那么如何控制呢?==》这就是事务的传播机制,看一个具体的案例:
spring一共提供了7种事务传播机制,常用的是required和required_new事务传播机制。
required传播机制:
required_new传播机制:
//required_new事务传播机制 propagation = Propagation.REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoods(int userId,int goodsId,int num){
默认情况下,事务传播机制是required传播机制
7.5 事务的隔离级别
首先,大家要清楚MySQL有四种隔离级别
读未提交 read uncommitted
==读已提交 read committed 默认的隔离级别==
可重复读 repeateable read
可串行化 serializable
事务隔离级别的设置:
isolation = Isolation.READ_COMMITTED
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void buyGoods(int userId,int goodsId,int num){
7.6 事务的超时回滚
【基本介绍】
如果一个事务执行的时间超过某个时间限制,就让该事务回滚。
可以通过设置事务超时回滚来实现。
【基本语法】
@Transactional(timeout = 2) 单位是秒 超时时间是2秒
表示这个事务如果2秒钟没有完成,就自动回滚
@Service
public class GoodsService {
@Transactional(timeout = 2) //单位是秒 超时时间是2秒
public void buyGoods(int userId,int goodsId,int num){
7.7 事务的只读模式
【基本介绍】
如果一个事务执行的操作都是读的操作,我们可以明确的指定该事务是readOnly,这样便于数据库底层对其操作进行优化处理。
【基本语法】
@Transactional(readOnly = true) 只读模式,效率更高,没有不必要的检查
@Service
public class GoodsService {
@Transactional(readOnly = true) //只读模式
public void buyGoods(int userId,int goodsId,int num){
7.8 基于xml的事务配置
【基本介绍】
除了通过注解来配置声明式事务,还可以通过xml的方式来配置事务。
【举例说明】
把前面的buyGoods()使用xml的方式来配置事务。
beans.xml
<!--配置事务切面,指定事务的管理器,和对哪些方法进行管理-->
<tx:advice id="myAdvice" transaction-manager="dataSourceTransactionManager" >
<tx:attributes>
<tx:method name="buyGoods" read-only="true" />
</tx:attributes>
</tx:advice>
<!--配置切入点,并和配置事务切面关联-->