Spring Spring6 Nuyoah 2023-02-20 2024-03-19 概述
Spring是什么
Spring 是一款主流的 Java EE 轻量级开源框架 ,Spring 由“Spring 之父”Rod Johnson 提出并创立,其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring 框架除了自己提供功能外,还提供整合其他技术和框架的能力。
Spring 自诞生以来备受青睐,一直被广大开发人员作为 Java 企业级应用程序开发的首选。时至今日,Spring 俨然成为了 Java EE 代名词,成为了构建 Java EE 应用的事实标准。
自 2004 年 4 月,Spring 1.0 版本正式发布以来,Spring 已经步入到了第 6 个大版本,也就是 Spring 6。本课程采用Spring当前最新发布的正式版本6.0.2 。
Spring的广义和狭义
在不同的语境中,Spring 所代表的含义是不同的。下面我们就分别从“广义”和“狭义”两个角度,对 Spring 进行介绍。
广义的 Spring:Spring 技术栈
广义上的 Spring 泛指以 Spring Framework 为核心的 Spring 技术栈。
经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。
这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。
狭义的 Spring:Spring Framework
狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。
Spring 框架是一个分层的、面向切面的 Java 应用程序的一站式轻量级解决方案,它是 Spring 技术栈的核心和基础,是为了解决企业级应用开发的复杂性而创建的。
Spring 有两个最核心模块: IoC 和 AOP。
IoC :Inverse of Control 的简写,译为“控制反转”,指把创建对象过程交给 Spring 进行管理。
AOP :Aspect Oriented Programming 的简写,译为“面向切面编程”。AOP 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP 还解决一些系统层面上的问题,比如日志、事务、权限等。
SpringFrameWork特点
非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。
控制反转:IoC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。
面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。
容器:Spring IoC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。
组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。
一站式:在 IoC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且 Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。
Spring模块组成
①Spring Core(核心容器)
spring core提供了IOC,DI,Bean配置装载创建的核心实现。核心概念: Beans、BeanFactory、BeanDefinitions、ApplicationContext。
spring-core :IOC和DI的基本实现
spring-beans:BeanFactory和Bean的装配管理(BeanFactory)
spring-context:Spring context上下文,即IOC容器(AppliactionContext)
spring-expression:spring表达式语言
②Spring AOP
spring-aop:面向切面编程的应用模块,整合ASM,CGLib,JDK Proxy
spring-aspects:集成AspectJ,AOP应用框架
spring-instrument:动态Class Loading模块
③Spring Data Access
spring-jdbc:spring对JDBC的封装,用于简化jdbc操作
spring-orm:java对象与数据库数据的映射框架
spring-oxm:对象与xml文件的映射框架
spring-jms: Spring对Java Message Service(java消息服务)的封装,用于服务之间相互通信
spring-tx:spring jdbc事务管理
④Spring Web
spring-web:最基础的web支持,建立于spring-context之上,通过servlet或listener来初始化IOC容器
spring-webmvc:实现web mvc
spring-websocket:与前端的全双工通信协议
spring-webflux:Spring 5.0提供的,用于取代传统java servlet,非阻塞式Reactive Web框架,异步,非阻塞,事件驱动的服务
⑤Spring Message
Spring-messaging:spring 4.0提供的,为Spring集成一些基础的报文传送服务
⑥Spring test
spring-test:集成测试支持,主要是对junit的封装
入门开发
程序开发
引入依赖
添加依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 6.0.2</version > </dependency > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter-api</artifactId > <version > 5.3.1</version > </dependency > </dependencies >
查看依赖:
创建java类
1 2 3 4 5 6 7 8 package com.atguigu.spring6.bean;public class HelloWorld { public void sayHello () { System.out.println("helloworld" ); } }
创建配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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" > <bean id ="helloWorld" class ="com.atguigu.spring6.bean.HelloWorld" > </bean > </beans >
创建测试类测试
1 2 3 4 5 6 7 8 9 public class HelloWorldTest { @Test public void testHelloWorld () { ApplicationContext ac = new ClassPathXmlApplicationContext ("beans.xml" ); HelloWorld helloworld = (HelloWorld) ac.getBean("helloWorld" ); helloworld.sayHello(); } }
程序分析
底层是通过反射机制调用无参数构造方法
把创建好的对象存储到一个什么样的数据结构当中了呢?
bean对象最终存储在spring容器中,在spring源码底层就是一个map集合,存储bean的map在DefaultListableBeanFactory 类中:
1 private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap <>(256 );
Spring容器加载到Bean类时 , 会把这个类的描述信息, 以包名加类名的方式存到beanDefinitionMap 中,
Map<String,BeanDefinition> , 其中 String是Key , 默认是类名首字母小写 , BeanDefinition , 存的是类的定义(描述信息) , 我们通常叫BeanDefinition接口为 : bean的定义对象。
启用Log4j2日志框架
Log4j2日志概述
在项目开发中,日志十分的重要,不管是记录运行情况还是定位线上问题,都离不开对日志的分析。日志记录了系统行为的时间、地点、状态等相关信息,能够帮助我们了解并监控系统状态,在发生错误或者接近某种危险状态时能够及时提醒我们处理,同时在系统产生问题时,能够帮助我们快速的定位、诊断并解决问题。
Apache Log4j2 是一个开源的日志记录组件,使用非常的广泛。在工程中以易用方便代替了 System.out 等打印语句,它是JAVA下最流行的日志输入工具。
Log4j2主要由几个重要的组件构成:
(1)日志信息的优先级 ,日志信息的优先级从高到低有TRACE < DEBUG < INFO < WARN < ERROR < FATAL
TRACE:追踪,是最低的日志级别,相当于追踪程序的执行
DEBUG:调试,一般在开发中,都将其设置为最低的日志级别
INFO:信息,输出重要的信息,使用较多
WARN:警告,输出警告的信息
ERROR:错误,输出错误信息
FATAL:严重错误
这些级别分别用来指定这条日志信息的重要程度;级别高的会自动屏蔽级别低的日志,也就是说,设置了WARN的日志,则INFO、DEBUG的日志级别的日志不会显示
(2)日志信息的输出目的地 ,日志信息的输出目的地指定了日志将打印到控制台 还是文件中 ;
(3)日志信息的输出格式 ,而输出格式则控制了日志信息的显示内容。
引入Log4j2依赖
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.apache.logging.log4j</groupId > <artifactId > log4j-core</artifactId > <version > 2.19.0</version > </dependency > <dependency > <groupId > org.apache.logging.log4j</groupId > <artifactId > log4j-slf4j2-impl</artifactId > <version > 2.19.0</version > </dependency >
加入日志配置文件
在类的根路径下提供log4j2.xml配置文件(文件名固定为:log4j2.xml,文件必须放到类根路径下。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <?xml version="1.0" encoding="UTF-8" ?> <configuration > <loggers > <root level ="DEBUG" > <appender-ref ref ="spring6log" /> <appender-ref ref ="RollingFile" /> <appender-ref ref ="log" /> </root > </loggers > <appenders > <console name ="spring6log" target ="SYSTEM_OUT" > <PatternLayout pattern ="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n" /> </console > <File name ="log" fileName ="d:/spring6_log/test.log" append ="false" > <PatternLayout pattern ="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n" /> </File > <RollingFile name ="RollingFile" fileName ="d:/spring6_log/app.log" filePattern ="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz" > <PatternLayout pattern ="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n" /> <SizeBasedTriggeringPolicy size ="50MB" /> <DefaultRolloverStrategy max ="20" /> </RollingFile > </appenders > </configuration >
使用日志
1 2 3 4 5 6 7 8 9 10 11 12 public class HelloWorldTest { private Logger logger = LoggerFactory.getLogger(HelloWorldTest.class); @Test public void testHelloWorld () { ApplicationContext ac = new ClassPathXmlApplicationContext ("beans.xml" ); HelloWorld helloworld = (HelloWorld) ac.getBean("helloWorld" ); helloworld.sayHello(); logger.info("执行成功" ); } }
IOC
IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想 ,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序 。
Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。
IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。
IoC概述
控制反转(IoC)
控制反转是一种思想。
控制反转是为了降低程序耦合度,提高程序扩展力。
控制反转,反转的是什么?
将对象的创建权利交出去,交给第三方容器负责。
将对象和对象之间关系的维护权交出去,交给第三方容器负责。
控制反转这种思想如何实现呢?
DI(Dependency Injection):依赖注入
依赖注入
DI(Dependency Injection):依赖注入,依赖注入实现了控制反转的思想。
依赖注入:
指Spring创建对象的过程中,将对象依赖属性通过配置进行注入
依赖注入常见的实现方式包括两种:
所以结论是:IOC 就是一种控制反转的思想, 而 DI 是对IoC的一种具体实现。
Bean管理说的是:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。
IoC容器在Spring的实现
Spring 的 IoC 容器就是 IoC思想的一个落地的产品实现。IoC容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建IoC 容器。Spring 提供了IoC 容器的两种实现方式:
①BeanFactory
这是 IoC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。
②ApplicationContext
BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。
③ApplicationContext的主要实现类
类型名
简介
ClassPathXmlApplicationContext
通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext
通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContext
ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。
WebApplicationContext
专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。
基于XML管理Bean
实验一:获取Bean
1 2 3 4 5 6 7 8 9 10 11 @Test public void test01 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" ); User user1 = (User) applicationContext.getBean("user" ); System.out.println("ID : " + user1); }
1 2 3 4 5 6 7 8 9 @Test public void test01 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" ); User user2 = applicationContext.getBean(User.class); System.out.println("Class : " + user2); }
1 2 3 4 5 6 7 8 9 @Test public void test01 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" ); User user3 = applicationContext.getBean("user" , User.class); System.out.println("ID && Class : " + user3); }
当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个
当IOC容器中一共配置了两个:
1 2 <bean id ="helloworldOne" class ="com.atguigu.spring6.bean.HelloWorld" > </bean > <bean id ="helloworldTwo" class ="com.atguigu.spring6.bean.HelloWorld" > </bean >
根据类型获取 时会抛出异常:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.atguigu.spring6.bean.HelloWorld’ available: expected single matching bean but found 2: helloworldOne,helloworldTwo
如果出现上面这种情况的话,则需要通过ID获取或者通过ID和类名获取 。
如果组件类实现了接口,根据接口类型 可以获取 bean 吗?
可以,前提是bean唯一
1 2 3 4 5 6 7 <?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" > <bean id ="user" class ="com.hgu.User" /> <bean id ="userImpl" class ="com.hgu.bean.UserImpl" /> </beans >
1 2 3 4 5 6 public void test02 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" ); UserBean bean = applicationContext.getBean(UserBean.class); System.out.println(bean); }
如果一个接口有多个实现类 ,这些实现类都配置了 bean,根据接口类型 可以获取 bean 吗?
不行,因为bean不唯一
1 2 3 4 5 6 7 8 <?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" > <bean id ="user" class ="com.hgu.User" /> <bean id ="userImpl" class ="com.hgu.bean.UserImpl" /> <bean id ="personImpl" class ="com.hgu.bean.PersonImpl" /> </beans >
1 2 3 4 5 6 7 public void test02 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" ); UserBean bean = applicationContext.getBean(UserBean.class); System.out.println(bean); }
结论
根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。
java中,instanceof运算符用于判断前面的对象是否是后面的类,或其子类、实现类的实例。如果是返回true,否则返回false。也就是说:用instanceof关键字做判断时, instanceof 操作符的左右操作必须有继承或实现关系
实验二:依赖注入之通过Setter注入
第一步. 创建实例类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.hgu;public class User { private String like; private String grade; public User () { } public String getLike () { return like; } public void setLike (String like) { this .like = like; } public String getGrade () { return grade; } public void setGrade (String grade) { this .grade = grade; } }
第二步. 在Spring配置文件配置
1 2 3 4 5 6 7 8 9 <?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" > <bean id ="user" class ="com.hgu.User" > <property name ="grade" value ="A" /> <property name ="like" value ="B" /> </bean > </beans >
第三步. 验证结果
1 2 3 4 5 6 7 8 9 10 @Test public void test01 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" ); User user1 = (User) applicationContext.getBean("user" ); System.out.println(user1); }
实验三:依赖注入之通过构造器注入
第一步. 创建实例类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.hgu;public class User { private String like; private String grade; public User () { } public User (String like, String grade) { System.out.println("有参构造器" ); this .like = like; this .grade = grade; } public String getLike () { return like; } public void setLike (String like) { this .like = like; } public String getGrade () { return grade; } public void setGrade (String grade) { this .grade = grade; } @Override public String toString () { return "User{" + "like='" + like + '\'' + ", grade='" + grade + '\'' + '}' ; } }
第二步. 在Spring配置文件配置
通过constructor-arg设置,name为属性名,value为属性值
1 2 3 4 5 6 7 8 9 10 11 <?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" > <bean id="user" class="com.hgu.User" > <constructor-arg name="grade" value="A" /> <constructor-arg name="like" value="B" /> </bean> </beans>
第三步. 验证结果
1 2 3 4 5 6 7 8 9 10 11 @Test public void test01 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" ); User user1 = (User) applicationContext.getBean("user" ); System.out.println(user1); }
实验四:特殊值处理
什么是字面量?
int a = 10;
声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a的时候,我们实际上拿到的值是10。
而如果a是带引号的:‘a’,那么它现在不是一个变量,它就是代表a这个字母本身,这就是字面量。所以字面量没有引申含义,就是我们看到的这个数据本身。
1 2 <property name ="name" value ="张三" />
1 2 3 <property name ="name" > <null /> </property >
注意:
1 <property name ="name" value ="null" > </property >
以上写法,为name所赋的值是字符串null
1 2 3 <property name ="expression" value ="a < b" />
因为我们在XML里面有很多特殊字符,所以我们需要使用< ![CDATA[a < b ]] >
1 2 3 4 5 6 7 <property name ="expression" > <value > <![CDATA[a < b]]></value > </property >
实验五:为对象类型属性赋值
外部bean
第一步:创建两个对象
Person
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.hgu;public class Person { private String sex; private String name; public Person () { } public String getSex () { return sex; } public void setSex (String sex) { this .sex = sex; } public String getName () { return name; } public void setName (String name) { this .name = name; } @Override public String toString () { return "Person{" + "sex='" + sex + '\'' + ", name='" + name + '\'' + '}' ; } }
User
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package com.hgu;public class User { private String like; private String grade; private Person person; public User () { } public User (String like, String grade) { System.out.println("有参构造器" ); this .like = like; this .grade = grade; } public String getLike () { return like; } public void setLike (String like) { this .like = like; } public String getGrade () { return grade; } public void setGrade (String grade) { this .grade = grade; } public Person getPerson () { return person; } public void setPerson (Person person) { this .person = person; } @Override public String toString () { return "User{" + "like='" + like + '\'' + ", grade='" + grade + '\'' + ", person=" + person + '}' ; } }
第二步:配置XML文件
不再使用value赋值,而是使用ref指向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?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" > <bean id ="person" class ="com.hgu.Person" > <property name ="name" value ="张三" /> <property name ="sex" value ="男" /> </bean > <bean id ="user" class ="com.hgu.User" > <property name ="grade" value ="A" /> <property name ="like" value ="B" /> <property name ="person" ref ="person" /> </bean > </beans >
第三步:测试结果
1 2 3 4 5 6 7 8 9 public void test01 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" ); User user1 = (User) applicationContext.getBean("user" ); System.out.println(user1); }
内部bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?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" > <bean id ="user" class ="com.hgu.User" > <property name ="grade" value ="A" /> <property name ="like" value ="B" /> <property name ="person" > <bean id ="person" class ="com.hgu.Person" > <property name ="name" value ="张三" /> <property name ="sex" value ="男" /> </bean > </property > </bean > </beans >
级联属性赋值
主要区别还是在于XML文件,该方法可以在赋值的时候再给对应的对象属性赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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" > <bean id ="person" class ="com.hgu.Person" > <property name ="name" value ="张三" /> <property name ="sex" value ="男" /> </bean > <bean id ="user" class ="com.hgu.User" > <property name ="grade" value ="A" /> <property name ="like" value ="B" /> <property name ="person" ref ="person" /> <property name ="person.name" value ="李四" /> </bean > </beans >
实验六:为数组类型属性赋值
还是在xml文件中进行设置,使用array方式来给数组赋值
Person
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.hgu;import java.util.Arrays;public class Person { private String[] loves; public Person () { } public String[] getLoves() { return loves; } public void setLoves (String[] loves) { this .loves = loves; } @Override public String toString () { return "Person{" + "loves=" + Arrays.toString(loves) + '}' ; } }
beans.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?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" > <bean id ="person" class ="com.hgu.Person" > <property name ="loves" > <array > <value > 吃饭</value > <value > 睡觉</value > <value > 打代码</value > </array > </property > </bean > </beans >
实验七:为集合类型属性赋值
区别还是在于XML
Person
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.hgu;import java.util.Arrays;import java.util.List;public class Person { private List<User> loves; public Person () { } public List<User> getLoves () { return loves; } public void setLoves (List<User> loves) { this .loves = loves; } @Override public String toString () { return "Person{" + "loves=" + loves + '}' ; } }
XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?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" > <bean id ="person" class ="com.hgu.Person" > <property name ="loves" > <list > <ref bean ="user1" /> <ref bean ="user2" /> </list > </property > </bean > <bean id ="user1" class ="com.hgu.User" > <property name ="grade" value ="A" /> <property name ="like" value ="B" /> <property name ="person" ref ="person" /> </bean > <bean id ="user2" class ="com.hgu.User" > <property name ="grade" value ="B" /> <property name ="like" value ="A" /> <property name ="person" ref ="person" /> </bean > </beans >
Person
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.hgu;import java.util.Map;public class Person { private Map<String,User> test; public Person () { } public Map<String, User> getTest () { return test; } public void setTest (Map<String, User> test) { this .test = test; } }
XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?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" > <bean id ="person" class ="com.hgu.Person" > <property name ="test" > <map > <entry > <key > <value > 123</value > </key > <ref bean ="user1" > </ref > </entry > <entry > <key > <value > 1234</value > </key > <ref bean ="user2" > </ref > </entry > </map > </property > </bean > <bean id ="user1" class ="com.hgu.User" > <property name ="like" value ="aa" /> </bean > <bean id ="user2" class ="com.hgu.User" > <property name ="like" value ="bb" /> </bean > </beans >
测试
1 2 3 4 5 6 7 @Test public void test03 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" ); Person person = (Person) applicationContext.getBean("person" ); System.out.println(person); }
Person
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.hgu;import java.util.List;import java.util.Map;public class Person { private Map<String,User> test; private List<User> myList; public Person () { } public Map<String, User> getTest () { return test; } public void setTest (Map<String, User> test) { this .test = test; } public List<User> getMyList () { return myList; } public void setMyList (List<User> myList) { this .myList = myList; } @Override public String toString () { return "Person{" + "test=" + test + ", myList=" + myList + '}' ; } }
beansXML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <?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:util ="http://www.springframework.org/schema/util" xsi:schemaLocation =" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd" > <bean id ="person" class ="com.hgu.Person" > <property name ="myList" ref ="stringList" /> <property name ="test" ref ="userList" /> </bean > <bean id ="user1" class ="com.hgu.User" > <property name ="like" value ="aa" /> </bean > <bean id ="user2" class ="com.hgu.User" > <property name ="like" value ="bb" /> </bean > <util:list id ="stringList" > <ref bean ="user1" > </ref > <ref bean ="user2" > </ref > </util:list > <util:map id ="userList" > <entry > <key > <value > 100</value > </key > <ref bean ="user1" > </ref > </entry > <entry > <key > <value > 100</value > </key > <ref bean ="user2" > </ref > </entry > </util:map > </beans >
测试
1 2 3 4 5 6 7 @Test public void test03 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" ); Person person = (Person) applicationContext.getBean("person" ); System.out.println(person); }
实验八:p命名空间
1 2 3 4 5 6 7 8 9 10 11 12 13 <?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:util ="http://www.springframework.org/schema/util" xmlns:p ="http://www.springframework.org/schema/p" xsi:schemaLocation =" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd" ><beans > <bean id ="person" class ="com.hgu.Person" p:test-ref ="userList" p:myList-ref ="stringList" > </bean > </beans >
测试:
1 2 3 4 5 6 7 @Test public void test03 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans.xml" ); Person person = (Person) applicationContext.getBean("person" ); System.out.println(person); }
实验九:引入外部文件
步骤:
引入数据库相关依赖
创建外部文件properties格式,定义数据信息:用户名,密码,地址
创建Spring配置文件,引入Context命名空间,引入属性文件,使表达式完成注入
加入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.30</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.2.15</version > </dependency >
创建外部文件
jdbc.properties
1 2 3 4 user =root password =admin url =jdbc:mysql://localhost:3306/test?CharacterEncoding=utf8&useUnicode=true driverClass =com.mysql.cj.jdbc.Driver
创建XML文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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:jdbc.properties" /> <bean id ="user" class ="com.hgu.User" > <property name ="url" value ="${url}" /> <property name ="user" value ="${user}" /> <property name ="password" value ="${password}" /> <property name ="driverClass" value ="${driverClass}" /> </bean > </beans >
测试
1 2 3 4 5 6 7 8 @Test public void test04 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("bean-jdbc.xml" ); User user = (User) applicationContext.getBean("user" ); System.out.println(user); }
实验十:bean作用域
概念
在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:
使用sinleton的话,整个项目中只会存在这一个对象,但是使用prototype的时候,我们使用一次getBean就会创建一个对象
取值
含义
创建对象的时机
singleton(默认)
在IOC容器中,这个bean的对象始终为单实例
IOC容器初始化时
prototype
这个bean在IOC容器中有多个实例
获取bean时
如果是在WebApplicationContext环境下还会有另外几个作用域(但不常用):
取值
含义
request
在一个请求范围内有效
session
在一个会话范围内有效
创建类User
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.hgu;public class User { private String url; private String password; private String user; private String driverClass; public String getUrl () { return url; } public void setUrl (String url) { this .url = url; } public String getPassword () { return password; } public void setPassword (String password) { this .password = password; } public String getUser () { return user; } public void setUser (String user) { this .user = user; } public String getDriverClass () { return driverClass; } public void setDriverClass (String driverClass) { this .driverClass = driverClass; } @Override public String toString () { return "User{" + "url='" + url + '\'' + ", password='" + password + '\'' + ", user='" + user + '\'' + ", driverClass='" + driverClass + '\'' + '}' ; } }
测试 prototype
XML
1 2 3 4 5 6 7 8 <?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" > <bean id ="user" class ="com.hgu.User" scope ="prototype" > </bean > </beans >
JAVA
可以看出是两个不同的对象
1 2 3 4 5 6 7 8 9 10 @Test public void test05 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("bean-scope.xml" ); Object user = applicationContext.getBean("user" ); System.out.println(user); Object user1 = applicationContext.getBean("user" ); System.out.println(user1); }
测试 singleton
XML
默认是singleton
1 2 3 4 5 6 7 8 <?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" > <bean id ="user" class ="com.hgu.User" > </bean > </beans >
JAVA
1 2 3 4 5 6 7 8 9 10 @Test public void test05 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("bean-scope.xml" ); Object user = applicationContext.getBean("user" ); System.out.println(user); Object user1 = applicationContext.getBean("user" ); System.out.println(user1); }
实验十一:bean生命周期
具体的生命周期过程
创建JAVA类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.hgu.life;public class LifeBean { private String name; public LifeBean () { System.out.println("1. bean对象创建调用无参构造器" ); } public String getName () { return name; } public void setName (String name) { System.out.println("2. 给bean对象赋值调用set方法" ); this .name = name; } public void initMethod () { System.out.println("4. bean对象初始化" ); } public void destroyMethod () { System.out.println("7. bean对象销毁" ); } }
bean的后置处理器
bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行
创建bean的后置处理器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.hgu.life;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanPostProcessor;public class MyPost implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException { System.out.println("3.bean后置处理器,初始化之前执行,调用postProcessBeforeInitialization方法" ); return bean; } @Override public Object postProcessAfterInitialization (Object bean, String beanName) throws BeansException { System.out.println("5.bean后置处理器,初始化之后执行,调用postProcessAfterInitialization方法" ); return bean; } }
创建XML文件
1 2 3 4 5 6 7 8 9 10 <?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" > <bean id ="life" class ="com.hgu.life.LifeBean" init-method ="initMethod" destroy-method ="destroyMethod" > <property name ="name" value ="张三" /> </bean > <bean id ="myBeanProcessor" class ="com.hgu.life.MyPost" /> </beans >
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void test06 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("beans-life.xml" ); applicationContext.getBean("life" ); System.out.println("6. bean对象已经就绪" ); applicationContext.close(); }
实验十二:FactoryBean
简介
FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 package org.springframework.beans.factory;import org.springframework.lang.Nullable;public interface FactoryBean <T> { String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType" ; @Nullable T getObject () throws Exception; @Nullable Class<?> getObjectType(); default boolean isSingleton () { return true ; } }
创建类UserFactoryBean
1 2 3 4 5 6 7 8 9 10 11 12 package com.atguigu.spring6.bean;public class UserFactoryBean implements FactoryBean <User> { @Override public User getObject () throws Exception { return new User (); } @Override public Class<?> getObjectType() { return User.class; } }
配置bean
1 <bean id ="user" class ="com.atguigu.spring6.bean.UserFactoryBean" > </bean >
测试
虽然我们在XML文件中配置的是UserFactoryBean对象,但是我们的得到的是User对象,因为UserFactoryBean对象实现了FactoryBean对象并重写了getObject方法,所以我们通过xml文件调用的时候是返回User对象
1 2 3 4 5 6 7 @Test public void testUserFactoryBean () { ApplicationContext ac = new ClassPathXmlApplicationContext ("spring-factorybean.xml" ); User user = (User) ac.getBean("user" ); System.out.println(user); }
实验十三:基于xml自动装配
通过模拟JavaWeb中的controller Service 和 DAO之间的关系来进行xml自动转配
场景模拟
Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.hgu.web.controller;import com.hgu.web.service.UserService;public class UserController { private UserService userService; public UserService getUserService () { return userService; } public void setUserService (UserService userService) { this .userService = userService; } public void UserControllerFunction () { System.out.println("UserControllerFunction中的方法调用了" ); userService.userServiceFunction(); } }
Service.Impl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.hgu.web.service;import com.hgu.web.dao.UserDAO;public class UserServiceImpl implements UserService { private UserDAO userDAO; public UserDAO getUserDAO () { return userDAO; } public void setUserDAO (UserDAO userDAO) { this .userDAO = userDAO; } @Override public void userServiceFunction () { System.out.println("UserServiceFunction中的方法调用了" ); userDAO.userDAOFunction(); } }
service.Inter
1 2 3 4 5 package com.hgu.web.service;public interface UserService { void userServiceFunction () ; }
DAO.Impl
1 2 3 4 5 6 7 8 package com.hgu.web.dao;public class UserDAOImpl implements UserDAO { @Override public void userDAOFunction () { System.out.println("UserDAO中的方法调用了" ); } }
DAO.Inter
1 2 3 4 5 package com.hgu.web.dao;public interface UserDAO { void userDAOFunction () ; }
配置bean byType
使用bean标签的autowire属性设置自动装配效果
自动装配方式:byType
byType:根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值
若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null
若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException
1 2 3 4 5 6 7 8 <?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" > <bean id ="userController" class ="com.hgu.web.controller.UserController" autowire ="byType" > </bean > <bean id ="userService" class ="com.hgu.web.service.UserServiceImpl" autowire ="byType" > </bean > <bean id ="userDAO" class ="com.hgu.web.dao.UserDAOImpl" > </bean > </beans >
配置bean byName
自动装配方式:byName
byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值
1 2 3 4 5 6 7 8 <?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" > <bean id ="userController" class ="com.hgu.web.controller.UserController" autowire ="byName" > </bean > <bean id ="userService" class ="com.hgu.web.service.UserServiceImpl" autowire ="byName" > </bean > <bean id ="userDAO" class ="com.hgu.web.dao.UserDAOImpl" > </bean > </beans >
测试
1 2 3 4 5 6 @Test public void testAutoWireByXML () { ApplicationContext ac = new ClassPathXmlApplicationContext ("autowire-xml.xml" ); UserController userController = ac.getBean(UserController.class); userController.saveUser(); }
基于注解管理bean(重点)
从 Java 5 开始,Java 增加了对注解(Annotation) 的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。
Spring 从 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化 Spring 的 XML 配置。
Spring 通过注解实现自动装配的步骤如下:
引入依赖
开启组件扫描
使用注解定义 Bean
依赖注入
搭建子模块spring6-ioc-annotation
①搭建模块
搭建方式如:spring6-ioc-xml
②引入配置文件
引入spring-ioc-xml模块日志log4j2.xml
③添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 6.0.3</version > </dependency > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter-api</artifactId > </dependency > <dependency > <groupId > org.apache.logging.log4j</groupId > <artifactId > log4j-core</artifactId > <version > 2.19.0</version > </dependency > <dependency > <groupId > org.apache.logging.log4j</groupId > <artifactId > log4j-slf4j2-impl</artifactId > <version > 2.19.0</version > </dependency > </dependencies >
开启组件扫描
Spring 默认不使用注解装配 Bean ,因此我们需要在 Spring 的 XML 配置中,通过 < context:component-scan > 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包 (base-package 属性设置)及其子包下的所有类,如果类上使用了@Component 。
1 2 3 4 5 6 7 8 9 10 11 <?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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" > <context:component-scan base-package ="com.atguigu.spring6" > </context:component-scan > </beans >
在使用 < context:component-scan > 元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 < beans > 中添加 context 相关的约束
情况一:最基本情况扫描
1 2 <context:component-scan base-package ="com.atguigu.spring6" > </context:component-scan >
情况二:指定要排除的组件
1 2 3 4 5 6 7 8 9 10 <context:component-scan base-package ="com.atguigu.spring6" > <context:exclude-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan >
情况三:仅扫描指定组件
context:include-filter标签:指定在原有扫描规则的基础上追加的规则
此时必须设置use-default-filters=“false”,因为默认规则即扫描指定包下所有类
1 2 3 4 5 6 7 8 9 10 11 12 <context:component-scan base-package ="com.atguigu" use-default-filters ="false" > <context:include-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan >
使用注解定义 Bean
Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类 上,将它们定义成 Spring Bean 。
注解
说明
@Component
该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository
该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service
该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller
该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
属性注解一:@Autowrite注入
属性注入
源码中有两处需要注意:
场景搭建 Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.hug.web.controller;import com.hug.web.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;@Controller(value = "user") public class UserController { @Autowired private UserService userService; public void add () { System.out.println("UserController....." ); userService.add(); } }
场景搭建 Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.hug.web.service;import com.hug.web.dao.UserDAO;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class UserServiceImpl implements UserService { @Autowired private UserDAO userDAO; @Override public void add () { System.out.println("UserService...." ); userDAO.add(); } }
场景搭建 DAO
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.hug.web.dao;import org.springframework.stereotype.Repository;@Repository public class UserDAOImpl implements UserDAO { @Override public void add () { System.out.println("DAO...." ); } }
测试
1 2 3 4 5 6 7 8 9 10 11 public class TestClass { @Test public void test01 () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext ("bean-spring6.xml" ); UserController user = (UserController) applicationContext.getBean("user" ); user.add(); } }
set注入
和属性注入使用大致相同,就是将@Autowrite放在set方法上即可
Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.hug.web.controller;import com.hug.web.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;@Controller(value = "user") public class UserController { private UserService userService; public UserService getUserService () { return userService; } @Autowired public void setUserService (UserService userService) { this .userService = userService; } public void add () { System.out.println("UserController....." ); userService.add(); } }
构造方法注入
和属性注入使用大致相同,就是将@Autowrite放在构造器方法上即可
Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.hug.web.controller;import com.hug.web.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;@Controller(value = "user") public class UserController { private UserService userService; @Autowired public UserController (UserService userService) { this .userService = userService; } public void add () { System.out.println("UserController....." ); userService.add(); } }
形参上注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.hug.web.controller;import com.hug.web.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;@Controller(value = "user") public class UserController { private UserService userService; public UserController (@Autowired UserService userService) { this .userService = userService; } public void add () { System.out.println("UserController....." ); userService.add(); } }
只有一个构造函数,无注解
User
下面代码中只有一个有参构造器,那么就可以不用些注解,会自动的给你赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.hug.web.controller;import com.hug.web.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;@Controller(value = "user") public class UserController { private UserService userService; public UserController (UserService userService) { this .userService = userService; } public void add () { System.out.println("UserController....." ); userService.add(); } }
@Autowired注解和@Qualifier注解联合
当一个接口有两个实现类的时候,如果我们是用接口来进行实例化的时候会报错,因为不知道我们具体要实例化哪一个类
为解决上述方法我们通过使用@Qualifier注解来通过名称进行自动赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Controller(value = "user") public class UserController { @Autowried @Qualifier(value="userServiceImpl") private UserService userService; public UserController (UserService userService) { this .userService = userService; } public void add () { System.out.println("UserController....." ); userService.add(); } }
属性注解二:@Resource注入
和@Autowired注解的区别
@Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
@Autowired注解是Spring框架自己的。
@Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
@Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
@Resource注解用在属性上、setter方法上。
@Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。
引入依赖
1 2 3 4 5 <dependency > <groupId > jakarta.annotation</groupId > <artifactId > jakarta.annotation-api</artifactId > <version > 2.1.1</version > </dependency >
根据name注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Controller(value = "userController") public class UserController { @Resource(name = "myUserService") private UserService userService; public UserController (UserService userService) { this .userService = userService; } public void add () { System.out.println("MyUserController....." ); userService.add(); } }
根据属性名注入
1 2 3 4 5 6 7 8 9 10 @Service(value = "myUserService") public class UserServiceImpl implements UserService { @Resource private UserDAO userDAO; @Override public void add () { System.out.println("UserService...." ); userDAO.add(); } }
根据类型注入
根据类型注入,当我们既没有写name,根据属性名也找不到对应的属性,那我们通过类型获取
1 2 3 4 5 6 7 8 9 10 @Service(value = "myUserService") public class UserServiceImpl implements UserService { @Resource private UserDAO myUserDAO; @Override public void add () { System.out.println("UserService...." ); userDAO.add(); } }
总结:
@Resource注解:默认byName注入,没有指定name时把属性名当做name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个
Spring全注解开发
通过创建配置类,来省略配置文件
配置类的创建
1 2 3 4 5 6 7 8 9 10 11 12 package com.hug;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;@Configuration @ComponentScan("com.hug.resource") public class SpringConfig {}
创建测试类
通过使用AnnotationConfigApplicationContext(配置类的Class)这种方法来进行配置
1 2 3 4 5 6 @Test public void test01 () { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (SpringConfig.class); UserController user = (UserController) applicationContext.getBean("userController" ); user.add(); }
原理-手写IOC
反射
获取类名并实例化
通过类名.class获取
通过对象.getClass()方法获取
1 new Person ().getClass();
通过Class.forName(“全路径”)
1 Class.forName("com.hgu.ioc.Person" );
实例化
1 Pserson person = (Person)Class.forName("com.hgu.ioc.Person" ).getDeclaredConstructor().newInstance();
获取构造方法
getDeclaredConstructor() 和 getConstructor()的区别?
getConstructor()可以获取对应类中所有的public的构造方法
getDeclaredConstructor() 可以获取类中所有的构造方法,不管是public还是private
获取指定构造器的方法
默认是无参构造器
如果是public的方法的话
1 2 3 4 5 6 @Test public void test01 () throws Exception { Constructor<User> declaredConstructor = User.class.getDeclaredConstructor(String.class); User user1 = declaredConstructor.newInstance("张三" ); System.out.println(user1); }
如果是private
1 2 3 4 5 6 7 @Test public void test01 () throws Exception { Constructor<User> declaredConstructor = User.class.getDeclaredConstructor(String.class); declaredConstructor.setAccessible(true ); User user1 = declaredConstructor.newInstance("张三" ); System.out.println(user1); }
获取属性
通过getDeclaredFields()获取所有属性
private使用getDeclaredField
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test01 () throws Exception { Constructor<User> declaredConstructor = User.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true ); User user = declaredConstructor.newInstance(); Field name = User.class.getDeclaredField("name" ); name.setAccessible(true ); name.set(user, "张三" ); System.out.println(user); }
public使用getField()
1 2 3 4 5 6 7 8 9 10 11 @Test public void test01 () throws Exception { Constructor<User> declaredConstructor = User.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true ); User user = declaredConstructor.newInstance(); Field name = User.class.getField("name" ); name.set(user, "张三" ); System.out.println(user); }
获取方法
private使用getDeclaredMethods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void test01 () throws Exception { Constructor<User> declaredConstructor = User.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true ); User user = declaredConstructor.newInstance(); user.setName("李四" ); Method[] declaredMethods = User.class.getDeclaredMethods(); for (Method m: declaredMethods){ if (m.getName().equals("toString" )){ m.setAccessible(true ); String invoke = (String) m.invoke(user); System.out.println(invoke); } } }
public使用getMethods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void test01 () throws Exception { Constructor<User> declaredConstructor = User.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true ); User user = declaredConstructor.newInstance(); user.setName("李四" ); Method[] declaredMethods = User.class.getMethods(); for (Method m: declaredMethods){ if (m.getName().equals("toString" )){ String invoke = (String) m.invoke(user); System.out.println(invoke); } } }
实现Spring的IOC
实现过程
创建子模块
创建测试类
创建注解
@Bean创建对象
@Di 属性注入
创建Bean容器接口 ApplicationContext定义方法,返回对象
实现Bean容器接口
返回对象
根据包加载bean
such as:包com.hgu,扫描com.hug这个包,和他的子包里面的所有类,看类上面是否有@Bean注解,如果有就把这个类通过反射进行实例化
创建注解
1 2 3 4 5 6 7 8 9 10 11 12 package com.hgu.anno;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Bean {}
1 2 3 4 5 6 7 8 9 10 11 12 package com.hgu.anno;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Di {}
创建接口
1 2 3 4 5 6 package com.hgu.bean;public interface ApplicationContext { Object getBean (Class clazz) ; }
创建接口实现类,并初始化beanMap方法
实现方法
先通过我们传入的包名的参数获取这个包对应的全路径
在通过截取获取我们的根路径
全路径:/E:/Project/JavaProject/Spring/hgu-spring/target/classes/com/hgu
根路径:/E:/Project/JavaProject/Spring/hgu-spring/target/classes/
通过创建leanBean方法填充beanMap
先判断文件是否为目录
获取该文件下面的所有文件,判断一下是否含有文件
如果含有的话,遍历整个子目录
如果子目录还是目录的的话,则递归调用
如果不是目录的话,则判断该目录是否是.class结尾的文件
如果是以.class结尾的文件的话,则通过字符串切割和替换获取对应的全类名
获取该全类名的class。判断这个class是否为interface(),
如果不是interface的话,则进行类的创建
类创建完毕之后判断该类是否实现了接口
如果实现了接口则beanMap中的k是以接口的名字命名
如果没有实现的话则k是以该类的class命名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 package com.hgu.bean.impl;import com.hgu.anno.Bean;import com.hgu.bean.ApplicationContext;import java.io.File;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.net.URL;import java.net.URLDecoder;import java.util.Enumeration;import java.util.HashMap;import java.util.Map;public class AnnotationApplicationContext implements ApplicationContext { private Map<Class, Object> beanFactory = new HashMap <>(); private String rootPath; @Override public Object getBean (Class clazz) { return beanFactory.get(clazz); } public AnnotationApplicationContext (String basePackage) throws Exception { String packagePath = basePackage.replaceAll("\\." , "/" ); Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(packagePath); String decode = "" ; while (resources.hasMoreElements()){ URL url = resources.nextElement(); decode = URLDecoder.decode(url.getFile(), "utf-8" ); rootPath = decode.substring(0 ,decode.length() - packagePath.length()); } loadBean(new File (decode)); } private void loadBean (File file) throws Exception { if (file.isDirectory()){ File[] files = file.listFiles(); if (files == null || files.length == 0 ){ return ; } for (File listFile : files) { if (listFile.isDirectory()){ loadBean(listFile); }else { String realPath = listFile.getAbsolutePath().substring(rootPath.length() - 1 ); String replace="" ; if (realPath.endsWith(".class" )){ replace = realPath.replace(".class" , "" ).replaceAll("\\\\" , "\\." ); } Class<?> aClass = Class.forName(replace); if (!aClass.isInterface()){ Bean annotation = aClass.getAnnotation(Bean.class); if (annotation != null ){ Object o = aClass.getDeclaredConstructor().newInstance(); if (aClass.getInterfaces().length > 0 ){ beanFactory.put(aClass.getInterfaces()[0 ], o); }else { beanFactory.put(aClass, o); } } } } } } } public static void main (String[] args) throws Exception { ApplicationContext applicationContext = new AnnotationApplicationContext ("com.hgu" ); } }
创建注入属性
通过beanFactory遍历所有的类
通过反射获取类中的所有属性
判断该属性是否有对应的接口
如果有借口的话则进行实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void loadDi () throws IllegalAccessException { Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet(); for (Map.Entry<Class, Object> entry : entries){ Object value = entry.getValue(); Field[] declaredFields = value.getClass().getDeclaredFields(); for (Field declaredField : declaredFields) { Di annotation = declaredField.getAnnotation(Di.class); if (annotation != null ){ declaredField.setAccessible(true ); declaredField.set(value, beanFactory.get(declaredField.getType())); } } } }
面向切面:AOP
场景模拟
创建一个计算器接口
Calculator
1 2 3 4 5 6 7 8 9 10 11 package com.hgu.example;public interface Calculator { int add (int i, int j) ; int sub (int i, int j) ; int mul (int i, int j) ; int div (int i, int j) ; }
实现实现类
CalculatorImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.hgu.example.impl;import com.hgu.example.Calculator;public class CalculatorImpl implements Calculator { @Override public int add (int i, int j) { int result = i + j; System.out.println("方法内部 result = " + result); return result; } @Override public int sub (int i, int j) { int result = i - j; System.out.println("方法内部 result = " + result); return result; } @Override public int mul (int i, int j) { int result = i * j; System.out.println("方法内部 result = " + result); return result; } @Override public int div (int i, int j) { int result = i / j; System.out.println("方法内部 result = " + result); return result; } }
创建带日志的实现类
CalculatorLogImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.hgu.example.impl;import com.hgu.example.Calculator;public class CalculatorLogImpl implements Calculator { @Override public int add (int i, int j) { System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j); int result = i + j; System.out.println("方法内部 result = " + result); System.out.println("[日志] add 方法结束了,结果是:" + result); return result; } @Override public int sub (int i, int j) { System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j); int result = i - j; System.out.println("方法内部 result = " + result); System.out.println("[日志] sub 方法结束了,结果是:" + result); return result; } @Override public int mul (int i, int j) { System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j); int result = i * j; System.out.println("方法内部 result = " + result); System.out.println("[日志] mul 方法结束了,结果是:" + result); return result; } @Override public int div (int i, int j) { System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j); int result = i / j; System.out.println("方法内部 result = " + result); System.out.println("[日志] div 方法结束了,结果是:" + result); return result; } }
问题提出
针对带日志功能的实现类,我们发现有如下缺陷:
对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
附加功能分散在各个业务功能方法中,不利于统一维护
解决思路
解决这两个问题,核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来 。
困难
解决问题的困难:要抽取的代码在方法内部 ,靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引入新的技术。
代理模式
代理的概念
介绍
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接 调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来 ——解耦 。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
使用代理后:
②生活中的代理
广告商找大明星拍广告需要经过经纪人
合作伙伴找大老板谈合作要约见面时间需要经过秘书
房产中介是买卖双方的代理
③相关术语
代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
目标:被代理“套用”了非核心逻辑代码的类、对象、方法。
静态代理
我们在创建一个代理类
CalculatorStaticProxy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class CalculatorStaticProxy implements Calculator { private Calculator target; public CalculatorStaticProxy (Calculator target) { this .target = target; } @Override public int add (int i, int j) { System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j); int addResult = target.add(i, j); System.out.println("[日志] add 方法结束了,结果是:" + addResult); return addResult; } }
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
动态代理
步骤:
创建代理类
创建getProxy方法获取代理类
使用Proxy.newProxyInstance方法来获取代理类
第三步的方法需要三个参数
运行时类的类加载器
运行时类的实现的所有的接口
invocationHandler类,用来表述代理的具体实现过程
invoke方法有三个参数
proxy需要被代理对象
method需要被代理的方法
被代理方法的参数
生产代理对象的工厂类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.hgu.example;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Arrays;public class ProxyFactory { private Object target; private InvocationHandler invocationHandler; public ProxyFactory (Object target) { this .target = target; } public Object getProxy () { ClassLoader classLoader = target.getClass().getClassLoader(); Class<?>[] interfaces = target.getClass().getInterfaces(); invocationHandler = new InvocationHandler () { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { Object result = null ; try { System.out.println("[动态代理][日志]" +method.getName()+",参数:" + Arrays.toString(args)); result = method.invoke(target, args); System.out.println("[动态代理][日志]" +method.getName()+",结果:" + result); }catch (Exception e){ e.printStackTrace(); System.out.println("[动态代理][日志]" +method.getName()+",错误:" + e.getMessage()); }finally { System.out.println("[动态代理][日志]" +method.getName()+",成功,方法执行完毕:" ); } return result; } }; return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler); } }
AOP概念和相关术语
概念
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术 。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性 ,同时提高了开发的效率。
相关术语
①横切关注点
分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点。
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
②通知(增强)
增强,通俗说,就是你想要增强的功能,比如 安全,事务,日志等。
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
前置通知:在被代理的目标方法前 执行
返回通知:在被代理的目标方法成功结束 后执行(寿终正寝 )
异常通知:在被代理的目标方法异常结束 后执行(死于非命 )
后置通知:在被代理的目标方法最终结束 后执行(盖棺定论 )
环绕通知:使用try…catch…finally结构围绕整个 被代理的目标方法,包括上面四种通知对应的所有位置
③切面
封装通知方法的类。
④目标
被代理的目标对象。
⑤代理
向目标对象应用通知之后创建的代理对象。
⑥连接点
这也是一个纯逻辑概念,不是语法定义的。
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。通俗说,就是spring允许你使用通知的地方
⑦切入点
定位连接点的方式。
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。
如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。
Spring 的 AOP 技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法
切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
作用
简化代码:把方法中固定位置的重复的代码抽取 出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用 了切面逻辑的方法就被切面给增强了。
基于注解的AOP
动态代理分为JDK动态代理和cglib动态代理
当目标类有接口的情况使用JDK动态代理和cglib动态代理 ,没有接口时只能使用cglib动态代理 .
JDK动态代理动态生成的代理类会在com.sun.proxy包下 ,类名为$proxy1,和目标类实现相同的接口
cglib动态代理动态生成的代理类会和目标在在相同的包下 ,会继承 目标类
动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口 (兄弟两个拜把子模式)。
cglib:通过继承被代理的目标类 (认干爹模式)实现代理,所以不需要目标类实现接口。
AspectJ:是AOP思想的一种实现 。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件 ,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解,spring使用Aspectj中的注解实现AOP 。
准备工作
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aop</artifactId > <version > 6.0.2</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 6.0.2</version > </dependency >
各种通知
前置通知:使用@Before注解标识,在被代理的目标方法之前执行 .
返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行 .
异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行 .
后置通知:使用@After注解标识,在被代理的目标方法最终执行 .
环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个 被代理的目标方法,包括上面四种通知对应的所有位置
各种通知的执行顺序:
Spring版本5.3.x以前:
Spring版本5.3.x以后:
切入点表达式语法
用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
在包名的部分,使用“*…”表示包名任意、包的层次深度任意
在类名的部分,类名部分整体用*号代替,表示类名任意
在类名的部分,可以使用*号代替类名的一部分
例如:*Service匹配所有名称以Service结尾的类或接口 .
在方法名部分,可以使用*号表示方法名任意
在方法名部分,可以使用*号代替方法名的一部分
例如:*Operation匹配所有方法名以Operation结尾的方法 .
在方法参数列表部分,使用(..)表示参数列表任意 .
在方法参数列表部分,使用(int
在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符 .
例如:execution(public int …Service. (…, int)) 正确
例如:execution( int *…Service. (…, int)) 错误
创建切面类并配置
XML配置
1 2 3 4 5 6 7 8 9 10 <?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/c" 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.hgu.annotation" > </context:component-scan > <aop:aspectj-autoproxy > </aop:aspectj-autoproxy > </beans >
切面类的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 package com.hgu.annotation;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.stereotype.Component;import java.util.Arrays;@Aspect @Component public class LogAspect { @Before(value = "execution(* com.hgu.annotation.impl.CalculatorImpl.*(..))") public void beforeMethod (JoinPoint joinPoint) { String name = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println("前置方法..., 方法名称:" +name + "方法参数: " + Arrays.toString(args)); } @After(value = "execution(* com.hgu.annotation.impl.CalculatorImpl.*(..))) ") public void afterMethod(JoinPoint joinPoint){ String name = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println(" 后置方法..., 方法名称:"+name + " 方法参数: "+Arrays.toString(args)); } /** * 返回通知,我们的方法如果有返回值的话,这个方法可以获取返回值 * 需要在注解中添加一个returning = “返回结果的名称” * 在方法中添加Object 返回结果名称 * 这两个返回结果名称一定要对应起来 * @param joinPoint */ @AfterReturning(value = " execution(* com.hgu.annotation.impl.CalculatorImpl.*(..))", returning = " result") public void afterReturningMethod(JoinPoint joinPoint, Object result){ String name = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println(" 后置返回方法..., 方法名称:"+name + " 方法参数: "+Arrays.toString(args) + " , 返回结果 :" + result); } /** * 错误方法,当执行的方法错误的时候才会调用这个方法,否则不会调用 * 获取返回值信息,需要在注解中添加一个throwing的参数 * 在方法中添加一个和注解中同名的参数,类型为Throwable * @param joinPoint * @param error */ @AfterThrowing(value = " execution(* com.hgu.annotation.impl.CalculatorImpl.*(..)))", throwing = " error") public void afterThrowingMethod(JoinPoint joinPoint, Throwable error){ String name = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println(" 异常通知....方法名称:"+name + " 方法参数: "+Arrays.toString(args) + " , 异常通知: "+error); } /** * 环绕通知 * @param proceedingJoinPoint * @return */ @Around(value = " execution(* com.hgu.annotation.impl.CalculatorImpl.*(..)))") public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){ String name = proceedingJoinPoint.getSignature().getName(); Object[] args = proceedingJoinPoint.getArgs(); Object result = null; try{ System.out.println(" 环绕通知....在方法执行前执行"); // 执行方法 result = proceedingJoinPoint.proceed(); System.out.println(" 环绕通知.....在方法返回值之后执行"); }catch (Throwable e) { e.printStackTrace(); System.out.println(" 环绕通知....在方法异常的时候执行"); } finally { System.out.println(" 环绕通知....在方法执行完之后执行"); } return result; } }
重用切入点表达式
①声明
1 2 @Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))") public void pointCut () {}
②在同一个切面中使用
1 2 3 4 5 6 @Before("pointCut()") public void beforeMethod (JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); System.out.println("Logger-->前置通知,方法名:" +methodName+",参数:" +args); }
③在不同切面中使用
1 2 3 4 5 6 @Before("com.atguigu.aop.CommonPointCut.pointCut()") public void beforeMethod (JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); System.out.println("Logger-->前置通知,方法名:" +methodName+",参数:" +args); }
获取通知的相关信息
①获取连接点信息
获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参
1 2 3 4 5 6 7 8 @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))") public void beforeMethod (JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); System.out.println("Logger-->前置通知,方法名:" +methodName+",参数:" +args); }
②获取目标方法的返回值
@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值
1 2 3 4 5 @AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result") public void afterReturningMethod (JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->返回通知,方法名:" +methodName+",结果:" +result); }
③获取目标方法的异常
@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常
1 2 3 4 5 @AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex") public void afterThrowingMethod (JoinPoint joinPoint, Throwable ex) { String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->异常通知,方法名:" +methodName+",异常:" +ex); }
环绕通知
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))") public Object aroundMethod (ProceedingJoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); Object result = null ; try { System.out.println("环绕通知-->目标对象方法执行之前" ); result = joinPoint.proceed(); System.out.println("环绕通知-->目标对象方法返回值之后" ); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("环绕通知-->目标对象方法出现异常时" ); } finally { System.out.println("环绕通知-->目标对象方法执行完毕" ); } return result; }
切面的优先级
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套 顺序。
使用@Order注解可以控制切面的优先级:
@Order(较小的数):优先级高
@Order(较大的数):优先级低
基于XML文件的AOP
配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?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.hgu.aopxml" > </context:component-scan > <aop:config > <aop:aspect ref ="logAspect" > <aop:pointcut id ="pointcut" expression ="execution(* com.hgu.aopxml.impl.CalculatorImpl.*(..)))" /> <aop:before method ="beforeMethod" pointcut-ref ="pointcut" > </aop:before > <aop:after method ="afterMethod" pointcut-ref ="pointcut" > </aop:after > <aop:after-returning method ="afterReturningMethod" returning ="result" pointcut-ref ="pointcut" > </aop:after-returning > <aop:around method ="aroundMethod" pointcut-ref ="pointcut" > </aop:around > <aop:after-throwing method ="afterThrowingMethod" throwing ="error" pointcut-ref ="pointcut" > </aop:after-throwing > </aop:aspect > </aop:config > </beans >
单元测试:JUnit
添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-test</artifactId > <version > 6.0.2</version > </dependency > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter-api</artifactId > <version > 5.9.0</version > </dependency >
添加配置
1 2 3 4 5 6 7 8 9 10 11 12 13 <?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.hgu.junit" /> </beans >
创建准备类
User
1 2 3 4 5 6 7 8 9 10 package com.hgu.junit.junit5;import org.springframework.stereotype.Component;@Component public class User { public void run () { System.out.println("User run...." ); } }
Junit5测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.hgu.junit.junit5;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;@SpringJUnitConfig(locations = "classpath:aopxml.xml") public class Junit5Test { @Autowired private User user; @Test public void test01 () { System.out.println(user); user.run(); } }
Junit4测试
1 2 3 4 5 6 7 8 9 10 11 12 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:beans.xml") public class JUnit4Test { @Autowired private User user; @Test public void testUser () { System.out.println(user); } }
事务
JdbcTemplate
准备
引入配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 6.0.2</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.30</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.2.15</version > </dependency > </dependencies >
创建配置文件
jdbc.properties
1 2 3 4 jdbc.user =root jdbc.password =root jdbc.url =jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false jdbc.driver =com.mysql.cj.jdbc.Driver
配置Spring配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?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:jdbc.properties" /> <bean id ="druidDataSource" class ="com.alibaba.druid.pool.DruidDataSource" > <property name ="username" value ="${jdbc.username}" /> <property name ="password" value ="${jdbc.password}" /> <property name ="url" value ="${jdbc.url}" /> <property name ="driver" value ="${jdbc.driver}" /> </bean > <bean id ="jdbcTemplate" class ="org.springframework.jdbc.core.JdbcTemplate" > <property name ="dataSource" ref ="druidDataSource" /> </bean > </beans >
测试增删改查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 @SpringJUnitConfig(locations = "classpath:beans.xml") public class JdbcTemplateTest { @Autowired private JdbcTemplate jdbcTemplate; @Test public void test01 () { String sql = "INSERT INTO t_emp VALUES(null,?,?,?)" ; int update = jdbcTemplate.update(sql, "狗男" , 18 , "未知" ); System.out.println(update); } @Test public void test02 () { String sql = "DELETE FROM t_emp WHERE id=?" ; int update = jdbcTemplate.update(sql, 1 ); System.out.println(update); } @Test public void test03 () { String sql = "UPDATE t_emp SET name=? WHERE id=?" ; int update = jdbcTemplate.update(sql, "狗策" , 2 ); System.out.println(update); } @Test public void test04 () { String sql = "SELECT * FROM t_emp WHERE id = ?" ; Emp emp1 = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { Emp emp = new Emp (); emp.setId(rs.getInt("id" )); emp.setName(rs.getString("name" )); emp.setAge(rs.getInt("age" )); emp.setSex(rs.getString("sex" )); return emp; }, 2 ); System.out.println(emp1); } @Test public void test05 () { String sql = "SELECT * FROM t_emp WHERE id = ?" ; Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper <>(Emp.class), 2 ); System.out.println(emp); } @Test public void test06 () { String sql = "SELECT * FROM t_emp" ; List<Emp> emps = jdbcTemplate.query(sql, new BeanPropertyRowMapper <>(Emp.class)); for (Emp emp : emps){ System.out.println(emp); } } @Test public void test07 () { String sql = "SELECT COUNT(*) FROM t_emp" ; Integer integer = jdbcTemplate.queryForObject(sql, Integer.class); System.out.println(integer); } }
事务概念
事务的基本概念
什么是事务
数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
事务的特性
A:原子性(Atomicity)
一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
C:一致性(Consistency)
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。
如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。
如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
I:隔离性(Isolation)
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
D:持久性(Durability)
指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
编程式事务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Connection conn = ...; try { conn.setAutoCommit(false ); conn.commit(); }catch (Exception e){ conn.rollBack(); }finally { conn.close(); }
编程式的实现方式存在缺陷:
细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
声明式事务
既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。
封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
好处1:提高开发效率
好处2:消除了冗余的代码
好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化
所以,我们可以总结下面两个概念:
编程式 :自己写代码 实现功能
声明式 :通过配置 让框架 实现功能
基于注解的声明式事务
准备
开启组件扫描
1 <context:component-scan base-package ="com.hug.tx" />
创建的对应的表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 CREATE TABLE `t_book` ( `book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `book_name` varchar(20) DEFAULT NULL COMMENT '图书名称', `price` int(11) DEFAULT NULL COMMENT '价格', `stock` int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)', PRIMARY KEY (`book_id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; insert into `t_book`(`book_id`,`book_name`,`price`,`stock`) values (1,'斗破苍穹',80,100),(2,'斗罗大陆',50,100); CREATE TABLE `t_user` ( `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(20) DEFAULT NULL COMMENT '用户名', `balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)', PRIMARY KEY (`user_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; insert into `t_user`(`user_id`,`username`,`balance`) values (1,'admin',50);
创建BookDAO BookDAOImpl BookService BookServiceImpl BookController
无事务情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class BookServiceImpl implements BookService { @Autowired private BookDAO bookDAO; @Override public void buyBook (Integer bookId, Integer userId) { bookDAO.updateStock(bookId); Double bookPrice = bookDAO.getBookPriceByID(bookId); bookDAO.updateBalance(userId, bookPrice); } }
添加事务
引入配置
1 2 3 4 5 6 7 8 9 10 <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="druidDataSource" > </property > </bean > <tx:annotation-driven transaction-manager ="transactionManager" />
添加注解
@Transactional标识在方法上,则只会影响该方法
@Transactional标识的类上,则会影响类中所有的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Service public class BookServiceImpl implements BookService { @Autowired private BookDAO bookDAO; @Transactional @Override public void buyBook (Integer bookId, Integer userId) { bookDAO.updateStock(bookId); Double bookPrice = bookDAO.getBookPriceByID(bookId); bookDAO.updateBalance(userId, bookPrice); } }
Transaction配置参数
介绍
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作 。这样数据库就能够针对查询操作来进行优化。
代码
1 2 3 4 5 6 7 8 9 10 @Transactional(readOnly = true) public void buyBook (Integer bookId, Integer userId) { Integer price = bookDao.getPriceByBookId(bookId); bookDao.updateStock(bookId); bookDao.updateBalance(userId, price); }
注意
对增删改操作设置只读会抛出下面异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
介绍
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源 。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。
概括来说就是一句话:超时回滚,释放资源。
使用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Transactional(timeout = 3) public void buyBook (Integer bookId, Integer userId) { try { TimeUnit.SECONDS.sleep(5 ); } catch (InterruptedException e) { e.printStackTrace(); } Integer price = bookDao.getPriceByBookId(bookId); bookDao.updateStock(bookId); bookDao.updateBalance(userId, price); }
观察结果
执行过程中抛出异常:
org.springframework.transaction.TransactionTimedOutException : Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022
介绍
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。
可以通过@Transactional中相关属性设置回滚策略
rollbackFor属性:需要设置一个Class类型的对象
rollbackForClassName属性:需要设置一个字符串类型的全类名
noRollbackFor属性:需要设置一个Class类型的对象
rollbackFor属性:需要设置一个字符串类型的全类名
使用方式
1 2 3 4 5 6 7 8 9 10 11 @Transactional(noRollbackFor = ArithmeticException.class) public void buyBook (Integer bookId, Integer userId) { Integer price = bookDao.getPriceByBookId(bookId); bookDao.updateStock(bookId); bookDao.updateBalance(userId, price); System.out.println(1 /0 ); }
观察结果
虽然购买图书功能中出现了数学运算异常(ArithmeticException),但是我们设置的回滚策略是,当出现ArithmeticException不发生回滚,因此购买图书的操作正常执行
隔离级别
读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。
读已提交:READ COMMITTED、
要求Transaction01只能读取Transaction02已提交的修改。
可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
各个隔离级别解决并发问题的能力见下表:
隔离级别
脏读
不可重复读
幻读
READ UNCOMMITTED
有
有
有
READ COMMITTED
无
有
有
REPEATABLE READ
无
无
有
SERIALIZABLE
无
无
无
各种数据库产品对事务隔离级别的支持程度:
隔离级别
Oracle
MySQL
READ UNCOMMITTED
×
√
READ COMMITTED
√(默认)
√
REPEATABLE READ
×
√(默认)
SERIALIZABLE
√
√
②使用方式
1 2 3 4 5 @Transactional(isolation = Isolation.DEFAULT) @Transactional(isolation = Isolation.READ_UNCOMMITTED) @Transactional(isolation = Isolation.READ_COMMITTED) @Transactional(isolation = Isolation.REPEATABLE_READ) @Transactional(isolation = Isolation.SERIALIZABLE)
事务属性:传播行为
什么是传播行为:有两个事务方法相互调用的时候,各自的事务是如何传播的,是合并到一个事务中,还是从新开启一个事务
一共有七种传播行为:
REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行**【有就加入,没有就不管了】**
MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常**【有就加入,没有就抛异常】**
REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起**【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】**
NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务**【不支持事务,存在就挂起】**
NEVER:以非事务方式运行,如果有事务存在,抛出异常**【不支持事务,存在就抛异常】**
NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】
测试
方法一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Transactional(propagation = Propagation.REQUIRED) @Service public class BookServiceImpl implements BookService { @Autowired private BookDAO bookDAO; @Override public void buyBook (Integer bookId, Integer userId) { bookDAO.updateStock(bookId); System.out.println(1 /0 ); Double bookPrice = bookDAO.getBookPriceByID(bookId); bookDAO.updateBalance(userId, bookPrice); } }
方法二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @SpringJUnitConfig(locations = "classpath:beans.xml") public class TestTx { @Autowired private BookController bookController; @Test @Transactional public void testBuyBook () { for (int i = 0 ; i < 2 ; i++){ bookController.buyBook(1 , 1 ); } } }
方法一和方法二都是事务方法,在方法二中调用方法一,因为方法一采用的是REQUIRED,方法二没有写,则默认也是REQUIRED有就加入没有就新建,方法二没有事务,则新建,在方法二中调用方法一,因为方法二中有事务,所以方法一会继续使用方法二中的事务,方法一中出错的话,则方法一和方法二都会回滚
全注解实现事务
创建配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Configuration @ComponentScan("com.hug.tx") @EnableTransactionManagement public class SpringConfig { @Bean public DataSource getDruidDataSource () { DruidDataSource dataSource = new DruidDataSource (); dataSource.setUsername("root" ); dataSource.setPassword("admin" ); dataSource.setUrl("jdbc:mysql://localhost:3306/spring?CharacterEncoding=utf8&useUnicode=true&serverTimezone=UTC" ); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver" ); return dataSource; } @Bean public JdbcTemplate getJdbcTemplate (DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate (); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } @Bean public DataSourceTransactionManager getDataSourceTransactionManager (DataSource dataSource) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager (); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } }
测试:
1 2 3 4 5 6 7 8 9 10 public class TestTx { @Test @Transactional public void testBuyBook () { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext (SpringConfig.class); BookController controller = (BookController) annotationConfigApplicationContext.getBean("bookController" ); controller.buyBook(1 ,1 ); } }
基于XML的声明式事务