Spring5
一、Spring下载
- 官方网址:https://spring.io/
- 在线文档:https://docs.spring.io/spring-framework/docs/current/reference/html
- 离线文档:解压spring-5.3.8-dist.zip : spring-framework-5.3.8\docs\reference\html\index.html
- 离线API:解压spring-5.3.8-dist.zip : spring-framework-5.3.8\docs\javadoc-api\index.html
二、内容介绍
1.spring学习的核心内容图
- Spring 核心学习内容IOC、AOP、jdbcTemplete,声明式事务
- IOC:控制反转,可以管理java对象
- AOP:切面编程
- JDBCTemplate:是spring提供一套访问数据库的技术,应用性强,相对好理解
- 声明式事务:基于IOC/AOP实现事务管理,理解需要时间
- IOC、AOP是重点同时是难点
三、Spring几个重要概念
- Spring可以整合其他的框架【Spring是管理框架的框架】
- Spring有两个核心的概念:IOC和AOP
- IOC【Inversion Of Control 反转控制】
- 传统的开发模式【JDBCUtils/反射】
- IOC的开发模式
- 程序<---------容器 //容器创建好对象,程序直接使用
- Spring 根据配置文件xml/注解,创建对象,并放入到容器(ConcurrentHashMap)中,并且可以完成对象之间的依赖【这个是由Spring来完成的】
- 当需要使用某个对象实例的时候,就直接从容器中获取即可
- 程序员可以更加关注如何使用对象完成相应的业务(以前是new ==>现在是注解/配置方式)
- DI——Dependency Injection 依赖注入,可以理解成是IOC的另外叫法
- Spring最大的价值,通过配置给程序员提供需要使用的Web层[Servlet(Action/Controller)/Service/Dao/JavaBean(entity)]对象,这个是核心价值所在,也是IOC的具体体现,实现解耦。
四、Spring 的初步使用
1.快速入门
javaBean package com.leon.spring.bean; /** * ClassName:Monster * Package:com.leon.spring.bean * Description: * 此类是一个JavaBean,是一个Monster类 * @Author: leon-->ZGJ * @Create: 2024/3/20 19:33 * @Version: 1.0 */ public class Monster { //ID号 private Integer id ; //名字 private String name; //年龄 private Integer age ; public Monster() { } public Monster(Integer id, String name, Integer age) { this.id = id; this.name = name; this.age = age; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Monster{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } } =================================================================================== 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"> <!-- 解读beans.xml文件 1.这个文件是用来配置JavaBean的 2.beans标签中可以定义多个的bean子标签 3.一个bean标签就代表一个bean对象 4.class属性是用于指定类的全路径==>Spring底层通过获取到的class属性值,使用反射创建JavaBean对象 5.id属性表示该Java对象在Spring容器中的id,可以通过id来获取容器中的JavaBean对象 --> <bean class="com.leon.spring.bean.Monster" id="monster"> <!-- 1.property标签是用来设置属性的 2.其中的name属性指的是JavaBean中的属性名,value属性是给JavaBean属性赋值的 --> <property name="id" value="1"/> <property name="name" value="牛魔王"/> <property name="age" value="10000"/> </bean> </beans> =================================================================================== SpringTest package com.leon.spring.test; import com.leon.spring.bean.Monster; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * ClassName:SpringTest * Package:com.leon.spring.test * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/20 19:49 * @Version: 1.0 */ public class SpringTest { @Test public void test01(){ //创建ApplicationContext容器 //该容器和容器配置文件关联 ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml"); //通过getBean()获取对应的对象 //获取的对象默认是Object对象,但是其运行对象是Monster //记住此时getBean()方法中的实参数据要是,你在容器配置文件中的id值,如果不一样则获取不到 Object monster = ioc.getBean("monster"); //打印对象 System.out.println(monster); //打印对象的运行类型 System.out.println(monster.getClass()); System.out.println("==========================="); //也可以直接返回你想要的对象类型 Monster monster1 = ioc.getBean("monster", Monster.class); //打印对象 System.out.println(monster1); //打印属性值 System.out.println(monster1.getName()); } }
2.注意事项和使用细节
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
为什么直接写文件名就能获取到容器的配置文件呢?
因为它是通过类路径来获取的,因为在src下的配置文件在通过加载解析之后文件会存放到项目的真实工作路径之下,下面是代码验证
//类加载路径的验证 @Test public void test02(){ //创建一个File对象 File file = new File(this.getClass().getResource("/").getPath()); //打印file类 System.out.println(file); } //结果 C:\software\javacodelibrary\main_stream_framework\out\production\spring5
图片
3.通过debug方式来了解Spring容器/机制
对ideal的debugger进行配置,方便了解结构
这些都要取消掉不要勾选
IOC容器debug解析
4.手写一个简单的Spring容器,方便理解
代码
package com.leon.myspring; import com.leon.spring.bean.Monster; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.io.File; import java.util.List; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; /** * ClassName:MyApplicationContext * Package:com.leon.myspring * Description: * 1.实现Spring的一个简单容器机制 * 2.后面还会详细的实现 * 3.这样做是为了理解Spring容器的机制 * @Author: leon-->ZGJ * @Create: 2024/3/21 19:10 * @Version: 1.0 */ public class MyApplicationContext { //用来存放创建好的单例对象 private ConcurrentHashMap<String, Monster> singletonObjects = new ConcurrentHashMap<>(); public MyApplicationContext(String xmlFile) throws Exception { //获取类的加载路径 String classPath = this.getClass().getResource("/").getPath(); //创建一个解析器 SAXReader reader = new SAXReader(); //读取document树 Document document = reader.read(new File(classPath+xmlFile)); //获取根节点 Element rootElement = document.getRootElement(); //获取想要的节点 Element bean = rootElement.elements("bean").get(0); //获取节点信息 //获取类的全路径 String classPathUrl = bean.attributeValue("class"); //获取id String id = bean.attributeValue("id"); //获取想要节点的子节点 List<Element> elements = bean.elements("property"); //通过属性名称获取属性值 String skill = elements.get(0).attributeValue("value"); String name = elements.get(1).attributeValue("value"); String age = elements.get(2).attributeValue("value"); //使用反射创建对象 //使用全路径获取class Class clazz = Class.forName(classPathUrl); //使用无参构造器创建对象 Monster monster = (Monster) clazz.newInstance(); //这里直接用方法赋值,不使用反射节省学习成本,后期会写完整 monster.setSkill(skill); monster.setAge(Integer.parseInt(age)); monster.setName(name); //将数据保存进容器中 singletonObjects.put(id,monster); } //通过容器中的id值来获取对象 public Monster getBean(String id){ return singletonObjects.get(id); } } ================================================================================ package com.leon.myspring.test; import com.leon.myspring.MyApplicationContext; import com.leon.spring.bean.Monster; /** * ClassName:MyApplicationContext * Package:com.leon.myspring.test * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/21 19:16 * @Version: 1.0 */ public class MyApplicationContextTest { public static void main(String[] args) throws Exception { MyApplicationContext myApplicationContext = new MyApplicationContext("beans.xml"); Monster monster = myApplicationContext.getBean("monster"); System.out.println(monster); } }
五、Spring管理bean-ioc容器
1.Spring配置/管理bean介绍
- bean管理包括两个方面
- 创建bean对象
- 给bean注入属性
- bean配置方式
- 基于xml文件配置方式
- 基于注解方式
2.基于xml配置bean
2.1 通过id的方式来获取bean
<!-- 解读beans.xml文件 1.这个文件是用来配置JavaBean的 2.beans标签中可以定义多个的bean子标签 3.一个bean标签就代表一个bean对象 4.class属性是用于指定类的全路径==>Spring底层通过获取到的class属性值,使用反射创建JavaBean对象 5.id属性表示该Java对象在Spring容器中的id,可以通过id来获取容器中的JavaBean对象 --> <bean class="com.leon.spring.bean.Monster" id="monster"> <!-- 1.property标签是用来设置属性的 2.其中的name属性指的是JavaBean中的属性名,value属性是给JavaBean属性赋值的 --> <property name="skill" value="芭蕉扇"/> <property name="name" value="牛魔王"/> <property name="age" value="10000"/> </bean> ============================================================================== //创建ApplicationContext容器 //该容器和容器配置文件关联 ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml"); //通过getBean()获取对应的对象 //获取的对象默认是Object对象,但是其运行对象是Monster //记住此时getBean()方法中的实参数据要是,你在容器配置文件中的id值,如果不一样则获取不到 Object monster = ioc.getBean("monster"); //也可以直接返回你想要的对象类型 Monster monster1 = ioc.getBean("monster", Monster.class);
2.2 通过类型来获取bean
<bean class="com.leon.spring.bean.Monster"> <property name="name" value="金角大王"/> <property name="skill" value="吐水"/> <property name="age" value="10000"/> </bean>
==============================================================================
//创建ioc容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
//通过类型来获取对象
Monster monster = ioc.getBean(Monster.class);
System.out.println(monster);+ 注意细节 + 按类型来获取bean,要求ioc容器中的同一类型的bean只能有一个,否则会抛出异常:NoUniqueBeanDefinitionException + 这种方式的应用场景:比如:XxxAction/Servlet,或XxxService 在一个线程中只需要一个对象实例的情况 + 在容器配置文件中给属性赋值,底层是通过setter方法完成的,因而在创建JavaBean时一定要给对象设置setter方法
2.3 通过指定构造器配置bean
<bean class="com.leon.spring.bean.Monster" id="monster4"> <!-- constructor-arg标签可以指定使用构造器的参数 1.name属性是通过属性名称来形参赋值 2.index属性是通过下标来给构造器的形参进行复制 注意:下标是从0开始的 3.type属性是通过类型给形参赋值 注意:1.赋值的顺序要和你定义的形参位置要相同,不然会赋值错位 2.为什么能够准确的选择构造器呢? 因为在java中构造器虽然可以重载,但是不可以出现相同类型的形参列表 --> <!--<constructor-arg name="age" value="100"/>--> <!--<constructor-arg name="name" value="孙悟空"/>--> <!--<constructor-arg name="skill" value="七十二变"/>--> <!--<constructor-arg index="0" value="铁扇公主"/>--> <!--<constructor-arg index="1" value="芭蕉扇"/>--> <!--<constructor-arg index="2" value="1000"/>--> <constructor-arg type="java.lang.String" value="猪八戒"/> <constructor-arg type="java.lang.String" value="地罡三十八变"/> <constructor-arg type="java.lang.Integer" value="10000"/> </bean>
==============================================================================
//通过构造器来配置bean
@Test
public void test05(){
//创建ioc容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
//通过类型来获取对象
Monster monster = ioc.getBean("monster4",Monster.class);
System.out.println(monster);
}+ 注意细节 + index属性是通过下标来区分第几个参数的,下标是从0开始的 + type属性是通过类型给形参赋值 + 赋值的顺序要和你定义的形参位置要相同,不然会赋值错位 + 为什么能够准确的选择构造器呢? + 因为在java中构造器虽然可以重载,但是不可以出现相同类型的形参列表
2.4 通过p名称空间来配置bean对象
<bean class="com.leon.spring.bean.Monster" id="monster5" p:name="沙僧" p:skill="戒杖" p:age="10000" />
==============================================================================
//通过p名称空间来配置bean
@Test
public void test06(){
//创建ioc容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
//通过类型来获取对象
Monster monster = ioc.getBean("monster5",Monster.class);
System.out.println(monster);
}
2.5 通过ref来配置bean
<!--配置MemberDaoImpl--> <bean class="com.leon.spring.dao.MemberDaoImpl" id="memberDao"/> <!--配置MemberServiceImpl--> <bean class="com.leon.spring.service.MemberServiceImpl" id="memberService"> <!-- 1.ref="memberDao" 表示 MemberServiceImpl对象属性memberDao引用对象id=memberDao的对象 1.这里ref的属性值是和上面定义的MemberDaoImpl-bean的id相同,如果不一样会报错的的 2.而且要引入/注入的bean在被引入的bean前后都没有关系的,因为整个容器配置文件是作为 一个整体来执行的,在扫描时作为一个整体扫描,然后再在ioc中记录bean之间的关系的,但是建议写在 前面方便阅读和理解 --> <property name="memberDao" ref="memberDao"/> </bean>
==============================================================================
package com.leon.spring.dao;/**
- ClassName:MemberDaoImpl
- Package:com.leon.spring.dao
- Description:
- @Author: leon-->ZGJ
- @Create: 2024/3/22 11:00
- @Version: 1.0
*/
public class MemberDaoImpl {public MemberDaoImpl() {
System.out.println("memberDaoImpl被创建了"); } public void add(){ System.out.println("MemberDaoImpl的add()方法被调用了······"); }
}
==============================================================================
package com.leon.spring.service;import com.leon.spring.dao.MemberDaoImpl;
/**
ClassName:MemberServiceImpl
- Package:com.leon.spring.service
Description:
@Author: leon-->ZGJ
@Create: 2024/3/22 11:01
@Version: 1.0
*/
public class MemberServiceImpl {private MemberDaoImpl memberDao ;
public MemberDaoImpl getMemberDao() {
return memberDao;
}public void setMemberDao(MemberDaoImpl memberDao) {
this.memberDao = memberDao;
}public void add(){
memberDao.add();
}
}==============================================================================
//通过ref名称空间来配置bean
@Test
public void test07(){
//创建ioc容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
//通过类型来获取对象
MemberServiceImpl memberService = ioc.getBean("memberService", MemberServiceImpl.class);
memberService.add();
}
2.6通过内部bean来配置属性
<!--配置MemberServiceImpl--> <bean class="com.leon.spring.service.MemberServiceImpl" id="memberService2"> <property name="memberDao"> <!-- 1.配置了一个内部bean,并且注入到了属性中 --> <bean class="com.leon.spring.dao.MemberDaoImpl"/> </property> </bean>
==============================================================================
//通过ref名称空间来配置bean
@Test
public void test08(){
//创建ioc容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
//通过类型来获取对象
MemberServiceImpl memberService = ioc.getBean("memberService2", MemberServiceImpl.class);
memberService.add();
}
2.7 对集合数组属性进行配置
<bean class="com.leon.spring.bean.Master" id="master"> <property name="name" value="太上老君"/> <!-- 1.对List进行配置 --> <property name="monsterList"> <list> <!-- 1.list标签代表List集合 2.可以通过外部引入或者注入 3.也可以内部配置 4.List可以存储对象,数组,集合等 --> <ref bean="monster5"/> <bean class="com.leon.spring.bean.Monster"> <property name="name" value="老鼠精"/> <property name="skill" value="吃大米"/> <property name="age" value="10000"/> </bean> </list> </property> <!-- 1.对Map进行配置 --> <property name="monsterMap"> <map> <entry> <key> <value>monster2</value> </key> <ref bean="monster2"/> </entry> <entry> <key> <value>monster3</value> </key> <ref bean="monster3"/> </entry> <entry> <key> <value>monster6</value> </key> <bean class="com.leon.spring.bean.Monster"> <property name="age" value="300"/> <property name="name" value="金蝉"/> <property name="skill" value="念经"/> </bean> </entry> </map> </property> <!-- 1.对set进行赋值 --> <property name="monsterSet"> <set> <ref bean="monster3"/> <bean class="com.leon.spring.bean.Monster"> <property name="name" value="银角大王"/> <property name="skill" value="射珠子"/> <property name="age" value="10000"/> </bean> </set> </property> <!-- 1.对数组进行赋值 --> <property name="monsterName"> <array> <value>小妖怪</value> <value>中妖怪</value> <value>大妖怪</value> </array> </property> <!-- 1.对Properties赋值 --> <property name="properties"> <props> <prop key="user">root</prop> <prop key="password">root</prop> <prop key="url">www.baidu.com</prop> </props> </property> </bean>
+ 使用细节 + 主要掌握List/Map/Properties三种集合 + Properties集合的特点 + 这个Properties是Hashtable的子类,是key-value的形式 + key是String而value也是String
2.8 使用util:list 进行配置
<!-- 1.定义一个util:list标签 并且指定id 可以达到数据复用 --> <util:list id="book"> <value>围城</value> <value>兄弟</value> <value>阿Q正传</value> <value>java编程教学</value> </util:list> <bean class="com.leon.spring.bean.BookStore" id="bookStore"> <!--这里可以通过ref引入和注入--> <property name="bookStore" ref="book"/> </bean> =============================================================================
//使用util:list进行配置
@Test
public void test10(){
//创建ioc容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
//通过类型来获取对象
BookStore bookStore = ioc.getBean("bookStore", BookStore.class);System.out.println(bookStore.getBookStore()); }
2.9 属性级联赋值配置
<!--配置Dept--> <bean class="com.leon.spring.bean.Dept" id="dept"/> <!--配置Emp--> <bean class="com.leon.spring.bean.Emp" id="emp"> <property name="name" value="小强"/> <property name="dept" ref="dept"/> <!-- 1.这里希望给dept的name属性赋值,这里就可以使用【属性级联赋值】 --> <property name="dept.name" value="人力资源"/> </bean>
==============================================================================
//使用属性级联赋值进行配置
@Test
public void test11(){
//创建ioc容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans.xml");
//通过类型来获取对象
Emp emp = ioc.getBean("emp", Emp.class);System.out.println(emp); }
2.10 通过静态工厂获取bean
<bean class="com.leon.spring.bean.MyStaticFactory" id="myStaticFactory" factory-method="getMonster"> <!-- 1.配置静态工厂的class和id跟前面配置普通的bean一样 2.factory-method标签是指定使用静态工厂的哪个方法返回对象 3.constructor-arg value="monster001" 该标签中的value属性值 是表示你要获取静态工厂的哪个对象,也可以理解是传入getMonster方法实参 --> <constructor-arg value="monster001"/> </bean>
==============================================================================
//通过静态工厂获取bean对象
@Test
public void test12() {
//创建ioc容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans2.xml");
//通过类型来获取对象/* * 1.每次获取的对象都是同一个,不是新创建的,都是之前创建好的 * 2.因为静态属性和成员都只会加载一次,所以每次获取的都是同一个对象【自己的理解】 * */ Monster monster = ioc.getBean("myStaticFactory", Monster.class); Monster monster2 = ioc.getBean("myStaticFactory", Monster.class); System.out.println(monster); System.out.println(monster == monster2); }
2.11 通过实例工厂获取bean
<bean class="com.leon.spring.bean.MyInstanceFactory" id="myInstanceFactory"/>
<bean id="monster" factory-bean="myInstanceFactory" factory-method="getMonster"> <constructor-arg value="monster003"/> </bean>
==============================================================================
//通过实例工厂获取bean对象
@Test
public void test13() {
//创建ioc容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans2.xml");
//通过类型来获取对象
/*
* 1.只要指定的实例工厂没有重新创建或者是单例模式,每次获取的bean对象都是同一个
* 2.因为你的对象没有发生改变,虽然说是一个实例工厂,但是其对象引用没发生改变
* 所以每次获取的同一个id的对象都是相同的
* */Monster monster = ioc.getBean("monster", Monster.class); Monster monster2 = ioc.getBean("monster", Monster.class); System.out.println(monster); System.out.println(monster == monster2); }
2.12 通过FactoryBean获取bean
<bean class="com.leon.spring.bean.MyFactoryBean" id="factoryBean"> <property name="key" value="monster005"/> </bean>
==============================================================================
//通过实例工厂获取bean对象
@Test
public void test14() {
//创建ioc容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans2.xml");
Monster monster = ioc.getBean("factoryBean", Monster.class);System.out.println(monster); }
2.13 Bean配置信息复用
<!-- 1.Bean配置信息的复用 --> <bean class="com.leon.spring.bean.Monster" id="monster4"> <property name="name" value="黄袍怪"/> <property name="skill" value="金箔"/> <property name="age" value="10000"/> </bean> <!-- 1.parent标签是指定当前对象属性从哪个Bean来获取 2.parent标签标签的属性值是Bean的id --> <bean class="com.leon.spring.bean.Monster" id="monster3" parent="monster4"/> <!-- 1.abstract该标签是表示该Bean是作为一个专门用来继承的,它不会在Spring容器中创建 2.abstract该标签属性值是true和false,true表示Bean是一个抽象的专门用来继承的 3.如果一个Bean是特意用来作为复用的,可以使用abstract标签,将属性值设置为true --> <bean class="com.leon.spring.bean.Monster" id="monster2" abstract="true"> <property name="name" value="黄袍怪--"/> <property name="skill" value="金箔--"/> <property name="age" value="10000"/> </bean> <bean class="com.leon.spring.bean.Monster" id="monster5" parent="monster2"/> ============================================================================== //bean配置信息的复用 @Test public void test15() { //创建ioc容器 ApplicationContext ioc = new ClassPathXmlApplicationContext("beans2.xml"); Monster monster = ioc.getBean("monster3", Monster.class); Monster monster2 = ioc.getBean("monster5", Monster.class); System.out.println(monster); System.out.println(monster2); }
2.14 bean的创建顺序
<bean class="com.leon.spring.service.MemberServiceImpl" id="memberService"> <property name="memberDao" ref="memberDao"/> </bean> <bean class="com.leon.spring.dao.MemberDaoImpl" id="memberDao" />
2.15 bean的单例和多实例
说明
- 在Spring的ioc容器中,默认是按照单例创建的,即配置一个bean对象后,ioc容器只会创建一个bean实例。如果我们希望ioc容器配置的某个bean对象,是以多个实例形式创建的则可以通过配置scope="prototupy"来指定
<!-- 1.bean的单例和多实例 2.在默认的情况下scope的属性是singleton,因而在ioc容器中,只有一个id=cat的bean对象, 也就是在使用getBean()方发获取的是同一个对象 3.如果想要每次使用getBean()获取的对象都是新创建的,则将scope属性值设置为prototype 4.lazy-init属性表示是否是懒加载,如果是懒加载,则不会提前创建好对象,只会在获取时创建 5.如果bean配置的是 scope="singleton" lazy-init="true" 这时ioc不会提前创建好对象 而是当你执行getBean()时创建,当然他只会创建一次,因为你是单例 --> <bean class="com.leon.spring.bean.Cat" id="cat" scope="prototype" lazy-init="true"> <property name="name" value="小花猫"/> <property name="id" value="1"/> </bean> ============================================================================== <!-- 1.bean的单例和多实例 2.在默认的情况下scope的属性是singleton,因而在ioc容器中,只有一个id=cat的bean对象, 也就是在使用getBean()方发获取的是同一个对象 3.如果想要每次使用getBean()获取的对象都是新创建的,则将scope属性值设置为prototype 4.lazy-init属性表示是否是懒加载,如果是懒加载,则不会提前创建好对象,只会在获取时创建 5.如果bean配置的是 scope="singleton" lazy-init="true" 这时ioc不会提前创建好对象 而是当你执行getBean()时创建,当然他只会创建一次,因为你是单例 --> <bean class="com.leon.spring.bean.Cat" id="cat" scope="prototype" lazy-init="true"> <property name="name" value="小花猫"/> <property name="id" value="1"/> </bean>
使用细节
- 默认是单例singleton,在启动容器时,默认就会创建,并放入到singletonObjects集合中
- 当
设置为多实例机制后,该bean是在getBean()时才会创建 - 如果是单例singleton,同时希望在getBean时才会创建,可以指定懒加载lazy-init="true"(注意:默认是false)
- 通常情况下,lazy-init就使用默认值false,在开发看来,用空间换时间是值得的,除非有特殊要求
- 如果scope="prototype"这时你的lazy-init属性的值不管是true,还是false都是在getBean时候才创建对象
2.16 bean的生命周期
说明
- 执行构造器
- 执行set相关方法
- 调用bean的初始化的方法(需要配置)
- 使用bean--->使用getBean()方法获取bean对象
- 当容器关闭时候,调用bean的销毁方法(需要配置){容器默认情况下是不会关闭的,需要自己调用关闭的方法}
package com.leon.spring.bean; /** * ClassName:House * Package:com.leon.spring.bean * Description: * 这个类是用来演示生命周期的 * @Author: leon-->ZGJ * @Create: 2024/3/25 15:04 * @Version: 1.0 */ public class House { /* * 1.这个类中方法是与程序员本身自己定义,没有固定要求,通常要求见名之意 * */ private String name ; public House() { System.out.println("House() 构造器~~~~"); } public String getName() { return name; } public void setName(String name) { System.out.println("setName() 设置数据:"+name); this.name = name; } public void init(){ System.out.println("init() 初始化数据~~~~"); } public void destroy(){ System.out.println("destroy() 销毁对象~~~~~"); } } ============================================================================== <!-- 1.生命周演示 2.init-method属性使用来指定bean的初始化方法,在setter方法后执行, init方法执行的时机,由Spring来控制的 3.destroy-method="destroy" 指定bean的销毁方法,在容器关闭的时候执行, destroy方法执行的时机,由Spring容器来控制 --> <bean class="com.leon.spring.bean.House" init-method="init" destroy-method="destroy" id="house"> <property name="name" value="北京大豪宅"/> </bean> ============================================================================== //bean生命周期 @Test public void test19() { //创建ioc容器 ApplicationContext ioc = new ClassPathXmlApplicationContext("beans3.xml"); House house = ioc.getBean("house", House.class); System.out.println("使用:House" + house); /* * 1.ioc的编译类型是ApplicationContext运行类型是ClassPathXmlApplicationContext,因为 * ApplicationContext接口没有close方法 * 2.因为ConfigurableApplicationContext有close方法,而且ClassPathXmlApplicationContext实现了 * ConfigurableApplicationContext,所以转成ConfigurableApplicationContext来调用close方法 * */ // 关闭容器 ((ConfigurableApplicationContext)ioc).close(); }
使用细节
- 初始化init方法和destory方法,是需要程序员来指定
- 销毁方法就是当关闭容器时,才会被调用
2.17 配置后置处理器
说明
- 在Spring的ioc容器,可以配置bean的后置处理器
- 该处理器/对象会在bean初始化方法调用前和初始化方法调用后被调用
- 程序员可以在后置处理器中编写自己的代码
package com.leon.spring.bean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; /** * ClassName:MyBeanPostProcessor * Package:com.leon.spring.bean * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/25 15:34 * @Version: 1.0 */ public class MyBeanPostProcessor implements BeanPostProcessor { /** * 什么时候被调用:在Bean的init方法前被调用 * @param bean 传入的是在ioc容器中创建/配置的Bean * @param beanName 传入的是在ioc容器中创建/配置的Bean的id * @return Object 程序员对传入的Bean 进行修改/处理(如果有需要的话) 然后返回 * @throws BeansException */ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessBeforeInitialization()方法~~~~~~~~"); if(bean instanceof House){ ((House) bean).setName("上海豪宅"); } return bean ; } /** * 什么时候被调用:在Bean的init方法后被调用 * @param bean 传入的是在ioc容器中创建/配置的Bean * @param beanName 传入的是在ioc容器中创建/配置的Bean的id * @return Object 程序员对传入的Bean 进行修改/处理(如果有需要的话) 然后返回 * @throws BeansException */ @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessBeforeInitialization()方法~~~~~~~~"); return bean ; } } ============================================================================== <!-- 1.生命周演示 2.init-method属性使用来指定bean的初始化方法,在setter方法后执行, init方法执行的时机,由Spring来控制的 3.destroy-method="destroy" 指定bean的销毁方法,在容器关闭的时候执行, destroy方法执行的时机,由Spring容器来控制 --> <bean class="com.leon.spring.bean.House" init-method="init" destroy-method="destroy" id="house"> <property name="name" value="北京大豪宅"/> </bean> <!-- 1.配置后置处理器对象 2.如果配置了后置处理器对象,将会作用于整个容器的Bean --> <bean class="com.leon.spring.bean.MyBeanPostProcessor" id="myBeanPostProcessor"/> <bean class="com.leon.spring.bean.Monster" id="monster"/> <bean class="com.leon.spring.bean.Monster" id="monster2"/> <bean class="com.leon.spring.bean.Monster" id="monster3"/> <bean class="com.leon.spring.bean.Monster" id="monster4"/> <bean class="com.leon.spring.bean.Monster" id="monster5"/> ============================================================================== //bean生命周期 @Test public void test20() { //创建ioc容器 ApplicationContext ioc = new ClassPathXmlApplicationContext("beans3.xml"); House house = ioc.getBean("house", House.class); System.out.println("使用:House" + house); /* * 1.ioc的编译类型是ApplicationContext运行类型是ClassPathXmlApplicationContext,因为 * ApplicationContext接口没有close方法 * 2.因为ConfigurableApplicationContext有close方法,而且ClassPathXmlApplicationContext实现了 * ConfigurableApplicationContext,所以转成ConfigurableApplicationContext来调用close方法 * */ // 关闭容器 ((ConfigurableApplicationContext)ioc).close(); }
其他说明
- 怎么执行到这个方法
- 使用AOP(反射+动态代理+IO+容器+注解)
- 有什么用?
- 可以对ioc容器中的所有的对象进行统一处理,比如日志处理、权限校验、安全验证、事务管理
- 针对容器的所有对象吗?
- 是的--->切面编程特点
2.18 通过属性文件配置bean
name="黄毛怪"
skill="吹牛皮"
age=10000<context:property-placeholder location="classpath:my.properties"/>
<bean class="com.leon.spring.bean.Monster" id="monster"> <property name="name" value="${name}"/> <property name="age" value="${age}"/> <property name="skill" value="${skill}"/> </bean>
==============================================================================
//通过属性文件配置bean
@Test
public void test21() {
//创建ioc容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans4.xml");Monster monster = ioc.getBean("monster", Monster.class); System.out.println(monster); }
2.19 自动装配bean
package com.leon.spring.dao;
/**
ClassName:OrderDao
Package:com.leon.spring.dao
Description:
@Author: leon-->ZGJ
@Create: 2024/3/25 16:16
@Version: 1.0
*/
public class OrderDao {public void saveOrder(){
System.out.println("保存了一个订单");
}}
==============================================================================
package com.leon.spring.service;
import com.leon.spring.dao.OrderDao;
import org.springframework.core.annotation.Order;/**
ClassName:OrderService
- Package:com.leon.spring.service
Description:
@Author: leon-->ZGJ
@Create: 2024/3/25 16:17
@Version: 1.0
*/
public class OrderService {private OrderDao orderDao;
public OrderDao getOrderDao() {
return orderDao;
}public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
}
==============================================================================package com.leon.spring.controller;
import com.leon.spring.service.OrderService;
/**
ClassName:OrderController
Package:com.leon.spring.controller
Description:
@Author: leon-->ZGJ
@Create: 2024/3/25 16:18
@Version: 1.0
*/
public class OrderController {
private OrderService orderService ;public OrderService getOrderService() {
return orderService;
}public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
}
==============================================================================<bean class="com.leon.spring.dao.OrderDao" id="orderDao"/>
<bean autowire="byName" class="com.leon.spring.service.OrderService" id="orderService" />
<bean autowire="byName" class="com.leon.spring.controller.OrderController" id="orderController"/>
==============================================================================
//自动装配
@Test
public void test22() {
//创建ioc容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans4.xml");
OrderController orderController = ioc.getBean("orderController", OrderController.class);System.out.println(orderController.getOrderService()); System.out.println(orderController.getOrderService().getOrderDao()); }
2.20 Spring EL表达式配置
说明
- Spring Expression Language,Spring表达语言,简称SpEL。支持运行时查询并可以操作对象
- 和EL表达式一样,SpEL根据JavaBean风格的getXxx()、setXxx()方法定义的属性访问对象
- SpEL使用#{}作为定界符,所有在大框好中的字符都将被认为是SpEL表达式
package com.leon.spring.bean; /** * ClassName:SpELBean * Package:com.leon.spring.bean * Description: * 这是一个用来测试SpEL的类 * @Author: leon-->ZGJ * @Create: 2024/3/25 20:03 * @Version: 1.0 */ public class SpELBean { private String name ; private Monster monster; private String monsterName; private String crySound; private String bookName; private Double result; public SpELBean() { } public SpELBean(String name, Monster monster, String monsterName, String crySound, String bookName, Double result) { this.name = name; this.monster = monster; this.monsterName = monsterName; this.crySound = crySound; this.bookName = bookName; this.result = result; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Monster getMonster() { return monster; } public void setMonster(Monster monster) { this.monster = monster; } public String getMonsterName() { return monsterName; } public void setMonsterName(String monsterName) { this.monsterName = monsterName; } public String getCrySound() { return crySound; } public void setCrySound(String crySound) { this.crySound = crySound; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public Double getResult() { return result; } public void setResult(Double result) { this.result = result; } public static String bookName(String t1){ return t1 ; } public String cry(String str){ return str; } @Override public String toString() { return "SpELBean{" + "name='" + name + '\'' + ", monster=" + monster + ", monsterName='" + monsterName + '\'' + ", crySound='" + crySound + '\'' + ", bookName='" + bookName + '\'' + ", result=" + result + '}'; } } ============================================================================== <!-- 1.通过给spel给bean属性赋值 --> <bean class="com.leon.spring.bean.SpELBean" id="spELBean"> <!--spel 字面量赋值--> <property name="name" value="#{'韩顺平'}"/> <!--spel 引用其他bean--> <property name="monster" value="#{monster2}"/> <!--spel 引用其他bean的属性--> <property name="monsterName" value="#{monster2.name}"/> <!--spel 调用普通方法--> <property name="crySound" value="#{spELBean.cry('喵喵····')}"/> <!--spel 调用静态方法--> <property name="bookName" value="#{T(com.leon.spring.bean.SpELBean).bookName('围城')}"/> <!--spel 通过运算--> <property name="result" value="#{6.3*3.2}"/> </bean>
3.基于注解配置bean
3.1 基本介绍
- 基于注解 的方式配置bean,主要是项目开发中的组件,比如:Controller、Service和Dao
- 组件注解的形式有
- @Component 表示当前注解标识的是一个组件
- @Controller表示当前注解标识的是一个控制器,通常用于Servlet类
- @Service表示当前注解标识的是一个处理业务逻辑的类,通常用于Service类
- @Repository表示当前注解标识的是一个持久化层的类,通常用于Dao类
3.2快速入门
代码
package com.leon.spring.component; import org.springframework.stereotype.Component; /** * ClassName:UserComponent * Package:com.leon.spring.component * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/25 20:44 * @Version: 1.0 */ @Component public class UserComponent { } ============================================================================== package com.leon.spring.component; import org.springframework.stereotype.Controller; /** * ClassName:UserController * Package:com.leon.spring.controller * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/25 20:43 * @Version: 1.0 */ @Controller public class UserController { } ============================================================================== package com.leon.spring.component; import org.springframework.stereotype.Repository; /** * ClassName:UserDao * Package:com.leon.spring.dao * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/25 20:42 * @Version: 1.0 */ @Repository public class UserDao { } ============================================================================== package com.leon.spring.component; import org.springframework.stereotype.Service; /** * ClassName:UserService * Package:com.leon.spring.service * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/25 20:43 * @Version: 1.0 */ @Service public class UserService { } ============================================================================== <!-- 1.配置容器要扫描的包 2.component-scan 要对指定包下的类进行扫描,包括子包 3.base-package 指定要扫描的包 4.含义是当Spring容器创建/初始化时,就会扫描com.leon.spring.component包 下的所有的有注解 @Controller @Service @Respository @Componentl类 将其实例化 --> <context:component-scan base-package="com.leon.spring.component"/> ============================================================================== @Test public void test01(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("beans5.xml"); UserDao dao = ioc.getBean(UserDao.class); UserComponent component = ioc.getBean(UserComponent.class); UserService service = ioc.getBean(UserService.class); UserController controller = ioc.getBean(UserController.class); System.out.println(dao); System.out.println(component); System.out.println(service); System.out.println(controller); System.out.println("ok"); }
注意事项和使用细节
必须在Spring配置文件中指定“自动扫描的包”,IOC容器才能够检测到当前项目中哪些被标注了注解,注意必须导入context名称细节
<!--配置自动扫描的包--> <context:component-scan base-package="com.leon.spring.component"/>
可以使用通配符*来指定,比如:com.leon.spring. *
Spring的IOC容器不能检测一个使用了@Controller注解的类到底是不是一个真正的控制器,注解的名称是用于程序员自己识别当前标识的是什么组件。其他的@Service@Repository也是一样的道理【也就是说Spring的IOC容器只要检查到注解就会生成对象,但是这个注解的含义Spring不会识别,注解是给程序员编程方便看的】
<context:component-scan base-package="com.leon.spring.component" resource-pattern="user * .class"/> resource-pattern="user*.class":表示只扫描满足要求的类【使用的少,不想扫描,就不写注解就可以】
<context:component-scan base-package="com.leon.spring.component"/>
<!-- 1.配置容器要扫描的包 2.component-scan 要对指定包下的类进行扫描,包括子包 3.base-package 指定要扫描的包 4.含义是当Spring容器创建/初始化时,就会扫描com.leon.spring.component包 下的所有的有注解 @Controller @Service @Respository @Componentl类 将其实例化 --> <context:component-scan base-package="com.leon.spring.component"> <!-- 1. context:exclude-filter标签表示过滤那些类不扫描 2. type 属性表示按照什么方式过滤 ,annotation表示注解方式 3. expression 属性表示是哪个注解,要求是全类名,这里就是@Service注解,比如 @Controller @Repository 以此类推 4. 上面表示过滤掉com.leon.spring.component包下,加入了@Service 注解的类 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/> </context:component-scan>
指定扫描
<!-- 1.配置容器要扫描的包 2.component-scan 要对指定包下的类进行扫描,包括子包 3.base-package 指定要扫描的包 4.含义是当Spring容器创建/初始化时,就会扫描com.leon.spring.component包 下的所有的有注解 @Controller @Service @Respository @Componentl类 将其实例化 --> <context:component-scan base-package="com.leon.spring.component" use-default-filters="false"> <!-- 1.use-default-filters="false"表示是否使用默认的过滤机制,true表示还使用,false则相反 2.context:include-filter: 表示只是扫描指定的注解的类 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> </context:component-scan>
默认情况:标记注解后,类名首字母小写作为id,也可以使用注解的value属性指定id值,并且value可以省略
package com.leon.spring.component; import org.springframework.stereotype.Repository; /** * ClassName:UserDao * Package:com.leon.spring.dao * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/25 20:42 * @Version: 1.0 */ @Repository("dao") public class UserDao { } ================================================================================ @Test public void test01(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("beans5.xml"); UserDao dao = ioc.getBean("dao",UserDao.class); UserComponent component = ioc.getBean("userComponent",UserComponent.class); UserService service = ioc.getBean(UserService.class); UserController controller = ioc.getBean(UserController.class); System.out.println(dao); System.out.println(component); System.out.println(service); System.out.println(controller); System.out.println("ok");
3.3 自己手写一个Spring注解机制
代码
package com.leon.myannotation.v1; import java.lang.annotation.*; /** * ClassName:ComponentScan * Package:com.leon.myannotation.v1 * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/28 19:10 * @Version: 1.0 */ /** * @Target(ElementType.TYPE) 指定我们的ComponentScan注解可以修饰的Type程序元素 * @Retention(RetentionPolicy.RUNTIME) 表示我们这个注解的保留范围 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ComponentScan { String value() default ""; } ============================================================================== package com.leon.myannotation.v1; /** * ClassName:MySpringConfig * Package:com.leon.myannotation.v1 * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/28 19:24 * @Version: 1.0 */ /* * 1.这是一个配置类,用以配置扫描哪个包的类 * */ @ComponentScan(value = "com.leon.spring.component") public class MySpringConfig { } ============================================================================== package com.leon.myannotation.v1; import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.io.File; import java.lang.reflect.Constructor; import java.net.URL; import java.util.concurrent.ConcurrentHashMap; /** * ClassName:MySpringApplicationContext * Package:com.leon.myannotation.v1 * Description: * 1.这是一个容器,用来扫描指定包下的类,然后通过反射和io等的处理创建对象 * * @Author: leon-->ZGJ * @Create: 2024/3/28 19:26 * @Version: 1.0 */ public class MySpringApplicationContext { // 创建一个Class类来接收从外部传来的class private Class configClass; // 创建一个保存容器 private final ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>(); //初始化数据 public MySpringApplicationContext(Class configClass) { //初始化数据 this.configClass = configClass; //使用初始化方法 init(); } public void init() { //获取到要扫描的包 //通过MySpringConfig类的注解:@ComponentScan(value = "com.len.spring.component")信息 ComponentScan componentScan = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class); //创建一个存储路径的字符串 String path = componentScan.value(); //替换,将.替换成/ path = path.replace(".", "/").trim(); //得到要扫描的包下的所有资源(也就是.class文件) //1.得到类加载器 ClassLoader classLoader = MySpringApplicationContext.class.getClassLoader(); //2.通过类加载器获取到要扫描的包的资源的url URL resource = classLoader.getResource(path); //3.将要加载的文件进行遍历 File file = new File(resource.getFile()); //4.判断是否是文件夹 if (file.isDirectory()) { File[] files = file.listFiles(); for (File file1 : files) { //获取到绝对路径 String fileAbsolutePath = file1.getAbsolutePath(); //只对class文件进行处理 if (fileAbsolutePath.endsWith(".class")) { //获取到类名 //String name = file1.getName();这个方式也可以 //C:\software\javacodelibrary\main_stream_framework\out\production\spring5\com\leon\spring\component\UserComponent.class //substring(begin,end)截取范围是[begin,end) String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class")); //获取类的完整路径 String fullPath = path.replace("/", ".") + "." + className; //判断该类是否需要注入到容器中,看其是否有@Service、@Controller、@Component等 try { //使用类加载器获取类信息 /* * 1.通过类加载器:这个方式不能加载到静态信息 * 2.通过Class.from() :这个方式获取的信息比较全 * */ Class<?> aClass = classLoader.loadClass(fullPath); // 判断是否是符合要求的注解 if (aClass.isAnnotationPresent(Component.class) || aClass.isAnnotationPresent(Controller.class) || aClass.isAnnotationPresent(Service.class) || aClass.isAnnotationPresent(Repository.class)) { //判断是否Component if(aClass.isAnnotationPresent(Component.class)){ //获取注解对象 Component component = aClass.getDeclaredAnnotation(Component.class); //获取value值 String id = component.value(); if(!id.equals("")){ className = id; } } //判断是否Component if(aClass.isAnnotationPresent(Controller.class)){ //获取注解对象 Controller controller = aClass.getDeclaredAnnotation(Controller.class); //获取value值 String id = controller.value(); if(!id.equals("")){ className = id; } } //判断是否Component if(aClass.isAnnotationPresent(Service.class)){ //获取注解对象 Service service = aClass.getDeclaredAnnotation(Service.class); //获取value值 String id = service.value(); if(!id.equals("")){ className = id; } } //判断是否Component if(aClass.isAnnotationPresent(Repository.class)){ //获取注解对象 Repository repository = aClass.getDeclaredAnnotation(Repository.class); //获取value值 String id = repository.value(); if(!id.equals("")){ className = id; } } //创建对象,放入到容器中 Class<?> forName = Class.forName(fullPath); //获取构造方法 Constructor<?> declaredConstructor = forName.getDeclaredConstructor(); //创建对象 Object obj = declaredConstructor.newInstance(); //放入到容器,key是首字母小写 ioc.put(StringUtils.uncapitalize(className), obj); } } catch (Exception e) { throw new RuntimeException(e); } } } } } public Object getBean(String key){ return ioc.get(key); } public <T> T getBean(String key,Class<T> clazz){ return (T) ioc.get(key); } } ============================================================================== package com.leon.myannotation.v1; import com.leon.spring.component.UserDao; import com.leon.spring.component.UserService; import org.junit.jupiter.api.Test; import org.springframework.stereotype.Service; /** * ClassName:MySpringApplicationContextTest * Package:com.leon.myannotation.v1 * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/28 19:32 * @Version: 1.0 */ public class MySpringApplicationContextTest { @Test public void test01(){ MySpringApplicationContext ioc = new MySpringApplicationContext(MySpringConfig.class); UserService bean = ioc.getBean("service", UserService.class); System.out.println(bean); System.out.println("ok"); } }
3.4注解自动装配:【@Autowired】或【@Resource】
基本说明
基于注解配置bean,也可以实现自动装配,使用注解是:@Autowired或者@Resource
@Autowired的规则说明
- 在IOC容器中查找待装配的组件的类型,如果有唯一的bean匹配,则使用该bean装配
- 如待装配的类型对应的bean在IOC容器中有多个,则使用待装配的属性名作为id值再进行查找,找到就装配,找不到就抛异常。
@Resource的规则说明
- @Resource有两个属性是比较重要的,分别是name和type,Spring将@Resource注解的name属性解析为bean的id名,而type属性则解析为bean的类型,所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略(name和type可以同时指定)
- 如果@Resource没有指定name和type,则先使用byName注入策略(这里是跟据属性名来匹配bean的id),如果匹配不上,再使用byType策略,如果都不成功,就会报错(一般都不指定name和type)
注意:不管是@Autowired还是@Resource都要保证属性名是规范的写法
代码
package com.leon.spring.component; import org.springframework.stereotype.Service; /** * ClassName:UserService * Package:com.leon.spring.service * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/25 20:43 * @Version: 1.0 */ @Service public class UserService { public void run(){ System.out.println("UserService run()~~~~~~~"); } } =========================================================================== package com.leon.spring.component; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Controller; import javax.annotation.Resource; /** * ClassName:UserController * Package:com.leon.spring.controller * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/25 20:43 * @Version: 1.0 */ @Controller public class UserController { //@Autowired //private UserService userService; //@Resource //private UserService userService200; /* * 1.@Autowired和@Qualifier(value = "userService200") * 组合也可以完成指定name/id 来进行自动装配 * 2.两个注解需要都写上才能指定name/id * */ @Autowired @Qualifier(value = "userService200") private UserService userService; public void run(){ System.out.println("UserController run()====="); userService.run(); } } =========================================================================== @Test public void test02(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("beans6.xml"); UserController userController = ioc.getBean("userController", UserController.class); userController.run(); } =========================================================================== <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.leon.spring.component"/> <bean class="com.leon.spring.component.UserService" id="userService200"/> <bean class="com.leon.spring.component.UserService" id="userService300"/> <bean class="com.leon.spring.component.UserService" id="userService400"/> </beans>
3.5泛型依赖注入
基本说明
- 为了更好的管理有继承和相互依赖的bean的自动装配,Spring还提供基于泛型依赖注入机制
- 在继承关系复杂情况下,泛型依赖注入就会有很大的优越性
代码实现
package com.leon.spring.genericity; /** * ClassName:Phone * Package:com.leon.spring.genericity * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/29 19:44 * @Version: 1.0 */ public class Phone { } ============================================================================== package com.leon.spring.genericity; /** * ClassName:Book * Package:com.leon.spring.genericity * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/29 19:45 * @Version: 1.0 */ public class Book { } ============================================================================== package com.leon.spring.genericity; /** * ClassName:BaseDao * Package:com.leon.spring.genericity * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/29 19:43 * @Version: 1.0 */ public abstract class BaseDao<T> { public abstract void save(); } ============================================================================== package com.leon.spring.genericity; import org.springframework.beans.factory.annotation.Autowired; /** * ClassName:BaseService * Package:com.leon.spring.genericity * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/29 19:47 * @Version: 1.0 */ public class BaseService<T>{ @Autowired private BaseDao<T> baseDao; public void save(){ baseDao.save(); } } ============================================================================== package com.leon.spring.genericity; import org.springframework.stereotype.Repository; /** * ClassName:BookDao * Package:com.leon.spring.genericity * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/29 19:46 * @Version: 1.0 */ @Repository public class BookDao extends BaseDao<Book>{ @Override public void save() { System.out.println("BookDao save()~~~~~~~~"); } } ============================================================================== package com.leon.spring.genericity; import org.springframework.stereotype.Service; /** * ClassName:BookService * Package:com.leon.spring.genericity * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/29 19:50 * @Version: 1.0 */ @Service public class BookService extends BaseService<Book>{ } ============================================================================== package com.leon.spring.genericity; import org.springframework.stereotype.Repository; /** * ClassName:PhoneDao * Package:com.leon.spring.genericity * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/29 19:45 * @Version: 1.0 */ @Repository public class PhoneDao extends BaseDao<Phone>{ @Override public void save() { System.out.println("PhoneDao save()~~~~~~"); } } ============================================================================== package com.leon.spring.genericity; import org.springframework.stereotype.Service; /** * ClassName:PhoneSerice * Package:com.leon.spring.genericity * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/29 19:51 * @Version: 1.0 */ @Service public class PhoneService extends BaseService<Phone> { }
六、 动态代理
动态代理需求说明
有Vehicle(交通工具接口,有一个run方法),下面有两个实现类Car和Ship
当运行Car对象的run方法和Ship对象的run方法时,输入如下内容
交通工具开始运行了~~~~~ 大轮船在水上 running~~~~~ 交通工具停止运行了~~~~~ ================================================ 交通工具开始运行了~~~~~ 小汽车在路上 running~~~~~ 交通工具停止运行了~~~~~
动态代理传统方式
代码
package com.leon.dynamic_proxy; /** * ClassName:Vehicle * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/28 14:58 * @Version: 1.0 */ public interface Vehicle { public abstract void run(); } ============================================================================== package com.leon.dynamic_proxy; /** * ClassName:Car * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/28 15:01 * @Version: 1.0 */ public class Car implements Vehicle{ @Override public void run() { //System.out.println("交通工具开始运行了~~~~~"); System.out.println("小汽车在路上 running~~~~~"); //System.out.println("交通工具停止运行了~~~~~"); } } ============================================================================== package com.leon.dynamic_proxy; /** * ClassName:Ship * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/28 14:59 * @Version: 1.0 */ public class Ship implements Vehicle{ @Override public void run() { //System.out.println("交通工具开始运行了~~~~~"); System.out.println("大轮船在水上 running~~~~~"); //System.out.println("交通工具停止运行了~~~~~"); } }
动态代理方式
代码
package com.leon.dynamic_proxy; /** * ClassName:Vehicle * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/28 14:58 * @Version: 1.0 */ public interface Vehicle { public abstract void run(); } ============================================================================== package com.leon.dynamic_proxy; /** * ClassName:Car * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/28 15:01 * @Version: 1.0 */ public class Car implements Vehicle{ @Override public void run() { //System.out.println("交通工具开始运行了~~~~~"); System.out.println("小汽车在路上 running~~~~~"); //System.out.println("交通工具停止运行了~~~~~"); } } ============================================================================== package com.leon.dynamic_proxy; /** * ClassName:Ship * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/28 14:59 * @Version: 1.0 */ public class Ship implements Vehicle{ @Override public void run() { //System.out.println("交通工具开始运行了~~~~~"); System.out.println("大轮船在水上 running~~~~~"); //System.out.println("交通工具停止运行了~~~~~"); } } ============================================================================== package com.leon.dynamic_proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * ClassName:VehicleProxyProvider * Package:com.leon.dynamic_proxy * Description: * * 这个类是提供代理对象的一个供应类 * @Author: leon-->ZGJ * @Create: 2024/3/28 15:06 * @Version: 1.0 */ public class VehicleProxyProvider { //创建一个Vehicle接口属性,通过动态绑定机制来进行赋值 //也就是真正的运行类型 // 为什么定义一个属性呢?是为了方便跨方法调用。 private Vehicle target_vehicle; //通过构造器来进行初始化 public VehicleProxyProvider(Vehicle target_vehicle) { this.target_vehicle = target_vehicle; } //创建一个返回代理对象的方法 public Vehicle getProxy(){ /* * public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {....} 类加载器(ClassLoader): Java中的类加载器用于加载类文件到内存中,创建类的实例。在创建代理对象时, 需要指定一个类加载器,因为代理类在运行时会动态地生成并加载到内存中。 这个类加载器会加载生成的代理类,因此需要确保代理类能够被正确加载和使用。 如果不传入类加载器,则会使用默认的类加载器,通常是当前类的类加载器。 接口信息: 在创建代理对象时,需要指定代理对象要实现的接口。代理对象将会实现传入的接口, 并且在代理对象上调用接口方法时,InvocationHandler中的invoke()方法将会被调用。 因此,必须提供一个接口信息来确保代理对象实现了所需的接口, 从而能够处理接口方法的调用。 * */ //获取一个类加载器 ClassLoader classLoader = target_vehicle.getClass().getClassLoader(); //获取接口信息 Class<?>[] interfaces = target_vehicle.getClass().getInterfaces(); //创建一个InvocationHandler匿名对象 InvocationHandler invocationHandler = new InvocationHandler() { /** * * @param proxy 表示代理对象 * @param method 表示要执行的方法 * @param args 表示执行方法的实参 * @return 返回方法的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("交通工具开始运行了~~~~~"); /** * 这里就是使用被代理对象需要运行的方法 */ Object invoke = method.invoke(target_vehicle, args); System.out.println("交通工具停止运行了~~~~~"); return invoke; } }; /* * 1.Proxy.newProxyInstance()方法的作用是返回一个代理对象 * 2.需要传入的值为 * 类加载器:因为代理对象是动态的生成,需要正确的加载和使用,所以需要传入一个类加载器 * 接口信息:因为生成的代理对象需要指定代理对象要实现的接口,所以才要传入接口信息 * InvocationHandler: * */ Vehicle proxy = (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); return proxy; } } ============================================================================== package com.leon.dynamic_proxy; import org.junit.jupiter.api.Test; /** * ClassName:DynamicProxyStudy * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/28 15:02 * @Version: 1.0 */ public class DynamicProxyStudy { @Test public void run(){ Vehicle vehicle = new Ship(); VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(vehicle); Vehicle proxy = vehicleProxyProvider.getProxy(); proxy.run(); System.out.println("ok"); } }
七、 动态代理深入
需求说明
有一个SamartAnimal接口,可以完成简单的加减法,要求在执行getSum()和getSub()时,输出执行前,执行过程,执行后的输出日志
请使用传统方式完成
请使用动态代理方式来完成,并要求考虑代理对象调用方法(底层是反射调用)时,可能出现的异常
代码实现
package com.leon.dynamic_proxy.proxy02; /** * ClassName:Number * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:41 * @Version: 1.0 */ public interface Number { public abstract Integer getSum(Integer num1,Integer num2); public abstract Integer getSub(Integer num1,Integer num2); } ============================================================================== package com.leon.dynamic_proxy.proxy02; /** * ClassName:Sum * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:42 * @Version: 1.0 */ public class Calculate implements Number { @Override public Integer getSum(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSum-参数["+num1+num2+"]"); Integer sum = num1 + num2 ; //Integer num = 1/0; System.out.println("方法内部打印-result["+sum+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSum-结果["+sum+"]"); //System.out.println("方法最终结束-日志-方法名-getSum"); return sum ; } @Override public Integer getSub(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSub-参数["+num1+num2+"]"); Integer sub = num1 - num2 ; System.out.println("方法内部打印-result["+sub+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSub-结果["+sub+"]"); //System.out.println("方法最终结束-日志-方法名-getSub"); return sub ; } } ============================================================================== package com.leon.dynamic_proxy.proxy02; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; /** * ClassName:CalculateProxyProvider * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:52 * @Version: 1.0 */ public class CalculateProxyProvider { private Number target_number; public CalculateProxyProvider(Number target_number){ this.target_number = target_number; } public Number getProxy(){ //获取一个类加载器 ClassLoader classLoader = target_number.getClass().getClassLoader(); //获取接口信息,方便代理对象实现 Class<?>[] interfaces = target_number.getClass().getInterfaces(); //实现一下InvocationHandler InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object invoke = null; try { //从AOP角度来看,横切关注点,前置通知 System.out.println("方法开始执行-日志-方法名-"+method.getName()+"-参数"+ Arrays.toString(args)); //调用方法 invoke = method.invoke(target_number, args); //从AOP角度来看,横切关注点,返回通知 System.out.println("方法正常执行结束-日志-方法名-"+method.getName()+"-结果["+invoke+"]"); } catch (Exception e) { e.printStackTrace(); //从AOP角度来看,横切关注点,异常通知 System.out.println("方法执行异常-日志-方法名-"+method.getName()+"-异常类型"+e.getClass().getName()); } finally { //从AOP角度来看,横切关注点,结束通知 System.out.println("方法最终结束-日志-方法名-"+method.getName()); } //返回方法的返回值 return invoke; } }; //获取代理对象 Number number = (Number) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); //返回代理对象 return number ; } } ============================================================================== package com.leon.dynamic_proxy.proxy02; import org.junit.jupiter.api.Test; /** * ClassName:Test * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:50 * @Version: 1.0 */ public class ProxyTest { @Test public void test01(){ Number number = new Calculate(); //number.getSum(10,2); //System.out.println("============================"); //number.getSub(10,2); CalculateProxyProvider calculateProxyProvider = new CalculateProxyProvider(number); Number proxy = calculateProxyProvider.getProxy(); proxy.getSub(10,2); System.out.println("===================================="); proxy.getSum(10,2); } }
提出问题,希望将输出语句改成方法
代码修改
package com.leon.dynamic_proxy.proxy03; /** * ClassName:Number * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:41 * @Version: 1.0 */ public interface Number { public abstract Integer getSum(Integer num1,Integer num2); public abstract Integer getSub(Integer num1,Integer num2); } ============================================================================== package com.leon.dynamic_proxy.proxy03; /** * ClassName:Sum * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:42 * @Version: 1.0 */ public class Calculate implements Number { @Override public Integer getSum(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSum-参数["+num1+num2+"]"); Integer sum = num1 + num2 ; //Integer num = 1/0; System.out.println("方法内部打印-result["+sum+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSum-结果["+sum+"]"); //System.out.println("方法最终结束-日志-方法名-getSum"); return sum ; } @Override public Integer getSub(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSub-参数["+num1+num2+"]"); Integer sub = num1 - num2 ; System.out.println("方法内部打印-result["+sub+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSub-结果["+sub+"]"); //System.out.println("方法最终结束-日志-方法名-getSub"); return sub ; } } ============================================================================== package com.leon.dynamic_proxy.proxy03; import java.lang.reflect.Method; import java.util.Arrays; /** * ClassName:Section * Package:com.leon.dynamic_proxy.proxy03 * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/1 14:29 * @Version: 1.0 */ public class Section { public static void before(Object proxy, Method method,Object ... args){ //从AOP角度来看,横切关注点,前置通知 System.out.println("before()--方法开始执行-日志-方法名-"+method.getName()+"-参数"+ Arrays.toString(args)); } public static void successReturn(Method method,Object invoke){ System.out.println("successReturn()--方法正常执行结束-日志-方法名-"+method.getName()+"-结果["+invoke+"]"); } public static void exception(Method method,Throwable throwable){ System.out.println("exception()--方法执行异常-日志-方法名-"+method.getName()+"-异常类型"+throwable.getClass().getName()); } public static void after(Method method){ System.out.println("after()--方法最终结束-日志-方法名-"+method.getName()); } } ============================================================================== package com.leon.dynamic_proxy.proxy03; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; /** * ClassName:CalculateProxyProvider * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:52 * @Version: 1.0 */ public class CalculateProxyProvider { private Number target_number; public CalculateProxyProvider(Number target_number){ this.target_number = target_number; } public Number getProxy(){ //获取一个类加载器 ClassLoader classLoader = target_number.getClass().getClassLoader(); //获取接口信息,方便代理对象实现 Class<?>[] interfaces = target_number.getClass().getInterfaces(); //实现一下InvocationHandler InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object invoke = null; try { ////从AOP角度来看,横切关注点,前置通知 //System.out.println("方法开始执行-日志-方法名-"+method.getName()+"-参数"+ Arrays.toString(args)); Section.before(proxy,method,args); //调用方法 invoke = method.invoke(target_number, args); //从AOP角度来看,横切关注点,返回通知 //System.out.println("方法正常执行结束-日志-方法名-"+method.getName()+"-结果["+invoke+"]"); Section.successReturn(method,invoke); } catch (Exception e) { e.printStackTrace(); //从AOP角度来看,横切关注点,异常通知 //System.out.println("方法执行异常-日志-方法名-"+method.getName()+"-异常类型"+e.getClass().getName()); Section.exception(method,e); } finally { //从AOP角度来看,横切关注点,结束通知 //System.out.println("方法最终结束-日志-方法名-"+method.getName()); Section.after(method); } //返回方法的返回值 return invoke; } }; //获取代理对象 Number number = (Number) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); //返回代理对象 return number ; } } ============================================================================== package com.leon.dynamic_proxy.proxy03; import org.junit.jupiter.api.Test; /** * ClassName:Test * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:50 * @Version: 1.0 */ public class ProxyTest { @Test public void test01(){ Number number = new Calculate(); //number.getSum(10,2); //System.out.println("============================"); //number.getSub(10,2); CalculateProxyProvider calculateProxyProvider = new CalculateProxyProvider(number); Number proxy = calculateProxyProvider.getProxy(); proxy.getSub(10,2); System.out.println("===================================="); proxy.getSum(10,2); } }
分析
- 土方法不够灵活
- 土方法复用性差
- 土方法还是一种硬编码,缺少灵活性,(没有注解和反射支撑)
- Spring AOP解决了这些问题,底层是ASPECTJ
- 通过前面的技术铺垫,理解SpringAOP就并不会那么难
八、Spring AOP
1.AOP基本介绍
什么是AOP
- AOP全称(aspect oriented programming),面向切面编程
示意图一

示意图二
2.AOP快速入门
基本介绍
- 需要引入核心的aspect包
- 切面类中声明通知方法
- 前置通知:@Before
- 返回通知:@AfterReturning
- 异常通知:@AfterThrowing
- 后置通知:@After
- 环绕通知:@Around
代码
package com.leon.aop.aspect; /** * ClassName:Number * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:41 * @Version: 1.0 */ public interface Number { public abstract Integer getSum(Integer num1,Integer num2); public abstract Integer getSub(Integer num1,Integer num2); } ================================================================================ package com.leon.aop.aspect; import org.springframework.stereotype.Component; /** * ClassName:Sum * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:42 * @Version: 1.0 */ @Component //使用这个注解是为了,Spring启动时,将我们的Calculate注入到容器中 public class Calculate implements Number { @Override public Integer getSum(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSum-参数["+num1+num2+"]"); Integer sum = num1 + num2 ; //Integer num = 1/0; System.out.println("方法内部打印-result["+sum+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSum-结果["+sum+"]"); //System.out.println("方法最终结束-日志-方法名-getSum"); return sum ; } @Override public Integer getSub(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSub-参数["+num1+num2+"]"); Integer sub = num1 - num2 ; System.out.println("方法内部打印-result["+sub+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSub-结果["+sub+"]"); //System.out.println("方法最终结束-日志-方法名-getSub"); return sub ; } } ================================================================================ package com.leon.aop.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Arrays; /** * ClassName:CalculateAspect * Package:com.leon.aop.aspect * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/1 19:16 * @Version: 1.0 */ @Aspect//使用来了这个注解的类,将会成为一个切面类【底层切面编程支撑(动态代理+反射+动态绑定.....)】 @Component //使用注解是为了将CalculateAspect注入到容器中 public class CalculateAspect { //想要将这个方法切入到getSum执行前-前置通知 /** * 解读 * 1.@Before 表示前置通知:即在目标对象执行被切入的方法前执行 * 2.value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer)) * 指定切入到哪个类的哪个方法 形式是:访问修饰符 返回类型 全类名.方法名(形参列表) * 3.before() 方法可以理解成是一个切入方法,这个方法名是可以自由指定的, * 但是一般都是有一个统一的写法的 * @param joinPoint 在底层执行时,由AspectJ切面框架,会给该切入方法传入JointPoint对象 * 通过该方法,程序员可以获取到,动态代理的一些先关信息如(Method,proxy,args等)信息 */ @Before(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))") public static void before(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); //从AOP角度来看,横切关注点,前置通知 System.out.println("AOP切面--方法开始执行-日志-方法名-"+signature.getName()+"-参数"+ Arrays.toString(joinPoint.getArgs())); } //这个就是方法正常执行结束后切入的通知-返回通知 @AfterReturning(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))") public static void successReturn(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("AOP切面--方法正常执行结束-日志-方法名-"+signature.getName()); } //这个就是方法执行发生异常后切入的通知-异常通知 @AfterThrowing(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))") public static void exception(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("AOP切面--方法执行异常-日志-方法名-"+signature.getName()); } //这个就是方法执行不管怎么样都会执行的通知-后置通知 @After("execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))") public static void after(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("AOP切面--方法最终结束-日志-方法名-"+signature.getName()); } } ================================================================================ <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--配置扫描包--> <context:component-scan base-package="com.leon.aop.aspect"/> <!--开启AOP注解功能--> <aop:aspectj-autoproxy/> </beans> ================================================================================ package com.leon.aop.aspect; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * ClassName:CalculateAspectTest * Package:com.leon.aop.aspect * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/1 19:38 * @Version: 1.0 */ public class CalculateAspectTest { @Test public void test01(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("beans7.xml"); //获取对象,我们需要通过接口类型来获取到注入的Calculate对象-就是代理对象 Number number = ioc.getBean(Number.class); //这个会报错,错误显示为不可获取 //Calculate bean = ioc.getBean(Calculate.class); number.getSum(10,2); System.out.println("Calculate 类型=" +number.getClass()); } }
细节说明
关于切面类方法命名可以自己规范一下:showBeginLog(),showSuccessEndLog(),showExceptionLog(),showFinallyEndLog()
切入表达式的更多配置,比如使用模糊配置
@Before(value="execution(* com.leon.aop.aspect.Calculate.*(..))")
表示 Calculate类下的所有方法,都会执行该前置方法
表示所有访问权限,所有包下所有类的所有方法,都会被执行该前置方法
@Before(value="execution(* *.*(..))")
当Spring容器开启了< aop:aspectj-autoproxy/>,我们获取注入的对象,需要以接口的类型来获取,因为注入的对象的类型已经是代理类型了
当Spring容器开启了< aop:aspectj-autoproxy/>,我们获取注入的对象,也可以通过id来获取,但是也要转成接口类型
3. AOP切入表达式
3.1 具体使用
作用
- 通过表达式的方式定位一个或多个具体的连接点
语法细节
- 切入点表达式的语法格式
execution([权限修饰符][返回值类型][简单类名(同包下)/全类名(不同包)].[方法名]([参数类型]))
- 举例说明
表达式 exection(* com.leon.aop.aspect.Number.*(..)) 含义 1.Number接口中声明的所有方法
2.第一个“*”代表任意修饰符及任意返回值
3.第二个"*"代表任意方法
4.“..”匹配任意数量、任意类型的参数
5.若目标类、目标接口与该切面类在同一个包中可以省略包名
表达式 exection(public * com.leon.aop.aspect.Number.*(..)) 含义 表示Number接口的所有公有方法
表达式 exection(public * com.leon.aop.aspect.Number.*(..)) 含义 表示Number接口的所有公有方法,exection(* integer com.leon.aop.aspect.Number.*(..)) 是不可以的,会报错
表达式 exection(public double com.leon.aop.aspect.Number.*(..)) 含义 表示Number接口中返回double类型数值的方法
表达式 exection(public double com.leon.aop.aspect.Number.*(double,..)) 含义 1.表示Number接口中公有的返回类型是double类型,且第一个形参是double类型的方法
2.“..”表示匹配任意数量、任意类型的参数
表达式 exection(public double com.leon.aop.aspect.Number.*(double,double)) 含义 表示Number接口的公有的返回值为double类型,形参为两个double类型的方法
- 在AspecJ中,切入点表达式可以通过“&&”、“||”、“!”等操作符结合起来
表达式 exection(* *.add(int,..) ) || execution(* *.sub(int,..)) 含义 表示所有类中的方法名为add且第一个形参为int类型的方法或所有类中方法名为sub且第一个形参为int类型的方法
3.2 注意事项和细节
- 切入表达式也可以指向类的方法,这时切入表达式会对该类/对象生效
- 切入表达式也可以指向接口的方法,这时切入表达式会对实现了接口方法的类/对象生效
- 切入点表达式也可以对没有实现接口的类,进行切入
- 动态代理jdk的Proxy(面向接口)与Spring的CGlib(面向对象)
- 两个动态代理的区别
- JDK动态代理是面向接口的,只能增强实现类中接口中存在的方法。CGlib是面向父类的,可以增强父类的所有方法
- JDK得到的对象是JDK代理对象实例,而CGlib得到的对象是被代理对象的子类
- 拓展网址:https://www.cnblogs.com/threeAgePie/p/15832586.html
3.3 JointPoint
通过JointPoint可以获取到调用方法的签名
- 代码
** * 解读 * 1.@Before 表示前置通知:即在目标对象执行被切入的方法前执行 * 2.value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer)) * 指定切入到哪个类的哪个方法 形式是:访问修饰符 返回类型 全类名.方法名(形参列表) * 3.before() 方法可以理解成是一个切入方法,这个方法名是可以自由指定的, * 但是一般都是有一个统一的写法的 * @param joinPoint 在底层执行时,由AspectJ切面框架,会给该切入方法传入JointPoint对象 * 通过该方法,程序员可以获取到,动态代理的一些先关信息如(Method,proxy,args等)信息 */ @Before(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))") public static void showBeginLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); ////从AOP角度来看,横切关注点,前置通知 //System.out.println("showBeginLog()--方法开始执行-日志-方法名-"+signature.getName()+"-参数"+ Arrays.toString(joinPoint.getArgs())); //获取目标方法名 String name = signature.getName(); //获取目标方法所属类的简单类名 String simpleName = signature.getDeclaringType().getSimpleName(); //获取目标方法所属类的类名 String declaringTypeName = signature.getDeclaringTypeName(); //获取目标方法声明类型(public、private、protected),返回的是数值 int modifiers = signature.getModifiers(); //获取传入目标方法的参数,返回一个数组 Object[] args = joinPoint.getArgs(); //获取被代理的对象 Object target = joinPoint.getTarget(); //获取代理对象自己 Object aThis = joinPoint.getThis(); System.out.println("signature=====>"+signature); System.out.println("name=====>"+name); System.out.println("simpleName=====>"+simpleName); System.out.println("declaringTypeName=====>"+declaringTypeName); System.out.println("modifiers=====>"+modifiers); System.out.println("args=====>"+Arrays.toString(args)); System.out.println("target=====>"+target); System.out.println("aThis=====>"+aThis); }
3.4 返回通知获取结果
如果我们希望把方法执行的结果,返回给切入方法,则需要在@AfterReturning注解中加入(returning)
我们还要在方法中添加一个形参,这个形参类型是Object类型 ,并且这个形参名要和returning的属性值要一样,不然会报错
例如:returning="res" Object res 注意两个名称要一样
代码
//这个就是方法正常执行结束后切入的通知-返回通知 /* 1. 如果我们希望把方法执行的结果,返回给切入方法,则需要在@AfterReturning注解中加入(returning) 2. 我们还要在方法中添加一个形参,这个形参类型是Object类型 ,并且这个形参名要和returning的属性值要一样,不然会报错 3. 例如:returning="res" Object res 注意两个名称要一样 * */ @AfterReturning(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))",returning = "res") public static void showSuccessEndLog(JoinPoint joinPoint,Object res){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("showSuccessEndLog()--方法正常执行结束-日志-方法名-"+signature.getName()+"-返回值是"+res); }
3.5 异常通知获取异常信息
如果我们希望把方法执行异常的信息,返回给切入方法,则需要在@AfterThrowing注解中加入throwing
我们还需要在方法的形参中加入一个异常类,我加的是:Throwable类,并且这个形参的名字还要和throwing的属性名一样
例如:throwing = "throwing" Throwable throwing
代码
//这个就是方法执行发生异常后切入的通知-异常通知 /* 1.如果我们希望把方法执行异常的信息,返回给切入方法,则需要在@AfterThrowing注解中加入throwing 2.我们还需要在方法的形参中加入一个异常类,我加的是:Throwable类,并且这个形参的名字还要和throwing的属性名一样 3.例如:throwing = "throwing" Throwable throwing * */ @AfterThrowing(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))",throwing = "throwing") public static void showExceptionLog(JoinPoint joinPoint,Throwable throwing){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("showExceptionLog()--方法执行异常-日志-方法名-"+signature.getName()+"-异常信息" + throwing.getClass().getName()); }
3.6 环绕通知
代码
package com.leon.aop.aspect; /** * ClassName:Number * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:41 * @Version: 1.0 */ public interface Number { public abstract Integer getSum(Integer num1,Integer num2); public abstract Integer getSub(Integer num1,Integer num2); } ================================================================================ package com.leon.aop.aspect; import org.springframework.stereotype.Component; /** * ClassName:Sum * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:42 * @Version: 1.0 */ @Component //使用这个注解是为了,Spring启动时,将我们的Calculate注入到容器中 public class Calculate implements Number { @Override public Integer getSum(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSum-参数["+num1+num2+"]"); Integer sum = num1 + num2 ; //Integer num = 1/0; System.out.println("方法内部打印-result["+sum+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSum-结果["+sum+"]"); //System.out.println("方法最终结束-日志-方法名-getSum"); return sum ; } @Override public Integer getSub(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSub-参数["+num1+num2+"]"); Integer sub = num1 - num2 ; System.out.println("方法内部打印-result["+sub+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSub-结果["+sub+"]"); //System.out.println("方法最终结束-日志-方法名-getSub"); return sub ; } } ================================================================================ package com.leon.aop.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Arrays; /** * ClassName:CalculateAround * Package:com.leon.aop.aspect * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/2 15:56 * @Version: 1.0 */ @Aspect @Component public class CalculateAround { /* * 1.@Around 表示这个方法是一个环绕通知【完成其他四个通知的功能】 * 2.value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer)) * 表示要切入哪个方法 * 3.around 表示要切入的方法-调用结构 try-catch-finally * */ @Around(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))") public Object around(ProceedingJoinPoint joinPoint){ Object result = null; try { //前置通知 showBeginLog(joinPoint); //方法执行 result = joinPoint.proceed(); //返回通知 showSuccessEndLog(joinPoint,result); } catch (Throwable e) { e.printStackTrace(); //异常通知 showExceptionLog(joinPoint,e); } finally { //后置通知 showFinallyEndLog(joinPoint); } return result; } public static void showBeginLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("around==showBeginLog()--方法开始执行-日志-方法名-"+signature.getName()+"-参数"+ Arrays.toString(joinPoint.getArgs())); } public static void showSuccessEndLog(JoinPoint joinPoint,Object res){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("around==showSuccessEndLog()--方法正常执行结束-日志-方法名-"+signature.getName()+"-返回值是"+res); } public static void showExceptionLog(JoinPoint joinPoint,Throwable throwing){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("around==showExceptionLog()--方法执行异常-日志-方法名-"+signature.getName()+"-异常信息" + throwing.getClass().getName()); } public static void showFinallyEndLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("around==showFinallyEndLog()--方法最终结束-日志-方法名-"+signature.getName()); } } ================================================================================ package com.leon.aop.aspect; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * ClassName:CalculateAspectTest * Package:com.leon.aop.aspect * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/1 19:38 * @Version: 1.0 */ public class CalculateAspectTest { @Test public void test01(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("beans7.xml"); //获取对象,我们需要通过接口类型来获取到注入的Calculate对象-就是代理对象 //Number number = ioc.getBean(Number.class); //这个会报错,错误显示为不可获取 Number number = (Number) ioc.getBean("calculate"); number.getSum(10,2); //System.out.println("Calculate 类型=" +number.getClass()); System.out.println("========================================="); number.getSub(10,2); } }
3.7 切入表达式的重用
为了统一管理切入表达式,可以使用切入点表达式重用技术
代码
package com.leon.aop.aspect; /** * ClassName:Number * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:41 * @Version: 1.0 */ public interface Number { public abstract Integer getSum(Integer num1,Integer num2); public abstract Integer getSub(Integer num1,Integer num2); } ================================================================================ package com.leon.aop.aspect; import org.springframework.stereotype.Component; /** * ClassName:Sum * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:42 * @Version: 1.0 */ @Component //使用这个注解是为了,Spring启动时,将我们的Calculate注入到容器中 public class Calculate implements Number { @Override public Integer getSum(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSum-参数["+num1+num2+"]"); Integer sum = num1 + num2 ; //Integer num = 1/0; System.out.println("方法内部打印-result["+sum+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSum-结果["+sum+"]"); //System.out.println("方法最终结束-日志-方法名-getSum"); return sum ; } @Override public Integer getSub(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSub-参数["+num1+num2+"]"); Integer sub = num1 - num2 ; System.out.println("方法内部打印-result["+sub+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSub-结果["+sub+"]"); //System.out.println("方法最终结束-日志-方法名-getSub"); return sub ; } } ================================================================================ package com.leon.aop.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Arrays; /** * ClassName:CalculateAspect * Package:com.leon.aop.aspect * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/1 19:16 * @Version: 1.0 */ @Aspect//使用来了这个注解的类,将会成为一个切面类【底层切面编程支撑(动态代理+反射+动态绑定.....)】 @Component //使用注解是为了将CalculateAspect注入到容器中 public class CalculateAspect { //定义一个切入点,在后面使用时可以直接引用,提高了复用性 @Pointcut(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer)))") public void pointCut(){ } //想要将这个方法切入到getSum执行前-前置通知 /** * 解读 * 1.@Before 表示前置通知:即在目标对象执行被切入的方法前执行 * 2.value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer)) * 指定切入到哪个类的哪个方法 形式是:访问修饰符 返回类型 全类名.方法名(形参列表) * 3.before() 方法可以理解成是一个切入方法,这个方法名是可以自由指定的, * 但是一般都是有一个统一的写法的 * @param joinPoint 在底层执行时,由AspectJ切面框架,会给该切入方法传入JointPoint对象 * 通过该方法,程序员可以获取到,动态代理的一些先关信息如(Method,proxy,args等)信息 */ //@Before(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))") @Before(value = "pointCut()") public static void showBeginLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); //从AOP角度来看,横切关注点,前置通知 System.out.println("pointCut--showBeginLog()--方法开始执行-日志-方法名-"+signature.getName()+"-参数"+ Arrays.toString(joinPoint.getArgs())); ////获取目标方法名 //String name = signature.getName(); ////获取目标方法所属类的简单类名 //String simpleName = signature.getDeclaringType().getSimpleName(); ////获取目标方法所属类的类名 //String declaringTypeName = signature.getDeclaringTypeName(); ////获取目标方法声明类型(public、private、protected),返回的是数值 //int modifiers = signature.getModifiers(); ////获取传入目标方法的参数,返回一个数组 //Object[] args = joinPoint.getArgs(); ////获取被代理的对象 //Object target = joinPoint.getTarget(); ////获取代理对象自己 //Object aThis = joinPoint.getThis(); // //System.out.println("signature=====>"+signature); //System.out.println("name=====>"+name); //System.out.println("simpleName=====>"+simpleName); //System.out.println("declaringTypeName=====>"+declaringTypeName); //System.out.println("modifiers=====>"+modifiers); //System.out.println("args=====>"+Arrays.toString(args)); //System.out.println("target=====>"+target); //System.out.println("aThis=====>"+aThis); } //这个就是方法正常执行结束后切入的通知-返回通知 /* 1. 如果我们希望把方法执行的结果,返回给切入方法,则需要在@AfterReturning注解中加入(returning) 2. 我们还要在方法中添加一个形参,这个形参类型是Object类型 ,并且这个形参名要和returning的属性值要一样,不然会报错 3. 例如:returning="res" Object res 注意两个名称要一样 * */ //@AfterReturning(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))",returning = "res") @AfterReturning(value = "pointCut()",returning = "res") public static void showSuccessEndLog(JoinPoint joinPoint,Object res){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("pointCut--showSuccessEndLog()--方法正常执行结束-日志-方法名-"+signature.getName()+"-返回值是"+res); } //这个就是方法执行发生异常后切入的通知-异常通知 /* 1.如果我们希望把方法执行异常的信息,返回给切入方法,则需要在@AfterThrowing注解中加入throwing 2.我们还需要在方法的形参中加入一个异常类,我加的是:Throwable类,并且这个形参的名字还要和throwing的属性名一样 3.例如:throwing = "throwing" Throwable throwing * */ //@AfterThrowing(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))",throwing = "throwing") @AfterThrowing(value = "pointCut()",throwing = "throwing") public static void showExceptionLog(JoinPoint joinPoint,Throwable throwing){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("pointCut--showExceptionLog()--方法执行异常-日志-方法名-"+signature.getName()+"-异常信息" + throwing.getClass().getName()); } //这个就是方法执行不管怎么样都会执行的通知-后置通知 //@After("execution(public * com.leon.aop.aspect.Calculate.*(..))") @After(value = "pointCut()") public static void showFinallyEndLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("pointCut--showFinallyEndLog()--方法最终结束-日志-方法名-"+signature.getName()); } } ================================================================================ package com.leon.aop.aspect; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * ClassName:CalculateAspectTest * Package:com.leon.aop.aspect * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/1 19:38 * @Version: 1.0 */ public class CalculateAspectTest { @Test public void test01(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("beans7.xml"); //获取对象,我们需要通过接口类型来获取到注入的Calculate对象-就是代理对象 //Number number = ioc.getBean(Number.class); //这个会报错,错误显示为不可获取 Number number = (Number) ioc.getBean("calculate"); number.getSum(10,2); //System.out.println("Calculate 类型=" +number.getClass()); System.out.println("========================================="); number.getSub(10,2); } }
3.8 切面类执行顺序
基本语法
- @order(value=n) 可以控制执行顺序,其中n越小,优先级越高
代码
package com.leon.aop.aspect; /** * ClassName:Number * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:41 * @Version: 1.0 */ public interface Number { public abstract Integer getSum(Integer num1,Integer num2); public abstract Integer getSub(Integer num1,Integer num2); } ================================================================================ package com.leon.aop.aspect; import org.springframework.stereotype.Component; /** * ClassName:Sum * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:42 * @Version: 1.0 */ @Component //使用这个注解是为了,Spring启动时,将我们的Calculate注入到容器中 public class Calculate implements Number { @Override public Integer getSum(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSum-参数["+num1+num2+"]"); Integer sum = num1 + num2 ; //Integer num = 1/0; System.out.println("方法内部打印-result["+sum+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSum-结果["+sum+"]"); //System.out.println("方法最终结束-日志-方法名-getSum"); return sum ; } @Override public Integer getSub(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSub-参数["+num1+num2+"]"); Integer sub = num1 - num2 ; System.out.println("方法内部打印-result["+sub+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSub-结果["+sub+"]"); //System.out.println("方法最终结束-日志-方法名-getSub"); return sub ; } } =============================================================================== package com.leon.aop.aspect; 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; /** * ClassName:CalculateAspect * Package:com.leon.aop.aspect * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/1 19:16 * @Version: 1.0 */ @Order(value = 2)//值越低优先级越高 @Aspect//使用来了这个注解的类,将会成为一个切面类【底层切面编程支撑(动态代理+反射+动态绑定.....)】 @Component //使用注解是为了将CalculateAspect注入到容器中 public class CalculateAspect2 { //定义一个切入点,在后面使用时可以直接引用,提高了复用性 @Pointcut(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer)))") public void pointCut(){ } //想要将这个方法切入到getSum执行前-前置通知 /** * 解读 * 1.@Before 表示前置通知:即在目标对象执行被切入的方法前执行 * 2.value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer)) * 指定切入到哪个类的哪个方法 形式是:访问修饰符 返回类型 全类名.方法名(形参列表) * 3.before() 方法可以理解成是一个切入方法,这个方法名是可以自由指定的, * 但是一般都是有一个统一的写法的 * @param joinPoint 在底层执行时,由AspectJ切面框架,会给该切入方法传入JointPoint对象 * 通过该方法,程序员可以获取到,动态代理的一些先关信息如(Method,proxy,args等)信息 */ //@Before(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))") @Before(value = "pointCut()") public static void showBeginLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); //从AOP角度来看,横切关注点,前置通知 System.out.println("CalculateAspect2--showBeginLog()--方法开始执行-日志-方法名-"+signature.getName()+"-参数"+ Arrays.toString(joinPoint.getArgs())); ////获取目标方法名 //String name = signature.getName(); ////获取目标方法所属类的简单类名 //String simpleName = signature.getDeclaringType().getSimpleName(); ////获取目标方法所属类的类名 //String declaringTypeName = signature.getDeclaringTypeName(); ////获取目标方法声明类型(public、private、protected),返回的是数值 //int modifiers = signature.getModifiers(); ////获取传入目标方法的参数,返回一个数组 //Object[] args = joinPoint.getArgs(); ////获取被代理的对象 //Object target = joinPoint.getTarget(); ////获取代理对象自己 //Object aThis = joinPoint.getThis(); // //System.out.println("signature=====>"+signature); //System.out.println("name=====>"+name); //System.out.println("simpleName=====>"+simpleName); //System.out.println("declaringTypeName=====>"+declaringTypeName); //System.out.println("modifiers=====>"+modifiers); //System.out.println("args=====>"+Arrays.toString(args)); //System.out.println("target=====>"+target); //System.out.println("aThis=====>"+aThis); } //这个就是方法正常执行结束后切入的通知-返回通知 /* 1. 如果我们希望把方法执行的结果,返回给切入方法,则需要在@AfterReturning注解中加入(returning) 2. 我们还要在方法中添加一个形参,这个形参类型是Object类型 ,并且这个形参名要和returning的属性值要一样,不然会报错 3. 例如:returning="res" Object res 注意两个名称要一样 * */ //@AfterReturning(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))",returning = "res") @AfterReturning(value = "pointCut()",returning = "res") public static void showSuccessEndLog(JoinPoint joinPoint,Object res){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("CalculateAspect2--showSuccessEndLog()--方法正常执行结束-日志-方法名-"+signature.getName()+"-返回值是"+res); } //这个就是方法执行发生异常后切入的通知-异常通知 /* 1.如果我们希望把方法执行异常的信息,返回给切入方法,则需要在@AfterThrowing注解中加入throwing 2.我们还需要在方法的形参中加入一个异常类,我加的是:Throwable类,并且这个形参的名字还要和throwing的属性名一样 3.例如:throwing = "throwing" Throwable throwing * */ //@AfterThrowing(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))",throwing = "throwing") @AfterThrowing(value = "pointCut()",throwing = "throwing") public static void showExceptionLog(JoinPoint joinPoint,Throwable throwing){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("CalculateAspect2--showExceptionLog()--方法执行异常-日志-方法名-"+signature.getName()+"-异常信息" + throwing.getClass().getName()); } //这个就是方法执行不管怎么样都会执行的通知-后置通知 //@After("execution(public * com.leon.aop.aspect.Calculate.*(..))") @After(value = "pointCut()") public static void showFinallyEndLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("CalculateAspect2--showFinallyEndLog()--方法最终结束-日志-方法名-"+signature.getName()); } } ================================================================================ package com.leon.aop.aspect; 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; /** * ClassName:CalculateAspect * Package:com.leon.aop.aspect * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/1 19:16 * @Version: 1.0 */ @Order(value = 1) //值越低,优先值越高 @Aspect//使用来了这个注解的类,将会成为一个切面类【底层切面编程支撑(动态代理+反射+动态绑定.....)】 @Component //使用注解是为了将CalculateAspect注入到容器中 public class CalculateAspect3 { //定义一个切入点,在后面使用时可以直接引用,提高了复用性 @Pointcut(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer)))") public void pointCut(){ } //想要将这个方法切入到getSum执行前-前置通知 /** * 解读 * 1.@Before 表示前置通知:即在目标对象执行被切入的方法前执行 * 2.value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer)) * 指定切入到哪个类的哪个方法 形式是:访问修饰符 返回类型 全类名.方法名(形参列表) * 3.before() 方法可以理解成是一个切入方法,这个方法名是可以自由指定的, * 但是一般都是有一个统一的写法的 * @param joinPoint 在底层执行时,由AspectJ切面框架,会给该切入方法传入JointPoint对象 * 通过该方法,程序员可以获取到,动态代理的一些先关信息如(Method,proxy,args等)信息 */ //@Before(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))") @Before(value = "pointCut()") public static void showBeginLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); //从AOP角度来看,横切关注点,前置通知 System.out.println("CalculateAspect3--showBeginLog()--方法开始执行-日志-方法名-"+signature.getName()+"-参数"+ Arrays.toString(joinPoint.getArgs())); ////获取目标方法名 //String name = signature.getName(); ////获取目标方法所属类的简单类名 //String simpleName = signature.getDeclaringType().getSimpleName(); ////获取目标方法所属类的类名 //String declaringTypeName = signature.getDeclaringTypeName(); ////获取目标方法声明类型(public、private、protected),返回的是数值 //int modifiers = signature.getModifiers(); ////获取传入目标方法的参数,返回一个数组 //Object[] args = joinPoint.getArgs(); ////获取被代理的对象 //Object target = joinPoint.getTarget(); ////获取代理对象自己 //Object aThis = joinPoint.getThis(); // //System.out.println("signature=====>"+signature); //System.out.println("name=====>"+name); //System.out.println("simpleName=====>"+simpleName); //System.out.println("declaringTypeName=====>"+declaringTypeName); //System.out.println("modifiers=====>"+modifiers); //System.out.println("args=====>"+Arrays.toString(args)); //System.out.println("target=====>"+target); //System.out.println("aThis=====>"+aThis); } //这个就是方法正常执行结束后切入的通知-返回通知 /* 1. 如果我们希望把方法执行的结果,返回给切入方法,则需要在@AfterReturning注解中加入(returning) 2. 我们还要在方法中添加一个形参,这个形参类型是Object类型 ,并且这个形参名要和returning的属性值要一样,不然会报错 3. 例如:returning="res" Object res 注意两个名称要一样 * */ //@AfterReturning(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))",returning = "res") @AfterReturning(value = "pointCut()",returning = "res") public static void showSuccessEndLog(JoinPoint joinPoint,Object res){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("CalculateAspect3--showSuccessEndLog()--方法正常执行结束-日志-方法名-"+signature.getName()+"-返回值是"+res); } //这个就是方法执行发生异常后切入的通知-异常通知 /* 1.如果我们希望把方法执行异常的信息,返回给切入方法,则需要在@AfterThrowing注解中加入throwing 2.我们还需要在方法的形参中加入一个异常类,我加的是:Throwable类,并且这个形参的名字还要和throwing的属性名一样 3.例如:throwing = "throwing" Throwable throwing * */ //@AfterThrowing(value = "execution(public Integer com.leon.aop.aspect.Calculate.getSum(Integer,Integer))",throwing = "throwing") @AfterThrowing(value = "pointCut()",throwing = "throwing") public static void showExceptionLog(JoinPoint joinPoint,Throwable throwing){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("CalculateAspect3--showExceptionLog()--方法执行异常-日志-方法名-"+signature.getName()+"-异常信息" + throwing.getClass().getName()); } //这个就是方法执行不管怎么样都会执行的通知-后置通知 //@After("execution(public * com.leon.aop.aspect.Calculate.*(..))") @After(value = "pointCut()") public static void showFinallyEndLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("CalculateAspect3--showFinallyEndLog()--方法最终结束-日志-方法名-"+signature.getName()); } } ================================================================================ package com.leon.aop.aspect; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * ClassName:CalculateAspectTest * Package:com.leon.aop.aspect * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/1 19:38 * @Version: 1.0 */ public class CalculateAspectTest { @Test public void test01(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("beans7.xml"); //获取对象,我们需要通过接口类型来获取到注入的Calculate对象-就是代理对象 //Number number = ioc.getBean(Number.class); //这个会报错,错误显示为不可获取 Number number = (Number) ioc.getBean("calculate"); number.getSum(10,2); //System.out.println("Calculate 类型=" +number.getClass()); System.out.println("========================================="); number.getSub(10,2); } }
注意事项和细节
优先级高每个消息通知都先执行,这个和方法调用机制(和Filter过滤链式调用类似)
示意图
3.9 基于XML配置AOP
代码
前置、返回、异常、后置通知
package com.leon.aop.xml; /** * ClassName:Number * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:41 * @Version: 1.0 */ public interface Number { public abstract Integer getSum(Integer num1,Integer num2); public abstract Integer getSub(Integer num1,Integer num2); } ================================================================================ package com.leon.aop.xml; /** * ClassName:Sum * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:42 * @Version: 1.0 */ public class Calculate implements Number { @Override public Integer getSum(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSum-参数["+num1+num2+"]"); Integer sum = num1 + num2 ; //Integer num = 1/0; System.out.println("方法内部打印-result["+sum+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSum-结果["+sum+"]"); //System.out.println("方法最终结束-日志-方法名-getSum"); return sum ; } @Override public Integer getSub(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSub-参数["+num1+num2+"]"); Integer sub = num1 - num2 ; System.out.println("方法内部打印-result["+sub+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSub-结果["+sub+"]"); //System.out.println("方法最终结束-日志-方法名-getSub"); return sub ; } } ================================================================================ package com.leon.aop.xml; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import java.util.Arrays; /** * ClassName:CalculateAspect * Package:com.leon.aop.aspect * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/1 19:16 * @Version: 1.0 */ public class CalculateAspect { public static void showBeginLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); //从AOP角度来看,横切关注点,前置通知 System.out.println("XML配置--showBeginLog()--方法开始执行-日志-方法名-"+signature.getName()+"-参数"+ Arrays.toString(joinPoint.getArgs())); } public static void showSuccessEndLog(JoinPoint joinPoint,Object res){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("XML配置--showSuccessEndLog()--方法正常执行结束-日志-方法名-"+signature.getName()+"-返回值是"+res); } public static void showExceptionLog(JoinPoint joinPoint,Throwable throwing){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("XML配置--showExceptionLog()--方法执行异常-日志-方法名-"+signature.getName()+"-异常信息" + throwing.getClass().getName()); } public static void showFinallyEndLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("XML配置--showFinallyEndLog()--方法最终结束-日志-方法名-"+signature.getName()); } } ================================================================================ <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--通过xml配置AOP--> <!--配置一个切面对象-bean--> <bean class="com.leon.aop.xml.CalculateAspect" id="aspect"/> <bean class="com.leon.aop.xml.CalculateAround" id="aspectAround"/> <!--配置一个Calculate对象-bean--> <bean class="com.leon.aop.xml.Calculate" id="calculate"/> <!--配置切面类--> <aop:config> <!--配置切入点,切入点要先配置--> <aop:pointcut id="pointCut" expression="execution(public Integer com.leon.aop.xml.Calculate.getSum(..))"/> <!--配置前置、返回、异常、后置通知--> <aop:aspect ref="aspect" order="1"> <!--前置通知--> <aop:before method="showBeginLog" pointcut-ref="pointCut"/> <!--返回通知 1.如果想获取到异常信息,要在aop:after-returning 标签中添加returning属性, 属性值要和方法中的Object类型形参的属性名一样(可以参考注解配置,和注解配值获取异常信息相类似) --> <aop:after-returning method="showSuccessEndLog" pointcut-ref="pointCut" returning="res"/> <!--异常通知 1.如果想获取到异常信息,要在aop:after-throwing 标签中添加throwing属性, 属性值要和方法中的Throwable类型形参的属性名一样(可以参考注解配置,和注解配值获取异常信息相类似) --> <aop:after-throwing method="showExceptionLog" pointcut-ref="pointCut" throwing="throwing"/> <!--后置通知--> <aop:after method="showFinallyEndLog" pointcut-ref="pointCut"/> </aop:aspect> <aop:aspect ref="aspectAround"> <!--环绕通知--> <aop:around method="around" pointcut-ref="pointCut"/> </aop:aspect> </aop:config> </beans> ================================================================================ package com.leon.aop.xml; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * ClassName:CalculateAspectTest * Package:com.leon.aop.xml * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/2 21:15 * @Version: 1.0 */ public class CalculateAspectTest { @Test public void test01(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("beans8.xml"); //获取对象,我们需要通过接口类型来获取到注入的Calculate对象-就是代理对象 Number number = ioc.getBean(Number.class); Integer sum = number.getSum(10, 2); System.out.println(sum); } }
环绕通知
package com.leon.aop.xml; /** * ClassName:Number * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:41 * @Version: 1.0 */ public interface Number { public abstract Integer getSum(Integer num1,Integer num2); public abstract Integer getSub(Integer num1,Integer num2); } ================================================================================ package com.leon.aop.xml; /** * ClassName:Sum * Package:com.leon.dynamic_proxy * Description: * * @Author: leon-->ZGJ * @Create: 2024/3/30 8:42 * @Version: 1.0 */ public class Calculate implements Number { @Override public Integer getSum(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSum-参数["+num1+num2+"]"); Integer sum = num1 + num2 ; //Integer num = 1/0; System.out.println("方法内部打印-result["+sum+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSum-结果["+sum+"]"); //System.out.println("方法最终结束-日志-方法名-getSum"); return sum ; } @Override public Integer getSub(Integer num1, Integer num2) { //System.out.println("方法开始执行-日志-方法名-getSub-参数["+num1+num2+"]"); Integer sub = num1 - num2 ; System.out.println("方法内部打印-result["+sub+"]"); //System.out.println("方法正常执行结束-日志-方法名-getSub-结果["+sub+"]"); //System.out.println("方法最终结束-日志-方法名-getSub"); return sub ; } } ================================================================================ package com.leon.aop.xml; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import java.util.Arrays; /** * ClassName:CalculateAround * Package:com.leon.aop.aspect * Description: * * @Author: leon-->ZGJ * @Create: 2024/4/2 15:56 * @Version: 1.0 */ //@Aspect //@Component public class CalculateAround { public Object around(ProceedingJoinPoint joinPoint){ Object result = null; try { //前置通知 showBeginLog(joinPoint); //方法执行 result = joinPoint.proceed(); //返回通知 showSuccessEndLog(joinPoint,result); } catch (Throwable e) { e.printStackTrace(); //异常通知 showExceptionLog(joinPoint,e); } finally { //后置通知 showFinallyEndLog(joinPoint); } return result; } public static void showBeginLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("around==showBeginLog()--方法开始执行-日志-方法名-"+signature.getName()+"-参数"+ Arrays.toString(joinPoint.getArgs())); } public static void showSuccessEndLog(JoinPoint joinPoint,Object res){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("around==showSuccessEndLog()--方法正常执行结束-日志-方法名-"+signature.getName()+"-返回值是"+res); } public static void showExceptionLog(JoinPoint joinPoint,Throwable throwing){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("around==showExceptionLog()--方法执行异常-日志-方法名-"+signature.getName()+"-异常信息" + throwing.getClass().getName()); } public static void showFinallyEndLog(JoinPoint joinPoint){ //通过连接点对象JoinPoint 获取方法签名 Signature signature = joinPoint.getSignature(); System.out.println("around==showFinallyEndLog()--方法最终结束-日志-方法名-"+signature.getName()); } } =================================================================================== <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--通过xml配置AOP--> <!--配置一个切面对象-bean--> <bean class="com.leon.aop.xml.CalculateAround" id="aspectAround"/> <!--配置一个Calculate对象-bean--> <bean class="com.leon.aop.xml.Calculate" id="calculate"/> <!--配置切面类--> <aop:config> <!--配置切入点,切入点要先配置--> <aop:pointcut id="pointCut" expression="execution(public Integer com.leon.aop.xml.Calculate.getSum(..))"/> <aop:aspect ref="aspectAround"> <!--环绕通知--> <aop:around method="around" pointcut-ref="pointCut"/> </aop:aspect> </aop:config> </beans>
九、手动实现Spring部分机制
1.代码实现
1.1 annotation包
package com.leon.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* ClassName:Autowired
* Package:com.leon.spring.annotation
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/6 14:23
* @Version: 1.0
*/
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
======================================================================================
package com.leon.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* ClassName:Component
* Package:com.leon.spring.annotation
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/6 9:55
* @Version: 1.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default "";
}
======================================================================================
package com.leon.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* ClassName:ComponentScan
* Package:com.leon.make_simple_spring.config
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/3 23:05
* @Version: 1.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
String value() default "";
}
======================================================================================
package com.leon.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* ClassName:Scope
* Package:com.leon.spring.annotation
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/6 10:10
* @Version: 1.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
String value() default "";
}
1.2 component 包
package com.leon.spring.component;
import com.leon.spring.annotation.Component;
/**
* ClassName:Sum
* Package:com.leon.dynamic_proxy
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/3/30 8:42
* @Version: 1.0
*/
@Component("calculate")
public class Calculate implements Number {
@Override
public Integer getSum(Integer num1, Integer num2) {
//System.out.println("方法开始执行-日志-方法名-getSum-参数["+num1+num2+"]");
Integer sum = num1 + num2 ;
//Integer num = 1/0;
System.out.println("方法内部打印-result["+sum+"]");
//System.out.println("方法正常执行结束-日志-方法名-getSum-结果["+sum+"]");
//System.out.println("方法最终结束-日志-方法名-getSum");
return sum ;
}
@Override
public Integer getSub(Integer num1, Integer num2) {
//System.out.println("方法开始执行-日志-方法名-getSub-参数["+num1+num2+"]");
Integer sub = num1 - num2 ;
System.out.println("方法内部打印-result["+sub+"]");
//System.out.println("方法正常执行结束-日志-方法名-getSub-结果["+sub+"]");
//System.out.println("方法最终结束-日志-方法名-getSub");
return sub ;
}
}
======================================================================================
package com.leon.spring.component;
/**
* ClassName:CalculateAspect
* Package:com.leon.aop.aspect
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/1 19:16
* @Version: 1.0
*/
//使用先死后活的方法
public class CalculateAspect {
public static void showBeginLog() {
System.out.println("pointCut--showBeginLog()--方法开始执行-日志-方法名");
}
public static void showSuccessEndLog() {
System.out.println("pointCut--showSuccessEndLog()--方法正常执行结束-日志-方法");
}
}
======================================================================================
package com.leon.spring.component;
import com.leon.spring.annotation.Component;
import com.leon.spring.process.BeanPostProcessor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* ClassName:MyBeanPostProcessor
* Package:com.leon.spring.process
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/3 20:50
* @Version: 1.0
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("postProcessBeforeInitialization 被调用--beanName= " + beanName + " bean= " + bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("postProcessAfterInitialization 被调用--beanName= " + beanName + " bean= " + bean.getClass());
return bean;
}
}
======================================================================================
package com.leon.spring.component;
/**
* ClassName:Number
* Package:com.leon.dynamic_proxy
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/3/30 8:41
* @Version: 1.0
*/
public interface Number {
public abstract Integer getSum(Integer num1,Integer num2);
public abstract Integer getSub(Integer num1,Integer num2);
}
======================================================================================
package com.leon.spring.component;
import com.leon.spring.annotation.Component;
import com.leon.spring.annotation.Scope;
/**
* ClassName:UserController
* Package:com.leon.component
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/3 20:36
* @Version: 1.0
*/
/*
* 1. 在默认情况下@Service @Component @Controller @Repository 是单例的
* 2. 如果想要每次获取的bean对象都是新的,可以在xml文件中配置,也可以使用注解
* 这里使用的就是Scope注解,作用范围[singleton(单例),prototype(多例)]
* */
@Component
@Scope("prototype")
public class UserController {
}
======================================================================================
package com.leon.spring.component;
import com.leon.spring.annotation.Component;
import com.leon.spring.process.InitializingBean;
/**
* ClassName:UserDao
* Package:com.leon.component
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/3 20:33
* @Version: 1.0
*/
@Component
public class UserDao implements InitializingBean {
public void hi(){
System.out.println("UserDao--hi()~~~~~");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("UserDao--afterPropertiesSet--初始化方法");
}
}
======================================================================================
package com.leon.spring.component;
import com.leon.spring.annotation.Autowired;
import com.leon.spring.annotation.Component;
import com.leon.spring.process.InitializingBean;
/**
* ClassName:UserService
* Package:com.leon.component
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/3 20:34
* @Version: 1.0
*/
@Component
public class UserService implements InitializingBean {
/*
* 1.自己实现的Autowired注解
* 2.通过名字来进行注入
* */
@Autowired
private UserDao userDao ;
public void ok(){
userDao.hi();
}
/*
* 1.afterPropertiesSet()方法在Bean的setter方法执行后,Spring容器调用
*
* */
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("UserService -- afterPropertiesSet() --这是一个初始化方法~~~~~~");
}
}
1.3 config包
package com.leon.spring.config;
import com.leon.spring.annotation.ComponentScan;
/**
* ClassName:ConfigClass
* Package:com.leon.make_simple_spring.config
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/3 23:07
* @Version: 1.0
*/
@ComponentScan(value = "com.leon.spring.component")
public class ConfigClass {
}
1.4 ioc 包
package com.leon.spring.ioc;
/**
* ClassName:BeanDefinition
* Package:com.leon.spring.ioc
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/6 10:04
* @Version: 1.0
*/
public class BeanDefinition {
//类的Class对象
private Class clazz;
//是多例还是单例
private String scope ="singleton";
//bean的名称,也就是id
private String beanName;
public BeanDefinition() {
}
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
}
======================================================================================
package com.leon.spring.ioc;
import com.leon.spring.annotation.Autowired;
import com.leon.spring.annotation.Component;
import com.leon.spring.annotation.Scope;
import com.leon.spring.annotation.ComponentScan;
import com.leon.spring.component.Calculate;
import com.leon.spring.component.CalculateAspect;
import com.leon.spring.component.Number;
import com.leon.spring.process.BeanPostProcessor;
import com.leon.spring.process.InitializingBean;
import com.leon.spring.utils.StringUtils;
import java.io.File;
import java.lang.reflect.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* ClassName:MyClassPathXmlContext
* Package:com.leon.make_simple_spring.ioc
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/3 23:08
* @Version: 1.0
*/
public class MyClassPathXmlContext {
// 创建一个Class类来获取注解的配置信息
private Class clazz;
//创建一个存放bean对象信息的容器
private final ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =
new ConcurrentHashMap<>();
//创建一个存放Bean对象的单例池容器
private final ConcurrentHashMap<String, Object> singletonObjects =
new ConcurrentHashMap<>();
//后置处理可能有多个,为了简化操作创建一个List集合存放对象
private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
public MyClassPathXmlContext(Class clazz) {
this.clazz = clazz;
//调用beanDefinitionByScan方法
beanDefinitionByScan();
//调用singletonObjectsInit方法
singletonObjectsInit();
}
public void singletonObjectsInit() {
//获取beanDefinitionMap中的 key信息
Enumeration<String> keys = beanDefinitionMap.keys();
//遍历 beanDefinitionMap集合
while (keys.hasMoreElements()) {
//获取key值
String beanName = keys.nextElement();
//获取BeanDefinition对象
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//判断是单例还是多例
if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
//创建Bean对象
Object bean = createBean(beanDefinition);
//存放进容器中
singletonObjects.put(beanName, bean);
}
}
}
public void beanDefinitionByScan() {
//获取指定的注解类
ComponentScan component =
(ComponentScan) clazz.getDeclaredAnnotation(ComponentScan.class);
//获取注解中的value值
String scanPath = component.value();
//获取一个类加载器
ClassLoader classLoader = clazz.getClassLoader();
//替换扫描路径中的"."换成"/"
scanPath = scanPath.replace(".", "/");
//获取工作路径
URL realPackagePath = classLoader.getResource(scanPath);
//创建一个File的对象
File targetFile = new File(realPackagePath.getFile());
//判断是否是目录
if (targetFile.isDirectory()) {
//获取目标目录下的文件
File[] files = targetFile.listFiles();
//将扫描路径中"/"替换成"."
scanPath = scanPath.replace("/", ".");
//遍历文件
for (File file : files) {
//通过绝对路径,判断是否是Class文件
if (file.getAbsolutePath().endsWith(".class")) {
//获取class文件名
String fileName = file.getName();
//获取类名
String className = fileName.split("\\.")[0];
//获取类路径
String fullClassName = scanPath + "." + className;
try {
//通过类路径获取class对象
Class<?> beanClass = Class.forName(fullClassName);
//判断是否是有对应的注解,如:@Service @Controller @Component @Repository
//为了方便学习,这里只使用了一个注解Component,其他一次类推
if (beanClass.isAnnotationPresent(Component.class)) {
//判断是否是实现了BeanPostProcessor接口的Class
if (BeanPostProcessor.class.isAssignableFrom(beanClass)) {
//获取构造器
Constructor<?> declaredConstructor = beanClass.getDeclaredConstructor();
//创建对象
BeanPostProcessor beanPostProcessor = (BeanPostProcessor) declaredConstructor.newInstance();
//将对象存放到List中
beanPostProcessorList.add(beanPostProcessor);
//结束此次循环
continue;
}
//创建一个beanName
String beanName = null;
//判断是否是指定的注解然后获取value值
if (beanClass.isAnnotationPresent(Component.class)) {
//获取对应的注解对象
Component temp =
beanClass.getDeclaredAnnotation(Component.class);
//获取value值
beanName = temp.value();
//判断id是否存在
if ("".equals(beanName)) {
beanName = StringUtils.uncapitalize(className);
}
}
//创建一个BeanDefinition对象
BeanDefinition beanDefinition = new BeanDefinition();
//设置beanClass
beanDefinition.setClazz(beanClass);
//设置scope
if (beanClass.isAnnotationPresent(Scope.class)) {
//获取Scope注解
Scope scope =
beanClass.getDeclaredAnnotation(Scope.class);
//获取value值
String scopeValue = scope.value();
//判断Scope值是否是"",不是则将scopeValue值设置个scope
if (!"".equals(scopeValue)) {
//设置scope值
beanDefinition.setScope(scopeValue);
}
}
//设置beanName
beanDefinition.setBeanName(beanName);
//存放到容器中
beanDefinitionMap.put(beanName, beanDefinition);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}
//创建Bean对象
public Object createBean(BeanDefinition beanDefinition) {
//获取到Class对象
Class beanClass = beanDefinition.getClazz();
try {
//获取构造器
Constructor declaredConstructor =
beanClass.getDeclaredConstructor();
//创建对象
Object instance = declaredConstructor.newInstance();
//进行业务处理,也就是依赖注入
Field[] declaredFields = beanClass.getDeclaredFields();
//遍历属性
for (Field field : declaredFields) {
//判断是否有Autowired属性
if (field.isAnnotationPresent(Autowired.class)) {
//获取字段名称
String fieldName = field.getName();
//获取对应的Bean对象
Object bean = getBean(fieldName);
//设置为允许访问
field.setAccessible(true);
//注入属性值
field.set(instance, bean);
}
}
System.out.println("创建了对象=========" + instance);
//循环执行后置处理器的前置方法
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
//执行方法
Object current = beanPostProcessor.postProcessBeforeInitialization(instance, beanDefinition.getBeanName());
//不为null就将返回的值赋值给instance
if (current != null) {
instance = current;
}
}
//判断该对象是否实现了InitializingBean接口
if (instance instanceof InitializingBean) {
//执行初化方法
((InitializingBean) instance).afterPropertiesSet();
}
//循环执行后置处理器的后置方法
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
//判断是否是指定的beanName
if ("calculate".equals(beanDefinition.getBeanName())) {
//创建一个属性用来接收instance
Object finalInstance = instance;
//返回代理对象
Object proxy = Proxy.newProxyInstance(beanClass.getClassLoader(), beanClass.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("getSum".equals(method.getName())) {
//前置通知
CalculateAspect.showBeginLog();
//执行方法
Object invoke = method.invoke(finalInstance, args);
//返回通知
CalculateAspect.showBeginLog();
return invoke;
}
Object invoke = method.invoke(finalInstance, args);
return invoke;
}
});
//执行后置方法
Object current = beanPostProcessor.postProcessAfterInitialization(proxy, beanDefinition.getBeanName());
//不为null就将返回的值赋值给instance
if (current != null) {
instance = current;
}
}
if (!"calculate".equals(beanDefinition.getBeanName())) {
Object current = beanPostProcessor.postProcessAfterInitialization(instance, beanDefinition.getBeanName());
//不为null就将返回的值赋值给instance
if (current != null) {
instance = current;
}
}
}
System.out.println("====================================================");
//返回对象
return instance;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
//获取容器中的Bean对象
public Object getBean(String key) {
//判断该Bean是否存在
if (beanDefinitionMap.containsKey(key)) {
BeanDefinition beanDefinition = beanDefinitionMap.get(key);
//判断是否是单例还是多例
//单则直接从单例池中返回
if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
//返回Bean对象
return singletonObjects.get(key);
}
if ("prototype".equalsIgnoreCase(beanDefinition.getScope())) {
//放回新创建的对象
return createBean(beanDefinition);
}
}
return null;
}
}
1.5 process 包
package com.leon.spring.process;
/**
* ClassName:BeanPostProcessor
* Package:com.leon.spring.process
* Description:
* 1.根据原生的Spring容器定义一个BeanPostProcessor接口
* 2.该接口中有两个方法,一个是:postProcessBeforeInitialization() 在初始化方法之前执行
* 一个是:postProcessAfterInitialization() 在初始化方法之后执行
* @Author: leon-->ZGJ
* @Create: 2024/4/6 14:52
* @Version: 1.0
*/
public interface BeanPostProcessor {
default Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
default Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
======================================================================================
package com.leon.spring.process;
/**
* ClassName:InitializingBean
* Package:com.leon.spring.process
* Description:
* 1.根据原生Spring定义了一个InitializingBean接口
* 2.InitializingBean接口中由于一个afterPropertiesSet()方法
* 3.它是在Bean的setter方法执行之后执行的,即我们原来的初始化方法
* 4.当一个Bean实现这个接口后,就是实现了afterPropertiesSet()方法
* @Author: leon-->ZGJ
* @Create: 2024/4/6 14:39
* @Version: 1.0
*/
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
1.6 test 包
package com.leon.spring.test;
import com.leon.spring.component.Number;
import com.leon.spring.component.UserService;
import com.leon.spring.config.ConfigClass;
import com.leon.spring.ioc.MyClassPathXmlContext;
import com.leon.spring.utils.StringUtils;
import org.junit.Test;
/**
* ClassName:MyClassPathXmlContext
* Package:com.leon.make_simple_spring.test
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/3 23:11
* @Version: 1.0
*/
public class MyClassPathXmlContextTest{
@Test
public void test01(){
MyClassPathXmlContext myClassPathXmlContext = new MyClassPathXmlContext(ConfigClass.class);
//Object userController = myClassPathXmlContext.getBean("userController");
//Object userController2 = myClassPathXmlContext.getBean("userController");
//
//System.out.println(userController);
//System.out.println(userController2);
//
//Object userDao = myClassPathXmlContext.getBean("userDao");
//Object userDao2 = myClassPathXmlContext.getBean("userDao");
//
//System.out.println(userDao);
//System.out.println(userDao2);
UserService userService = (UserService) myClassPathXmlContext.getBean("userService");
userService.ok();
Number number = (Number) myClassPathXmlContext.getBean("calculate");
number.getSub(10,2);
number.getSum(10,2);
System.out.println(number.getClass());
}
@Test
public void stringUtilsTest(){
StringUtils.uncapitalize("HolleWord");
}
}
1.7 utils包
package com.leon.spring.utils;
/**
* ClassName:StringUtils
* Package:com.leon.spring.utils
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/6 9:48
* @Version: 1.0
*/
public class StringUtils {
public static String uncapitalize(String str){
String temp = str.charAt(0) + "";
String lowerCase = temp.toLowerCase();
return str.replace(temp,lowerCase);
}
}
十、JDBCTemplate
1. JDBCTemplate 基本介绍
官方文档
- spring-framework-5.3.8\docs\javadoc-api\index.html
基本介绍
通过Spring可以配置数据源,从而完成对数据表的操作
JDBCTemplate是Spring提供的访问数据库的技术。可以将JDBC的常用操作装为模板方法
2. JDBCTemplate 的使用和配置
2.1 配置JDBCTemplate
xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--配置要读取的配置文件--> <context:property-placeholder location="classpath:jdbctemplate.properties"/> <!--配置数据库连接池(配置数据源)--> <bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="comboPooledDataSource"> <!--配置数据库连接需要的一些参数--> <!--用户名称--> <property name="user" value="${jdbc.user}"/> <!--用户密码--> <property name="password" value="${jdbc.password}"/> <!--驱动路径--> <property name="driverClass" value="${jdbc.driver}"/> <!--连接的URL--> <property name="jdbcUrl" value="${jdbc.url}"/> </bean> <!--配置JDBETemplate--> <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate"> <!--配置数据源--> <property name="dataSource" ref="comboPooledDataSource"/> </bean> </beans>
2.2 JDBCTemplate API的使用
2.2.1 添加数据,修改数据,删除数据
@Test
public void JDBCTemplateByUpdate(){
//创建Spring IOC 容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("JDBCTemplate_ioc.xml");
//获取JdbcTemplate对象
JdbcTemplate jdbcTemplate = (JdbcTemplate) ioc.getBean("jdbcTemplate");
//创建说起来语句
//插入语句
String sqlInsert = "insert into monster value(?,?,?)";
//修改语句
String sqlUpdate = "update monster set skill= ? where id = ?";
//删除语句
String sqlDelete = "delete from monster where id = ? ";
//执行插入语句
jdbcTemplate.update(sqlInsert,4, "银角大王", "紫金葫芦");
//执行修改语句
jdbcTemplate.update(sqlUpdate,"吐水",4);
//执行删除语句
jdbcTemplate.update(sqlDelete,4);
}
2.2.2 查询语句,所有的数据,单行的数据,单行单列的数据
/*
* 使用API技巧
* 1.先确定API的名字
* 2.根据API提供相应的参数
* 3.将自己的调用思路理清楚
* */
@Test
public void jdbcTemplateByQuery(){
//创建Spring IOC 容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("JDBCTemplate_ioc.xml");
//获取JdbcTemplate对象
JdbcTemplate jdbcTemplate = (JdbcTemplate) ioc.getBean("jdbcTemplate");
//创建sql语句
//查询多个对象
String sql = "select id,name,skill from monster";
//查询一个对象
String sqlOneObject = "select id,name,skill from monster where id = ?";
//查询单行单列
String sqlSingleRowAndColumn = "select name from monster where id = ?";
//创建一个BeanPropertyRowMapper对象来传递Class对象,用RowMapper接口引用
RowMapper<Monster> rowMapper = new BeanPropertyRowMapper(Monster.class);
//执行sql语句,并获取返回的结果,查询所有数据
List<Monster> query = jdbcTemplate.query(sql, rowMapper);
//遍历结果
for (Monster monster : query) {
System.out.println(monster);
}
//查询单行
Monster monster = jdbcTemplate.queryForObject(sqlOneObject, rowMapper, 1);
System.out.println(monster);
//查询单行单列
String name = jdbcTemplate.queryForObject(sqlSingleRowAndColumn, String.class, 2);
System.out.println(name);
}
2.2.3 批处理
@Test
public void jdbcTemplateByBatch(){
//创建Spring IOC 容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("JDBCTemplate_ioc.xml");
//获取JdbcTemplate对象
JdbcTemplate jdbcTemplate = (JdbcTemplate) ioc.getBean("jdbcTemplate");
//创建sql语句
String sql ="insert into monster value(?,?,?)";
//创建一个集合要来存放预编译的数据
List<Object[]> values = new ArrayList<>();
//添加批处理的数据
values.add(new Object[]{4,"红孩儿","三昧真火"});
values.add(new Object[]{5,"猪八戒","九齿钉耙"});
//返回的数组是影响的行数
int[] ints = jdbcTemplate.batchUpdate(sql, values);
//遍历返回的数据
for (int i : ints) {
System.out.println(i);
}
}
2.2.4 具名参数
① xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置要读取的配置文件-->
<context:property-placeholder location="classpath:jdbctemplate.properties"/>
<!--配置数据库连接池(配置数据源)-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="comboPooledDataSource">
<!--配置数据库连接需要的一些参数-->
<!--用户名称-->
<property name="user" value="${jdbc.user}"/>
<!--用户密码-->
<property name="password" value="${jdbc.password}"/>
<!--驱动路径-->
<property name="driverClass" value="${jdbc.driver}"/>
<!--连接的URL-->
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
<!--配置JDBETemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<!--配置数据源-->
<property name="dataSource" ref="comboPooledDataSource"/>
</bean>
<!--配置NamedParameterJdbcTemplate-->
<bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" id="namedParameterJdbcTemplate">
<!--通过构造器对数据源进行引用-->
<constructor-arg name="dataSource" ref="comboPooledDataSource"/>
</bean>
②代码
@Test
public void namedParameterJdbcTemplate(){
//创建Spring IOC 容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("JDBCTemplate_ioc.xml");
//获取NamedParameterJdbcTemplate对象
NamedParameterJdbcTemplate namedParameterJdbcTemplate =
ioc.getBean("namedParameterJdbcTemplate", NamedParameterJdbcTemplate.class);
//插入一条数据
String sql = "insert into monster value(:id,:name,:skill)";
Map<String,Object> parameter = new HashMap<>();
parameter.put("id",6);
parameter.put("name","孙悟空");
parameter.put("skill","七十二变");
namedParameterJdbcTemplate.update(sql,parameter);
}
2.2.5 sqlparametersource
① 代码
@Test
public void namedParameterJdbcTemplateBySqlParameterSource(){
//创建Spring IOC 容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("JDBCTemplate_ioc.xml");
//获取NamedParameterJdbcTemplate对象
NamedParameterJdbcTemplate namedParameterJdbcTemplate =
ioc.getBean("namedParameterJdbcTemplate", NamedParameterJdbcTemplate.class);
//插入一条数据
String sql = "insert into monster value(:id,:name,:skill)";
//创建一个Monster对象
/*
* 注意:
* 1.Monster的对象属性要和具名参数名字相同,否则会报错
* 也就是和id,name,skill名称相同
* */
Monster monster = new Monster(7,"沙僧","宝月禅杖");
//这个需要将Monster对象,通过构造器赋值给BeanPropertySqlParameterSource的内部属性
SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(monster);
//执行sql语句
int update = namedParameterJdbcTemplate.update(sql, parameterSource);
System.out.println(update);
}
2.3 DAO 使用JdbcTemplate
2.3.1 xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置包扫描-->
<context:component-scan base-package="com.leon.jdbctemplate.dao"/>
<!--配置要读取的配置文件-->
<context:property-placeholder location="classpath:jdbctemplate.properties"/>
<!--配置数据库连接池(配置数据源)-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="comboPooledDataSource">
<!--配置数据库连接需要的一些参数-->
<!--用户名称-->
<property name="user" value="${jdbc.user}"/>
<!--用户密码-->
<property name="password" value="${jdbc.password}"/>
<!--驱动路径-->
<property name="driverClass" value="${jdbc.driver}"/>
<!--连接的URL-->
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
<!--配置JDBETemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<!--配置数据源-->
<property name="dataSource" ref="comboPooledDataSource"/>
</bean>
<!--配置NamedParameterJdbcTemplate-->
<bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" id="namedParameterJdbcTemplate">
<!--通过构造器对数据源进行引用-->
<constructor-arg name="dataSource" ref="comboPooledDataSource"/>
</bean>
</beans>
2.3.2 Dao包
package com.leon.jdbctemplate.dao;
import com.leon.jdbctemplate.pojo.Monster;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.lang.annotation.Retention;
/**
* ClassName:MonsterDao
* Package:com.leon.jdbctemplate.dao
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/9 13:47
* @Version: 1.0
*/
@Repository
public class MonsterDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public int insertMonster(Monster monster){
String sql = "insert into monster value(?,?,?)";
int affected = jdbcTemplate.update(sql, monster.getId(), monster.getName(), monster.getSkill());
return affected;
}
}
2.3.3 测试类
@Test
public void daoJdbcTemplate(){
//创建Spring IOC 容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("JDBCTemplate_ioc.xml");
//获取Dao对象
MonsterDao monsterDao = ioc.getBean("monsterDao", MonsterDao.class);
//调用方法
int i = monsterDao.insertMonster(new Monster(8, "老鼠精", "吹风"));
System.out.println(i);
}
十一、事务
1.事务的分类
1.1 编程式事务
Connection connection = JdbcUtils.getConnection();
try{
//1.先设置事务不要自动提交
connection.setAutoCommit(false);
//2.进行各种增删改查
//多表的修改、添加、删除
//3.提交
connection.commit();
}catch(Exception e){
//4.回滚
connection.rollback();
}
1.2 声明式事务(底层使用:AOP 【动态代理+动态绑定+反射+注解 】)
1.2.1 dao 包
package com.leon.tx.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
* ClassName:GoodsDao
* Package:com.leon.jdbctemplate.dao
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/9 20:47
* @Version: 1.0
*/
@Repository
public class GoodsDao {
@Resource
private JdbcTemplate jdbcTemplate ;
public Double queryPriceById(Integer id){
//创建sql语句
String sql = "select price from goods where goods_id=?";
//执行sql语句
Double price = jdbcTemplate.queryForObject(sql, Double.class, id);
return price ;
}
public void updateBalance(Integer user_id,Double money){
String sql = "update user_account set money = money - ? where user_id = ?";
jdbcTemplate.update(sql,money,user_id);
}
public void updateAmount(Integer goods_id,int amount){
String sql ="update1 goods_amount set goods_num = goods_num - ? where goods_id=?";
jdbcTemplate.update(sql,amount,goods_id);
}
}
1.2.2 service包
package com.leon.tx.service;
import com.leon.tx.dao.GoodsDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* ClassName:GoodsService
* Package:com.leon.tx.dao.service
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/9 21:16
* @Version: 1.0
*/
@Service
public class GoodsService {
@Resource
private GoodsDao goodsDao;
/**
* 1. Transactional注解 可以进行声明式事务控制
* 即将标识的方法中的对数据库的操作作为一个事务处理
* 2. Transactional注解 底层使用的是AOP机制
* buyGoods() 方法执行前,会先调用事务管理器的begin()方法 然后再调用buyGoods()方法
* 如果执行没有发生异常,则调用事务管理器的doCommit() 方法,如果发生异常则调用事务管理器的doRollback()方法
*
* @param userId
* @param goodsId
* @param amount
*/
@Transactional
public void buyGoods(Integer userId,Integer goodsId,Integer amount){
Double price = goodsDao.queryPriceById(goodsId);
goodsDao.updateBalance(userId,price*amount);
goodsDao.updateAmount(goodsId,amount);
System.out.println("购买成功~~~~~");
}
}
1.2.3 xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:content="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--配置包扫描-->
<content:component-scan base-package="com.leon.tx.dao"/>
<!--配置包扫描-->
<content:component-scan base-package="com.leon.tx.service"/>
<!--配置要读去的配置文件-->
<content:property-placeholder location="classpath:jdbctemplate.properties"/>
<!--配置数据库连接池(数据源)-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="comboPooledDataSource">
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
<!--配置JdbcTemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="comboPooledDataSource"/>
</bean>
<!--配置事务管理器
1.DataSourceTransactionManager 这个对象是进行事务管理
2.需要配置数据源,这样事务管理器才知道是对哪个数据源进行事务控制
-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
<property name="dataSource" ref="comboPooledDataSource"/>
</bean>
<!--启用基于注解的声明式事务管理功能
1. transaction-manager 属性表示启用哪个事务管理器
-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
</beans>
1.2.4 test类
@Test
public void buyGoodsTest(){
//创建容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("tx_ioc.xml");
//获取GoodsService对象
GoodsService goodsService = ioc.getBean("goodsService", GoodsService.class);
goodsService.buyGoods(1,1,1);
}
1.3 事务传播机制
1.3.1 事务传播的属性/种类一览表
传播属性 | 描述 |
---|---|
REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行,否则就启动一个新 的事务,并在自己的事务内运行 |
REQUIRES_NEW | 当前的方法必须启动新的事务,并在它自己的事务内运行,如果有事务正 在运行,应该将它挂起 |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 |
NOT_SUPPORTED | 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起 |
MANDATORY | 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常 |
NEVER | 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则 ,就启动一个新的事务,并在它自己的事务内运行 |
1.3.2 REQUIRED和REQUIRES_NEW 解释
①运行代码
@Transactional
public void buyGoods(){
//被Transactional注解修饰
goodsService.buyGoods(1,1,1);
//被Transactional注解修饰
goodsService.buyGoods2(1,1,1);
}
- 为了方便解释,这里将buyGoods()方法、goodsService.buyGoods(1,1,1)、 goodsService.buyGoods2(1,1,1)说成事务,
②REQUIRED解释(默认的事务传播机制)
运行时,goodsService.buyGoods(1,1,1)事务和goodsService.buyGoods2(1,1,1)事务会在buyGoods这个事务中运行,其中任何一个事务出现异常,所有的操作数据都会回滚,它们是作为一个整体的事务运行,而不是独立的事务
图解
③REQUIRES_NEW解释
运行时,当运行到goodsService.buyGoods(1,1,1)事务时会将buyGoods()事务挂起直至运行结束,当运行goodsService.buyGoods2(1,1,1)事务时,也会将buyGoods()事务挂起直至运行结束,然后再运行buyGoods()事务,在运行过程中任何一个事务发生异常都不会影响其他事务数据的回滚,每一个事务都是独立的事务,互不干扰
图解
1.3.3 事务的传播机制的设置方法
/**
* 1. Transactional注解 可以进行声明式事务控制
* 即将标识的方法中的对数据库的操作作为一个事务处理
* 2. Transactional注解 底层使用的是AOP机制
* buyGoods() 方法执行前,会先调用事务管理器的begin()方法 然后再调用buyGoods()方法
* 如果执行没有发生异常,则调用事务管理器的doCommit() 方法,如果发生异常则调用事务管理器的doRollback()方法
* 3. 通过Transactional注解中propagation来设置事务传播机制的属性,通过Propagation来获取传播机制的属性
* @param userId
* @param goodsId
* @param amount
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoods(Integer userId,Integer goodsId,Integer amount){
Double price = goodsDao.queryPriceById(goodsId);
goodsDao.updateBalance(userId,price*amount);
goodsDao.updateAmount(goodsId,amount);
System.out.println("buyGoods() -- 购买成功~~~~~");
}
1.3.4 代码
① Dao包
package com.leon.tx.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
* ClassName:GoodsDao
* Package:com.leon.jdbctemplate.dao
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/9 20:47
* @Version: 1.0
*/
@Repository
public class GoodsDao {
@Resource
private JdbcTemplate jdbcTemplate ;
public Double queryPriceById(Integer id){
//创建sql语句
String sql = "select price from goods where goods_id=?";
//执行sql语句
Double price = jdbcTemplate.queryForObject(sql, Double.class, id);
return price ;
}
public void updateBalance(Integer user_id,Double money){
String sql = "update user_account set money = money - ? where user_id = ?";
jdbcTemplate.update(sql,money,user_id);
}
public void updateAmount(Integer goods_id,int amount){
String sql ="update goods_amount set goods_num = goods_num - ? where goods_id=?";
jdbcTemplate.update(sql,amount,goods_id);
}
public Double queryPriceById2(Integer id){
//创建sql语句
String sql = "select price from goods where goods_id=?";
//执行sql语句
Double price = jdbcTemplate.queryForObject(sql, Double.class, id);
return price ;
}
public void updateBalance2(Integer user_id,Double money){
String sql = "update user_account set money = money - ? where user_id = ?";
jdbcTemplate.update(sql,money,user_id);
}
public void updateAmount2(Integer goods_id,int amount){
String sql ="update goods_amount set goods_num = goods_num - ? where goods_id=?";
jdbcTemplate.update(sql,amount,goods_id);
}
}
② Service包
package com.leon.tx.service;
import com.leon.tx.dao.GoodsDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* ClassName:GoodsService
* Package:com.leon.tx.dao.service
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/9 21:16
* @Version: 1.0
*/
@Service
public class GoodsService {
@Resource
private GoodsDao goodsDao;
/**
* 1. Transactional注解 可以进行声明式事务控制
* 即将标识的方法中的对数据库的操作作为一个事务处理
* 2. Transactional注解 底层使用的是AOP机制
* buyGoods() 方法执行前,会先调用事务管理器的begin()方法 然后再调用buyGoods()方法
* 如果执行没有发生异常,则调用事务管理器的doCommit() 方法,如果发生异常则调用事务管理器的doRollback()方法
* 3. 通过Transactional注解中propagation来设置事务传播机制的属性,通过Propagation来获取传播机制的属性
* 4. propagation = Propagation.REQUIRED 如果buyGoods()发生异常则其他事务都会失败,如果buyGoods()没有发生异常,
* 且其他事务也没有发生异常,才会成功
* * 5. propagation = Propagation.REQUIRES_NEW 不管其他的事务如何,只要buyGoods()没有发生异常则当前事务就会执行成功
* @param userId
* @param goodsId
* @param amount
*/
//@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional
public void buyGoods(Integer userId,Integer goodsId,Integer amount){
Double price = goodsDao.queryPriceById(goodsId);
goodsDao.updateBalance(userId,price*amount);
goodsDao.updateAmount(goodsId,amount);
System.out.println("buyGoods() -- 购买成功~~~~~");
}
//@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional
public void buyGoods2(Integer userId,Integer goodsId,Integer amount){
Double price = goodsDao.queryPriceById2(goodsId);
goodsDao.updateBalance2(userId,price*amount);
goodsDao.updateAmount2(goodsId,amount);
System.out.println("buyGoods2() -- 购买成功~~~~~");
}
}
③ Controller包
package com.leon.tx.controller;
import com.leon.tx.service.GoodsService;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* ClassName:GoodsController
* Package:com.leon.tx.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/10 9:10
* @Version: 1.0
*/
@Controller
public class GoodsController {
@Resource
private GoodsService goodsService ;
@Transactional
public void buyGoods(){
//被Transactional注解修饰
goodsService.buyGoods(1,1,1);
//被Transactional注解修饰
goodsService.buyGoods2(1,1,1);
}
}
④ xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:content="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--配置包扫描-->
<content:component-scan base-package="com.leon.tx.dao"/>
<!--配置包扫描-->
<content:component-scan base-package="com.leon.tx.service"/>
<!--配置包扫描-->
<content:component-scan base-package="com.leon.tx.controller"/>
<!--配置要读去的配置文件-->
<content:property-placeholder location="classpath:jdbctemplate.properties"/>
<!--配置数据库连接池(数据源)-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="comboPooledDataSource">
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
<!--配置JdbcTemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="comboPooledDataSource"/>
</bean>
<!--配置事务管理器
1.DataSourceTransactionManager 这个对象是进行事务管理
2.需要配置数据源,这样事务管理器才知道是对哪个数据源进行事务控制
-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
<property name="dataSource" ref="comboPooledDataSource"/>
</bean>
<!--启用基于注解的声明式事务管理功能
1. transaction-manager 属性表示启用哪个事务管理器
-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
</beans>
⑤ test包
@Test
public void txPropagation(){
//创建容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("tx_ioc.xml");
//获取Controller对象
GoodsController goodsController = ioc.getBean("goodsController", GoodsController.class);
goodsController.buyGoods();
}
2. 事务隔离级别
2.1 隔离级别表格
隔离级别 | 脏读 | 不可重读 | 幻读 | 加锁读 |
---|---|---|---|---|
读未提交(Read uncommitted) | √ | √ | √ | 不加锁 |
读已提交(Read committed) | × | √ | √ | 不加锁 |
可重复度(Repeatable read) | × | ×【SQL 92 标准,MySql数据库改进了,解决了这个级别幻读的问题】 | × | 不加锁 |
可串行化(Serializable) | × | × | × | 加锁 |
2.2 事务隔离级别说明
- 默认的隔离级别,就是MySql数据库默认的隔离级别一般为 PEPEATABLE_READ
- 查看源码可知Isolation.DEFAULT 是:Use the default isolation level of the underlying datastore.
- 查看数据库,默认的隔离级别:select @@transaction_isolation (msql8)
2.2.1 代码
/*
* 1.isolation 可以设置隔离级别,通过Isolation类来获取属性
* 2.默认的隔离级别是:REPEATABLE_READ
* 3.Isolation.READ_COMMITTED 表示读可提交,也就是只要是提交的数据,在当前事务是可以读取到最新数据的
* */
@Transactional(isolation = Isolation.READ_COMMITTED)
public void queryPriceByTxIsolation(){
Double price = goodsDao.queryPriceById(1);
System.out.println("第一次查询的结果===>"+price);
Double price2 = goodsDao.queryPriceById(1);
System.out.println("第一次查询的结果===>"+price2
);
}
2.3 事务超时回滚
2.3.1 基本介绍
- 如果一个事务执行的时间超过某个时间限制,就让该事务回滚
- 可以通过设置事务超时回滚来实现
2.3.2 基本语法
@Transactional(timeout = 2)
2.3.3 代码
/*
* 1.timeout 表示超时时间,这里设置的是2秒,它以秒为单位,如果超过了两秒就会进行回滚
* 2.如果没有设置timeout 默认是-1,表示使用事务的默认超时时间或者不支持
*
* */
@Transactional(timeout = 2)
public void buyGoodsTimeOut(Integer userId,Integer goodsId,Integer amount){
//查询价格
Double price = goodsDao.queryPriceById(goodsId);
try {
//设置睡眠时间,睡眠4秒,超过设置的时间看是否会回滚
Thread.sleep(4000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//减少余额
goodsDao.updateBalance(userId,amount*price);
//减少库存
goodsDao.updateAmount(goodsId,amount);
}