赞
踩
Spring学习笔记_韩顺平
基本介绍:Spring是一个管理框架的框架,可以理解成就是一个超大的容器
说明:spring是一个管理框架的框架,因此他可以以各种组合的方式来集成Struts2,SpringMVC,Hibernate、MyBaties,
为了让大家灵活掌握各种方式,加深对Spring框架的特点,我们将会给大家演示不同的整合方式。
在实际开发中,完全可能是SS/SH/SSH的方式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1XLnicPu-1653839641237)(https://gitee.com/tangguanlin2006/image/raw/master/20220306185030.png)]
1.Spring是可以整合其他框架的框架(是管理框架的框架)
2.Spring有两个核心概念:IOC 和 AOP
3.IOC(控制反转)
传统的编程:
程序–读取—>环境(配置信息)----->业务操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZHfw39Lh-1653839641240)(https://gitee.com/tangguanlin2006/image/raw/master/20220306193307.png)]
Spring设计理念:
程序 <----- 环境(创建好对象)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZasRb2ba-1653839641242)(https://gitee.com/tangguanlin2006/image/raw/master/20220306193354.png)]
4.DI,依赖注入,可以理解为IOC的另外叫法
5.Spring最大的价值,就是可以接管Struts2,MyBatis,通过配置,给程序提供需要使用的Action/Service/Dao对象,
这个是核心价值所在,也是IOC的具体体现。
案例说明:通过Spring的IOC容器【可以理解成就是beans.xml文件】创建对象,并给对象属性赋值。
开发步骤:(看老师演示一遍)
通过IOC容器配置一个Monster对象(bean),并且通过Java程序来获取这个bean,并输出信息。
步骤: 安装一把Spring开发插件
。开发Spring的IOC容器beans.xml,该文件放在src目录
点击 src->new–>spring bean config -->名字 beans.xml —>勾选 beans命名空间
File–>New–>Project…
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gSMmbnA3-1653839641243)(https://gitee.com/tangguanlin2006/image/raw/master/20220306225757.png)]
在弹出的界面中,选择Spring,勾选“Spring”,
勾选“Create empty spring-config.xml”,创建空的spring bean文件,
选择“download”,
点击“Next”
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UXVMcJTa-1653839641244)(https://gitee.com/tangguanlin2006/image/raw/master/20220306225926.png)]
在弹出的界面中,输入项目名称,
选择项目路径,
点击“Finish”
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ynJYuvGy-1653839641245)(https://gitee.com/tangguanlin2006/image/raw/master/20220307000350.png)]
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 + '\'' + '}'; } }
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>
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); } }
项目结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AS8YFIYN-1653839641246)(https://gitee.com/tangguanlin2006/image/raw/master/20220307001133.png)]
运行结果:
Monster被加载...
Monster{id=100, nickname='牛魔王', skill='芭蕉扇'}
说明:本章讲解的内容就是IOC容器中如何配置bean的各种方法,
以及如何给配置的bean注入属性值。
为什么这样就把所有的bean加载了,到底怎么加载的?
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“beans.xml”);
前面快速入门案例是通过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在一个线程中只需要一个对象实例的情况,单例
指定构造器来获取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='吃人'}
在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='吐口水'}
在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='芭蕉扇'}}
在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='顺风耳'}}
在Spring的IOC容器,看看如何给bean对象的集合类型属性赋值
给bean对象的List,Map,Properties集合属性赋值
主要看一下List/Map/Properties三种集合的使用
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">
<list>
<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'}]}
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"> <map> <entry> <key><value>monstermap1</value></key> <ref bean="monster03"></ref> </entry> <entry> <key><value>monstermap2</value></key> <ref bean="monster04"></ref> </entry> </map> </property> </bean>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LT0Mey5y-1653839641248)(https://gitee.com/tangguanlin2006/image/raw/master/20220311095348.png)]
测试类 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='吐口水'}}}
【细节说明】
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>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pZ2fAZcd-1653839641249)(https://gitee.com/tangguanlin2006/image/raw/master/20220311103408.png)]
测试类: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
在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
在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='哈趴狗'}}
在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='喝水'}
在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='喝水'}
在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='抓老鼠'}
在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'}
案例说明:
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。
【案例说明】
在spring的IOC容器,在默认情况下是按照单例创建的,即配置一个bean对象后,IOC容器只会创建一个bean实例。
如果我们希望IOC容器配置的某个bean对象,是以多个实例形式创建的则可以通过配置scope="prototype"来指定
【细节说明】
1.默认是单例,当 设置为多实例机制
2.设置为scope="prototype"后,该bean是在getBean时才创建。
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
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
【案例说明】
在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岁,安息...
【案例说明】
在spring的IOC容器,可以配置bean的后置处理器,该处理器会在bean初始化方法调用前和初始化方法后被调用
(程序员可以在后置处理器中编写自己的代码)
【细节说明】
1.后置处理器,需要实现BeanPostProcessor接口
2.后置处理器,也是一个bean对象,如果希望它正常工作,需要在IOC容器中配置,
这样每一个bean被初始化的前后,都会调用程序员编写的代码(该bean在配置时指定了初始化方法)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGNy4gbb-1653839641251)(https://gitee.com/tangguanlin2006/image/raw/master/20220328164310.png)]
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快乐的玩耍
【案例说明】
在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'}
【案例说明】
在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一个对象");
}
}
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>
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" />
按名字自动装配原理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jSQKtYO8-1653839641253)(https://gitee.com/tangguanlin2006/image/raw/master/20220328220140.png)]
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一个对象
使用spring el表达式完成各种使用
Spring Expression Language,Spring表达式语言,简称SpEL。
支持运行时查询并可以操作对象图。
和JSP页面上的EL表达式、Struts2中用到的OGNL表达式一样,
spring EL根据JavaBean风格的getXXX()、setXXX()方法定义的属性访问对象图,完全符合我们熟悉的操作习惯。
spring EL使用#{…} 作为定界符,所有在大括号中的字符都将被认为是spring EL表达式。
说明:spring EL表达式,知道怎么使用即可
基本语法: #{表达式}
#{monster04.nickname}
演示一下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
读的书是天龙八部
基于注解的方式配置bean–通过类型获取
基于注解的方式配置bean,主要是项目开发中的组件,比如:Action、Service、和Dao
原理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s7SoTJif-1653839641254)(https://gitee.com/tangguanlin2006/image/raw/master/20220329225818.png)]
@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>
使用注解的方式配置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
【说明】
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
这个用的更多一点
在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();
}
}
【基本介绍】
为了更好的管理有继承和相互依赖的bean的自动装配,spring还提供一种基于泛型依赖的注入机制。
【说明】
1.传统方法就是直接将PhoneDao/BookDao自动装配到PhoneService/Bookservice中
2.我们现在使用基于泛型的依赖注入,因为我们只是一级继承关系,所以看起来比原来的要麻烦些,
但是后面如果继承关系复杂,就会有很大的优越性(大家体会)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h3Wpk7vz-1653839641256)(https://gitee.com/tangguanlin2006/image/raw/master/20220522152037.png)]
【代码】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mnUjCUgk-1653839641257)(https://gitee.com/tangguanlin2006/image/raw/master/20220522152147.png)]
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
有一个SmartAnimal接口,可以完成对简单的加法和减法,要求在执行加法getSum()和减法getSub()时,
输出执行前,执行过程,执行后的日志输出,请思考如何实现。
传统的解决思路,在各个方法的[前,执行过程,后]输出日志
代码:
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.缺点就是日志代码维护不方便,代码复用性差—每个方法都要写
解决思路:
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.缺点:不太好理解,复杂度高
1.AOP的全称(aspect oriented programming),面向切面编程
2.一张示意图说明AOP的相关概念
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DyibuuEf-1653839641259)(https://gitee.com/tangguanlin2006/image/raw/master/20220522212317.png)]
3.aop的实现方式
(1)基于动态代理的方式[内置aop实现]
(2)使用框架aspectj来实现
4.AOP的相关概念–看一下示意图来说明
(1)日志
(2)连接点(切入点):切面类可以通过连接点,获取到目标方法信息
(3)通知(5种)
(4)切入表达式
1.基于动态代理的方式【内置aop实现】
2.使用框架aspectj来实现
1.需要引入核心的aspect包,后面案例会具体说明
2.在切面类中声明通知方法
前置通知: @Before 在目标类的目标方法前执行
返回通知: @AfterReturning 返回通知,目标类的目标方法执行完毕后,就执行的代码
异常通知: @AfterThrowing 当执行的目标方法发生异常时,被调用
后置通知: @After
环绕通知: @Around 一个环绕通知, 包含前面4个通知的作用
3.五种通知和前面写的动态代理类方法的对应关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tB6HodJt-1653839641260)(https://gitee.com/tangguanlin2006/image/raw/master/20220522214530.png)]
说明1:环绕通知可以完成另外4个通知的所有的事情
说明2:执行流程说明
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kqxjj6GS-1653839641261)(https://gitee.com/tangguanlin2006/image/raw/master/20220522215203.png)]
【案例演示】
我们使用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);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mk8iKEqu-1653839641262)(https://gitee.com/tangguanlin2006/image/raw/master/20220526172844.png)]
运行结果:
前置通知
方法内部打印:result=86.0
最终通知通知
返回通知
前置通知
方法内部打印:result=50.0
最终通知通知
返回通知
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.切入点表达式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L7QAD8Vi-1653839641263)(https://gitee.com/tangguanlin2006/image/raw/master/20220523134817.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YdnXJrxG-1653839641264)(https://gitee.com/tangguanlin2006/image/raw/master/20220523134853.png)]
JoinPoint:切点 用到砍的地方
通过JoinPoint获取调用函数的签名
看一个需求:任何在调用前置消息对应的方法时,得到方法名,参数信息。
通过连接点得到目标方法的参数
Object[] args = joinPoint.getArgs();
获取连接点(切点)的签名
Signature signature = joinPoint.getSignature();
通过连接点得到目标方法的名称
//得到函数的签名
Signature signature = joinPoint.getSignature();
//得到目标方法
String name = signature.getName();
通过连接点得到 目标类的类名
//得到目标类
Object target = joinPoint.getTarget();
String targetClass = target.getClass().getName(); //com.tangguanlin.aop.SmartDog
说明:在调用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]
增加一个字段:returning = “res”
举例说明:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vObvLLQl-1653839641265)(https://gitee.com/tangguanlin2006/image/raw/master/20220523154004.png)]
业务类 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
增加一个字段:throwing = “myExcepion”
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j2aeIAOu-1653839641267)(https://gitee.com/tangguanlin2006/image/raw/master/20220523173235.png)]
业务类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)
环绕通知可以完成其他四个通知要做的事情
【看一个需求】
如何使用环绕通知完成其他四个通知的功能。
【案例演示】
业务类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.环绕通知做的事情,和动态代理做的事情非常的类似
框架使用+框架怎么实现的
学编程本质---->学习框架,写项目的过程【提示对编程和设计思想】----->多写,多看【从量变到质变】
自己要写一篇很优秀的文字,自己就要看很多优秀的文字,
自己要写很优秀的代码,自己就要看很多优秀的代码。
这种水平上升是潜移默化的。当你的量到一定程度的时候,你会发现,以前觉得很难的东西,现在看简单了。
说明一个问题:
前面我们使用的切入表达式是分别写在自己切面方法的上面,这样切面表达式就会重复,利用率不高。
因此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)); } }
作用在同一个方法的多个切面的优先级
如果同一个函数,有多个切面在同一个切入点切入,那么执行的优先级如何控制
【 基本语法】
@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的返回通知
【基本说明】
前面我们是通过注解的方式来配置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>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CmjMlmab-1653839641269)(https://gitee.com/tangguanlin2006/image/raw/master/20220528145053.png)]
运行结果:
前置通知SmartXMLAspect 目标类com.tangguanlin.aop.SmartDog 目标方法的名称getSum 参数[35.0, 51.0]
方法内部打印:result=86.0
返回通知SmartXMLAspect res=86.0
最终通知SmartXMLAspect
看一个实际需求:
如果程序员就希望使用spring框架来做项目,不使用hibernate等其他持久层框架,那么spring框架如何处理对数据库的操作呢?
方案1. 使用同学前面做项目自己开发的JdbcUtils类
方案2. 其实spring提供了一个操作数据库(表)功能强大的类JdbcTemplate
我们可以同IOC容器来配置一个jdbcTemplate对象,使用它来完成对数据库表的各种操作。
【基本说明】
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
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XEJjLDqa-1653839641270)(https://gitee.com/tangguanlin2006/image/raw/master/20220528180818.png)]
步骤2:
新建jdbc.propertis配置文件,放在类路径下
配置文件多的情况下,也可以创建源码包Sources包,本质仍然是类路径,只是Idea做了一个文件的映射管理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7lmk254Z-1653839641271)(https://gitee.com/tangguanlin2006/image/raw/master/20220528181642.png)]
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
创建数据源的目的,就是给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>
获取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);
}
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q3gigRxV-1653839641272)(https://gitee.com/tangguanlin2006/image/raw/master/20220528185239.png)]
数据插入成功
jdbcTemplate的相关方法
执行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);
}
查询一条记录,按指定类型返回
//查询单个对象
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;
}
查询一条记录,按指定类型返回,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); }
查询多条数据,按指定类型返回
//查询列表
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 update(Monster monster){
String sql = "update monster set skill = ? where id= ? ";
jdbcTemplate.update(sql,monster.getSkill(),monster.getId());
}
添加多条数据
//批量添加数据 @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); }
添加一个新的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);
}
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xTP9oO2D-1653839641273)(https://gitee.com/tangguanlin2006/image/raw/master/20220528185239.png)]
数据插入成功
添加多个新的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); } }
修改一个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修改成功!"); } } }
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xd2q35a9-1653839641274)(https://gitee.com/tangguanlin2006/image/raw/master/20220528211654.png)]
查询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='吐火'}
查询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='变丑女'}]
]
查询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
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条记录
<!--引入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>
@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); } }
@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); } }
@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); } }
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); } }
事务的4个特性:原子性:不管操作多少个都是一个整体
一致性:数据一致性
隔离性:不同事务对数据的隔离程度不同 [读未提交、读已提交、可重复读、可以串行化]
持久性:一旦提交,就入库
事务在哪里会用到? 账户减额和转账,这2步要通过一个整体来完成。
当我们处理一个比较复杂的业务需求时,比如银行转账,比如淘宝网上支付,涉及到多个表的问题。有时候表还不在同一个系统,需要调接口得到响应后再操作本地表,跨系统场景 。
以前都是按照编程式事务来做的。
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编程式事务示意图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d8w9iDKW-1653839641275)(https://gitee.com/tangguanlin2006/image/raw/master/20220529134402.png)]
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){
//....
}
}
创建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);
使用声明式事务完成一个用户购买商品的业务处理
加入事务,控制用户购买某商品的数据一致性
【代码】
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);
}
事务的传播机制基本说明:
当有多个事务并存时,如何控制?
比如用户去购买两次商品(使用不同的方法),每个方法都是一个事务,
那么如何控制呢?==》这就是事务的传播机制,看一个具体的案例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kQDPWEGa-1653839641276)(https://gitee.com/tangguanlin2006/image/raw/master/20220529173012.png)]
spring一共提供了7种事务传播机制,常用的是required和required_new事务传播机制。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ubcA34zy-1653839641277)(https://gitee.com/tangguanlin2006/image/raw/master/20220529173107.png)]
required传播机制:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KKJx0DZK-1653839641278)(https://gitee.com/tangguanlin2006/image/raw/master/20220529173144.png)]
required_new传播机制:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FbOk5dn0-1653839641279)(https://gitee.com/tangguanlin2006/image/raw/master/20220529173331.png)]
//required_new事务传播机制 propagation = Propagation.REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoods(int userId,int goodsId,int num){
}
默认情况下,事务传播机制是required传播机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XCpoBk1f-1653839641280)(https://gitee.com/tangguanlin2006/image/raw/master/20220529174617.png)]
首先,大家要清楚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){
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-alxveMCB-1653839641281)(https://gitee.com/tangguanlin2006/image/raw/master/20220529182811.png)]
【基本介绍】
如果一个事务执行的时间超过某个时间限制,就让该事务回滚。
可以通过设置事务超时回滚来实现。
【基本语法】
@Transactional(timeout = 2) 单位是秒 超时时间是2秒
表示这个事务如果2秒钟没有完成,就自动回滚
@Service
public class GoodsService {
@Transactional(timeout = 2) //单位是秒 超时时间是2秒
public void buyGoods(int userId,int goodsId,int num){
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Y7mZqLk-1653839641282)(https://gitee.com/tangguanlin2006/image/raw/master/20220529214212.png)]
【基本介绍】
如果一个事务执行的操作都是读的操作,我们可以明确的指定该事务是readOnly,这样便于数据库底层对其操作进行优化处理。
【基本语法】
@Transactional(readOnly = true) 只读模式,效率更高,没有不必要的检查
@Service
public class GoodsService {
@Transactional(readOnly = true) //只读模式
public void buyGoods(int userId,int goodsId,int num){
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BtG3AsFv-1653839641284)(https://gitee.com/tangguanlin2006/image/raw/master/20220529214905.png)]
【基本介绍】
除了通过注解来配置声明式事务,还可以通过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>
<!--配置切入点,并和配置事务切面关联-->
<aop:config>
<aop:pointcut id="myPointCut" expression="execution(public void
com.tangguanlin.transaction.service.GoodsService.buyGoods(int,int,int))"/>
<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut"/>
</aop:config>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n4uPbdwN-1653839641285)(https://gitee.com/tangguanlin2006/image/raw/master/20220529223458.png)]
//修改库存量
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 ```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);
}
事务的传播机制基本说明:
当有多个事务并存时,如何控制?
比如用户去购买两次商品(使用不同的方法),每个方法都是一个事务,
那么如何控制呢?==》这就是事务的传播机制,看一个具体的案例:
[外链图片转存中…(img-kQDPWEGa-1653839641276)]
spring一共提供了7种事务传播机制,常用的是required和required_new事务传播机制。
[外链图片转存中…(img-ubcA34zy-1653839641277)]
required传播机制:
[外链图片转存中…(img-KKJx0DZK-1653839641278)]
required_new传播机制:
[外链图片转存中…(img-FbOk5dn0-1653839641279)]
//required_new事务传播机制 propagation = Propagation.REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoods(int userId,int goodsId,int num){
}
默认情况下,事务传播机制是required传播机制
[外链图片转存中…(img-XCpoBk1f-1653839641280)]
首先,大家要清楚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){
}
[外链图片转存中…(img-alxveMCB-1653839641281)]
【基本介绍】
如果一个事务执行的时间超过某个时间限制,就让该事务回滚。
可以通过设置事务超时回滚来实现。
【基本语法】
@Transactional(timeout = 2) 单位是秒 超时时间是2秒
表示这个事务如果2秒钟没有完成,就自动回滚
@Service
public class GoodsService {
@Transactional(timeout = 2) //单位是秒 超时时间是2秒
public void buyGoods(int userId,int goodsId,int num){
}
}
[外链图片转存中…(img-1Y7mZqLk-1653839641282)]
【基本介绍】
如果一个事务执行的操作都是读的操作,我们可以明确的指定该事务是readOnly,这样便于数据库底层对其操作进行优化处理。
【基本语法】
@Transactional(readOnly = true) 只读模式,效率更高,没有不必要的检查
@Service
public class GoodsService {
@Transactional(readOnly = true) //只读模式
public void buyGoods(int userId,int goodsId,int num){
}
}
[外链图片转存中…(img-BtG3AsFv-1653839641284)]
【基本介绍】
除了通过注解来配置声明式事务,还可以通过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>
<!--配置切入点,并和配置事务切面关联-->
<aop:config>
<aop:pointcut id="myPointCut" expression="execution(public void
com.tangguanlin.transaction.service.GoodsService.buyGoods(int,int,int))"/>
<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut"/>
</aop:config>
[外链图片转存中…(img-n4uPbdwN-1653839641285)]
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。