简介
- Spring:春天–>给软件行业带来了春天!
- 2002,首次推出了Spring框架的雏形:interface21框架!
- Spring框架即以interface21框架为基础,经过重新设计,并不断丰富内涵,于2004年3月24日,发布了1.0正式版。
- Rod Johnson,Spring Framework创始人,著名作者。很难想象其学历,真的让好多人大吃一惊,他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
- spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架。
- SSH:Struct2+Spring+Hibernate!
- SSM:SpringMVC+Spring+Mybatis!
- 官方文档|官网|官方下载地址|Github
Maven仓库:导入webmvc包会自动导入相关依赖;jdbc用于和Mybatis整合。
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
优点
-
Spring是一个开源的免费的框架(容器)!
-
Spring是一个轻量级的、非入侵式的框架!
-
控制反转(IOC)、面向切面编程(AOP)!
-
支持事务的处理,对框架整合的支持!
总结一句话:Spring就是一个轻量级的控制反转(IOC)和面向切面编程的框架!
组成
拓展
在Spring的官网有这个介绍:现代化的java开发!说白了就是基于Spring的开发!
- Spring Boot
- 一个快速开发的脚手架。
- 基于Spring Boot可以快速的开发单个微服务。
- 约定大于配置!
- Spring Cloud
- SpringCloud是基于SpringBoot实现的。
因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring以及SpringMVC!承上启下的作用。
弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱”。
IOC
IOC理论推导
-
UserDao接口
-
UserDaoImpl实现类
-
UserService业务接口
-
UserServiceImpl业务实现类
在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改源代码!如果程序代码量十分大,修改一次的成本代价十分昂贵!
我们使用一个Set接口实现,已经发生了革命性的变化!
private UserDao userDao;
//利用set进行动态实现值的注入!
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
- 之前,程序是主动创建对象!控制权在程序员手上!
- 使用了set注入后,程序不再具有主动性,而是变成了被动的接收对象!
这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了。系统的耦合性大大降低,可以更加专注在业务的实现上。这是IOC的原型!
IOC本质
控制反转IOC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IOC的一种方法,也有人认为DI是IoC的另一种说法。没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方式是依赖注入(Dependency Injection,DI)。
HelloSpring
步骤
- 导入依赖。Spring项目,需要导入的依赖:(导入webmvc,其他相关的也会被导入)
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
- 在resources文件夹下,创建beans.xml配置文件,采用XML方式配置Bean:(官方建议配置文件起名为“applicationContext.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
- 添加bean对象:(beans.xml中)
<!--使用Spring来创建对象,在Spring这些都称之为Bean-->
<bean id="hello" class="com.qsdbl.pojo.Hello">
<property name="str" value="hello world!"/>
</bean>
<!--
bean标签,添加bean到该配置文件(Spring容器)。
id,给bean起的名字,在获取bean时使用的名字(见下边的测试)。class,bean的全限定名。
property标签,设置某个属性的值。
name设置属性名,设置属性值可以使用ref或value,区别如下:
ref:引用Spring容器中已经创建好的对象(引用数据类型)
value:具体的值,基本数据类型
-->
上边用到的实体类Hello,java代码如下:
package com.qsdbl.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//使用Lombok注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hello {
private String str;
}
- 测试:
@Test
public void test() {
//获取Spring的上下文对象(解析beans.xml文件,生成相应的Bean对象)
//可以一次加载多个XML文件,使用逗号隔开
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象现在都在Spring中管理了,我们要使用,直接去里面取出来就可以(getBean:参数即为spring配置文件中bean的id)
Hello hello = (Hello)context.getBean("hello");
System.out.println(hello.getStr());
}
思考问题?
-
Hello对象是谁创建的?hello对象是由Spring创建的。
-
Hello对象的属性是怎么设置的?hello对象的属性是由Spring容器设置的。
这个过程就叫做控制反转:
控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。
反转:程序本身不创建对象,而变成被动的接收对象。
依赖注入:就是利用set方法来进行注入。
IoC是一种编程思想,由主动的编程变成被动的接收。
可以通过new ClassPathXmlApplicationContext
去浏览一下底层源码。
OK,到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IoC,一句话搞定:对象由Spring来创建,管理,装配!
IOC创建对象的方式
无参构造器
Spring容器,默认使用无参构造器创建对象
package com.qsdbl.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
public class Hello {
private String str;
public Hello() {
System.out.println("使用无参构造器,创建对象");
}
public Hello(String str) {
this.str = str;
System.out.println("使用有参构造器,创建对象");
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
测试:(beans.xml没有修改,跟上边一样)
@Test
public void test() {
// 获取Spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 我们的对象现在都在Spring中管理了,我们要使用,直接去里面取出来就可以
Hello hello = (Hello)context.getBean("hello");
}
运行结果:
使用无参构造器,创建对象
Process finished with exit code 0
有参构造器
在beans.xml文件中,指定使用有参构造器创建对象。
使用有参构造器,有三种方式赋值:(在constructor-arg
标签中设置)
-
index
index指(有参构造器的)参数下标 , 下标从0开始。(可以写多个参数,若构造器可以接收多个参数)
//beans.xml文件中,添加”constructor-arg“标签(指定有参构造器) <bean id="hello" class="com.qsdbl.pojo.Hello"> <constructor-arg index="0" value="hello world!"/> </bean> //测试结果:(其他条件不变,只是修改beans.xml文件) 使用有参构造器,创建对象 Process finished with exit code 0
-
type
type根据参数类型设置。(不建议使用)
//修改beans.xml文件 <bean id="hello" class="com.qsdbl.pojo.Hello"> <constructor-arg type="java.lang.String" value="hello world!"/> </bean> //测试结果:(其他条件不变,只是修改beans.xml文件) 使用有参构造器,创建对象 Process finished with exit code 0
-
name
根据参数名称来设置。
//修改beans.xml文件(见上边实体类Hello的定义,有参构造器的参数名称为str) <bean id="hello" class="com.qsdbl.pojo.Hello"> <constructor-arg name="str" value="hello world"/> </bean> //测试结果:(其他条件不变,只是修改beans.xml文件) 使用有参构造器,创建对象 Process finished with exit code 0
小结
- 在配置文件加载的时候(
new ClassPathXmlApplicationContext("beans.xml");
),容器中管理的对象就已经初始化(创建)了。
Spring配置
前边的beans.xml文件中进行配置。
别名
- 使用alias标签起别名
- name,对应bean标签的id。
- alias,我们给bean设置的别名。
<bean id="hello" class="com.qsdbl.pojo.Hello">
<constructor-arg name="str" value="hello world"/>
</bean>
<!--如果添加了别名,我们可以通过别名获取到这个对象-->
<alias name="hello" alias="myhello"/>
//使用(例子见上边”HelloSpring“的“测试”步骤):
Hello hello = (Hello)context.getBean("hello");
等价于:
Hello hello = (Hello)context.getBean("myhello");
bean配置
-
bean就是java对象,由Spring创建和管理。使用bean标签进行配置(注册bean到Spring中)
-
id是bean的唯一标识符,如果没有配置id,name就是默认标识符
- 如果配置了id,又配置了name,那么name是别名。name可以设置多个别名,可以用逗号,分号,空格隔开
- 如果不配置id和name,可以根据
applicationContext.getBean(xxx.class)
获取对象;
-
class是bean对象所对应的类型的全限定名=包名+类名
<bean id="hello" name="helloworld h1,h2;h3" class="com.qsdbl.pojo.Hello">
</bean>
import
这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个。
假设,现在项目中有多个人开发,这三个人负责不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的!
- 张三
- 李四
- 王五
- applicationContext.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">
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
<!--都放在同一目录下,例如resources文件夹-->
</beans>
使用的时候,直接使用总的配置就可以了。
依赖注入
我的对象的『依赖』是注入进来的,而和它的构造方式解耦了。构造它这个『控制』操作也交给了第三方,也就是控制反转。
个人理解:依赖可以理解为类各属性的具体值,现在创建对象不是提前将各属性值写死在程序中(new的方式)而是交给框架,调用者通过调用框架将”依赖“注入再创建。对类的属性进行赋值(依赖注入),可以通过构造器或setter方法。
构造器注入
前边已经介绍过。(IOC创建对象的方式)
Set方式注入【重点】
- 依赖注入:Set注入!
- 依赖:bean对象的创建依赖于容器!
- 注入:bean对象中的所有属性,由容器来注入!
环境搭建
-
引用数据类型(Lombok的使用,可以参考这篇博客)
package com.qsdbl.pojo; import lombok.Data; @Data public class Address { private String address; }
-
测试对象Student
package com.qsdbl.pojo; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import lombok.Data; @Data public class Student { private String name;//基本数据类型,XML文件中 value-实现注入 private Address address;//引用数据类型,XML文件中 ref-实现注入 private String[] books; private List<String> hobbys; private Map<String,String> card; private Set<String> games; private String wife; private Properties info; }
-
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"> <bean id="address" class="com.qsdbl.pojo.Address"> </bean> <bean id="student" class="com.qsdbl.pojo.Student"> <!-- 第一种,普通值注入,value--> <property name="name" value="轻率的保罗"/> <property name="address" ref="address"/> </bean> </beans>
-
测试Student类
import com.qsdbl.pojo.Student; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = (Student)context.getBean("student"); //Student student = context.getBean("student", Student.class); System.out.println(student.toString()); } }
注入方式
下边展示了各种数据类型的注入方式,有:基本数据类型、引用数据类型(自定义的Bean、数组、List集合、map键值对、set集合、null、property(类似map))
详细介绍见官方文档。
<bean id="address" class="com.qsdbl.pojo.Address">
<property name="address" value="北京市"/>
</bean>
<bean id="student" class="com.qsdbl.pojo.Student">
<!-- 第一种,普通值注入,value-->
<property name="name" value="轻率的保罗"/>
<!-- 第二种,Bean(引用数据类型)注入,ref-->
<property name="address" ref="address"/>
<!-- 数组注入-->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
</array>
</property>
<!-- List注入-->
<property name="hobbys">
<list>
<value>听歌</value>
<value>看电影</value>
</list>
</property>
<!-- map注入-->
<property name="card">
<map>
<entry key="身份证" value="62341234123"/>
<entry key="银行卡" value="23452352345"/>
</map>
</property>
<!-- set注入-->
<property name="games">
<set>
<value>和平精英</value>
<value>王者荣耀</value>
</set>
</property>
<!-- null-->
<property name="wife">
<null/>
</property>
<!-- property-->
<property name="info">
<props>
<prop key="学号">2019252343</prop>
<prop key="性别">男</prop>
</props>
</property>
</bean>
测试结果:(数据均成功注入到对象中)
Student(name=轻率的保罗, address=Address(address=北京市), books=[红楼梦, 西游记], hobbys=[听歌, 看电影], card={身份证=62341234123, 银行卡=23452352345}, games=[和平精英, 王者荣耀], wife=null, info={学号=2019252343, 性别=男})
Process finished with exit code 0
注入-拓展
可以使用p命名空间(属性)和c命名空间(构造器)进行注入(可以理解为一种便捷方式)。详细介绍见官方文档。
演示:(Spring配置文件中)
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--p命名空间注入=Set注入,p=>properties-->
<bean id="user" class="com.tian.pojo.User" p:name="test" p:age="20"/>
<!--c命名空间注入=构造器注入,c=>constructor-arg-->
<bean id="user2" class="com.tian.pojo.User" c:name="test1" c:age="22"/>
</beans>
注意点:使用p命名空间和c命名空间需要导入xml约束!(头部)
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
Bean
Spring Bean是被实例的,组装的及被Spring 容器管理的Java对象。
Spring 容器会自动完成@bean对象的实例化。
创建应用对象之间的协作关系的行为称为:装配(wiring),这就是依赖注入的本质。
Bean配置
Spring配置文件中,使用bean标签,配置bean。
Bean作用域
详细介绍,见官方文档。
Scope | Description |
---|---|
singleton | (Default) Scopes a single bean definition to a single object instance for each Spring IoC container. |
prototype | Scopes a single bean definition to any number of object instances. |
request | Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext . |
session | Scopes a single bean definition to the lifecycle of an HTTP Session . Only valid in the context of a web-aware Spring ApplicationContext . |
application | Scopes a single bean definition to the lifecycle of a ServletContext . Only valid in the context of a web-aware Spring ApplicationContext . |
websocket | Scopes a single bean definition to the lifecycle of a WebSocket . Only valid in the context of a web-aware Spring ApplicationContext . |
-
单例模式(Spring默认机制)
一般单线程下使用。
<!--默认就是单例模式--> <bean id="address" class="com.qsdbl.pojo.Address"> <property name="address" value="北京市"/> </bean> <!--显式设置为单例模式(singleton)--> <bean id="address" class="com.qsdbl.pojo.Address" scope="singleton"> <property name="address" value="北京市"/> </bean>
-
原型模式:每次从容器中get的时候,都会产生一个新对象!
一般多线程下使用。
<bean id="address" class="com.qsdbl.pojo.Address" scope="prototype"> <property name="address" value="北京市"/> </bean>
-
其余的request、session、application、websocket这些只能在web开放中使用!
自动装配Bean
依赖注入的本质就是装配,装配是依赖注入的具体行为。
《spring实战》中给装配下了一个定义:创建应用对象之间协作关系的行为称为装配。也就是说当一个对象的属性是另一个对象时,实例化时,需要为这个对象属性进行实例化。这就是装配。
自动装配是为了将依赖注入“自动化”的一个简化配置的操作。
- 手动装配,在前边依赖注入中也有涉及,在Spring配置文件中配置bean时手动设置各个属性的值(这一步就是set方式的依赖注入,不过自动装配一般都是null),现在通过自动装配不需要在Spring配置文件中手动设置。(自动装配允许null,所以使用自动装配一般都是null,而手动装配可以设置各种值)
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文自动查找,并自动给bean装配属性
在Spring中有三种装配方式:
- 在xml中显示配置
- 在Java中显示配置
- 隐式的自动装配bean【重要】
测试
手动装配
手动装配:通过property标签,手动设置属性address的值。
<?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="address" class="com.qsdbl.pojo.Address" scope="prototype">
<property name="address" value="北京市"/>
</bean>
<bean id="student" class="com.qsdbl.pojo.Student">
<!-- 属性address,为引用数据类型-->
<property name="address" ref="address"/>
</bean>
</beans>
测试:
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean("student", Student.class);
System.out.println(student.getAddress());
}
测试结果:(Spring配置文件中的第7行,Address对象设置了默认值“北京市”)
Address(address=北京市)
Process finished with exit code 0
自动装配
byName:会自动在容器上下文中查找,和自己对象set方法后面的值(set方法名后边部分,小写开头)对应的bean的id!
例如:address属性,set方法名为setAddress,byName自动装配查找的bean id为address(小写开头)
<bean id="address" class="com.qsdbl.pojo.Address" scope="prototype">
<property name="address" value="北京市"/>
</bean>
<bean id="student" class="com.qsdbl.pojo.Student" autowire="byName">
<!--属性address,为引用数据类型-->
<!--<property name="address" ref="address"/>-->
<!--属性address,在Spring配置文件中(该文件第6行)存在同名的bean id,可以通过byName方式正常自动装配-->
byType:需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
<bean id="address" class="com.qsdbl.pojo.Address" scope="prototype">
<property name="address" value="北京市"/>
</bean>
<bean id="student" class="com.qsdbl.pojo.Student" autowire="byType">
<!--属性address,为引用数据类型-->
<!--<property name="address" ref="address"/>-->
<!--属性address,类型为Address,在Spring配置文件中(该文件第6行)存在该类型的bean,可以通过byType方式正常自动装配-->
</bean>
<!--拓展:-->
<!--因为是根据类型查找对应的bean,所以上边的Address类的bean配置去掉id,下边的Student类也可以正常自动装配-->
<bean class="com.qsdbl.pojo.Address" scope="prototype">
<property name="address" value="北京市"/>
</bean>
小结
- byName:需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致!
- byType:需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
注解实现自动装配
此处了解即可,示例见下边的使用注解开发。
jdk1.5支持注解,Spring2.5就支持注解了!
The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML. 基于注解的配置的引入引发了这样一个问题:这种方法是否比XML“更好”。
步骤
使用注解须知:
1.导入约束:context约束
<!--beans标签中添加:-->
xmlns:context="http://www.springframework.org/schema/context"
<!--beans标签的”xsi:schemaLocation“中添加:-->
"http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
2.配置注解的支持:<context:annotation-config/>
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
@Autowired
直接在属性上使用即可!也可以在set方法上使用!
使用Autowired我们可以不用编写Set方法了,前提是你这个自动装配的属性在IOC(Spring)容器中存在(属性的类型,在Spring配置文件中已经进行了bean配置),Autowired默认使用byType方式进行自动装配。
测试代码:
public class Person {
//如果显式定义了Autowired的required属性为false,说明这个对象可以为null,否则不允许为空
@Autowired(required = false)
private Cat cat;
@Autowired
private Dog dog;
private String name;
}
public @interface Autowired {
boolean required() default true;
}
@Nullable
拓展:
public People(@Nullable String name){
//@Nullable 字段标记了这个注解,说明这个字段可以为null
this.name = name;
}
@Qualifier
如果@Autowired自动装配(默认使用byType方式)的环境比较复杂,自动装配无法通过一个注解【@AutoWired】完成的时候,我们可以使用@Qualifier(value = "xxx")
去配合使用(byName方式),指定一个唯一的bean对象(bean的id、name)注入!
public class Person {
@Autowired
private Cat cat;
@Autowired
@Qualifier(value = "dog222")
private Dog dog;
private String name;
}
@Resource
public class Person {
@Resource(name="cat2")
private Cat cat;
@Resource
private Dog dog;
private String name;
}
小结
@Resource和@Autowired的区别:
- 都是用来自动装配的,都可以放在属性字段上;
- @Autowired通过byType的方式实现,而且必须要求这个对象存在!【常用】
- @Resource默认通过byName的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错!
- 执行顺序不同:
- @Autowired默认通过byType的方式实现(要使用byName方式,需要使用@Qualifier注解)。
- @Resource默认通过byName的方式实现,找不到再通过类型查找。
使用注解开发
在spring4之后,要使用注解开发,必须要保证aop的包导入了。使用注解需要导入context约束,增加注解的支持!
bean配置/注册
bean配置。Spring配置文件中添加context:component-scan
标签,扫描包。在要注册bean的类上使用@Component
注解。即可将bean注册到Spring容器中,通过使用类名(小写开头)即可获取bean。
Spring配置文件(springboot项目不需要此配置):
<!--增加注解的支持-->
<context:annotation-config/>
<!--指定要扫描的包,这个包下的注解会生效-->
<context:component-scan base-package="com.qsdbl.pojo"/>
在包中使用@Component注解”配置“bean,代替原来的<bean id="address" class="com.qsdbl.pojo.Address"></bean>
类、方法上使用注解,将bean注册到spring容器中:
package com.qsdbl.pojo;
import lombok.Data;
import org.springframework.stereotype.Component;
//等价于<bean id="address" class="com.qsdbl.pojo.Address"></bean>
//@Component 组件
@Component//Spring的注解
@Data//lombok的注解
public class Address {
private String address;
}
//或者类(bean)中的某个方法(方法返回值为一个实例化对象)
//创建RSA对象
@Bean(name = "myrsa")//不指定bean的name,则该bean的标识为方法名。
private RSA init() {
if (publicKey.length() <= 10 || privateKey.length() <= 10) {
//公钥、密钥长度必须大于10位,否则创建新的密钥对
KeyPair pair = SecureUtil.generateKeyPair("RSA");
publicKey = Base64.encode(pair.getPublic().getEncoded());
privateKey = Base64.encode(pair.getPrivate().getEncoded());
}
return new RSA(privateKey, publicKey);
}
拓展:衍生的注解
@Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!
- dao【@Repository】
- service【@Service】
- controller【@Controller】
这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean!
属性注入
属性如何注入。使用@Value
注解,添加在属性上或者setter方法上。
package com.qsdbl.pojo;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//等价于<bean id="address" class="com.qsdbl.pojo.Address"></bean>
//@Component 组件
@Component//Spring的注解
@Data//lombok的注解
public class Address {
@Value("北京市")
private String address;
}
代替原来的XML配置(Spring配置文件):
<bean id="address" class="com.qsdbl.pojo.Address">
<property name="address" value="北京市"/>
</bean>
自动装配
从spring容器中获取我们注册进去的bean来使用。
使用注解实现自动装配,使用@Autowired
注解,添加在属性上或者setter方法上。自动装配可能不是很好理解,可以复习一下前边关于Bean的笔记。
- @Autowired - 自动装配(常用)
- @Qualifier - 指定Bean的name,完善@Autowired的自动装配
- @Nullable - 表明该字段可以为null
- @Resource - 自动装配
作用域
使用注解配置bean的作用域,使用@Scope
注解,添加在类上。(更多作用域介绍,点这里查看)
package com.qsdbl.pojo;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
//等价于<bean id="address" class="com.qsdbl.pojo.Address"></bean>
//@Component 组件
@Component//Spring的注解
@Data//lombok的注解
@Scope("prototype")//原型模式
public class Address {
@Value("北京市")
private String address;
}
小结
xml与注解:
- xml更加万能,适用于任何场合!维护简单方便。
- 注解,不是自己的类使用不了,维护相对复杂!
xml与注解最佳实践:
- xml用来管理bean;
- 注解只负责完成属性的注入;
- 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持。导入context约束和添加如下配置:
<!--指定要扫描的包,这个包下的注解会生效-->
<context:component-scan base-package="com.qsdbl"/>
<!--配置注解的支持-->
<context:annotation-config/>
JavaConfig
使用java的方式配置Spring。我们现在要完全不使用Spring的xml配置了,全权交给java来做!(使用java类代替xml配置文件)
javaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能。
实体类:
// 这里这个注解的意思,就是说明这个类被Spring托管了,注册到了容器中
@Component
public class User {
@Value("qsdbl")//属性注入
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
配置类 - 配置文件:
import com.kuang.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
//这个也会被Spring容器托管,注册到容器中,因为本来就是一个@Component
//@Configuration代表这是一个配置类,就和我们之前看的beans.xml
@Configuration
@ComponentScan("com.kuang.pojo")//包扫描,扫描使用@Component注解的bean(注册bean到容器中)
@Import(KuangConfig2.class )//合并 多个配置类
public class KuangConfig {
//注册一个bean,就相当于我们之前写的一个bean标签
//这个方法的名字,就相当于bean标签中的id属性
//这个方法的返回值,就相当于bean标签中的class属性
@Bean("user")//不想使用方法名作为bean的标识符,可以设置成其他的,例如这里设置成“user“
public User getUser(){
return new User();//就是返回要注入到bean的对象
}
}
测试类:从Spring容器中获取bean
public class MyTest {
public static void main(String[] args) {
//如果完全使用了配置类方式去做,我们就只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载!
ApplicationContext context = new AnnotationConfigApplicationContext(KuangConfig.class);
//User getUser = (User) context.getBean("getUser");//@Bean("")中设置了bean的名字,就不能使用方法名了
User user = (User) context.getBean("user");
System.out.println(user.getName());
}
}
这种纯java的配置方式,在SpringBoot中随处可见!(这里了解即可,到SpringBoot中再详细学习)
AOP
代理模式
为什么要学习代理模式?因为这就是SpringAOP的底层!【SpringAOP 和 SpringMVC 面试必问】
代理模式的分类:
- 静态代理
- 动态代理
静态代理
- 抽象角色 : 一般使用接口或者抽象类来实现。
- 真实角色 : 被代理的角色。
- 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 。
- 客户 : 使用代理角色来进行一些操作 。
例子一
- 接口
// 抽象角色:租房
public interface Rent {
public void rent();
}
- 真实角色
// 真实角色:房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
- 代理角色
public class Proxy implements Rent{
private Host host;//继承是is a的关系,组合是has a的关系
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHouse();
host.rent();
fare();
}
// 看房
public void seeHouse(){
System.out.println("带客户看房子!");
}
// 收中介费
public void fare(){
System.out.println("收中介费!");
}
}
- 客户端访问代理角色
// 客户
public class Client {
public static void main(String[] args) {
// 房东要出租房子
Host host=new Host();
// 中介帮房东出租,代理一般会有一些附属操作!
Proxy proxy=new Proxy(host);
proxy.rent();
}
}
例子二
例子二:传送门
例子三
- 抽象角色(接口)
// 抽象角色:增删改查业务(要实现的业务)
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
- 真实角色
// 真实角色
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("添加一个用户!");
}
@Override
public void delete() {
System.out.println("删除一个用户!");
}
@Override
public void update() {
System.out.println("修改一个用户!");
}
@Override
public void query() {
System.out.println("查询一个用户!");
}
}
- 代理角色
public class UserServiceProxy implements UserService{
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
// 打印日志信息
public void log(String msg){
System.out.println("[Debug]执行了"+msg+"方法");
}
}
- 客户端访问代理角色
public class Client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService = new UserServiceImpl();
//代理类
UserServiceProxy proxy = new UserServiceProxy();
//使用代理类实现日志功能!
proxy.setUserService(userService);
proxy.add();
}
}
小结
- 【开闭原则】Software entities like classes,modules and functions should be open for extension but closed for modifications.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。)
- 我们在不改变原有代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想!
静态代理的好处:
- 可以使得我们的真实角色更加纯粹 。不再去关注一些公共的事情。
- 公共的业务由代理来完成。. 实现了业务的分工。
- 公共业务发生扩展时变得更加集中和方便 。
缺点 :
- 一个真实角色就会产生一个代理角色, 工作量变大了 . 开发效率会降低 .
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
动态代理
- 动态代理的角色和静态代理的一样 。
- 动态代理的代理类是动态生成的 ,静态代理的代理类是我们提前写好的。
- 动态代理分为两类 : 一类是基于接口的动态代理 , 一类是基于类的动态代理。
-
- 基于接口的动态代理:JDK动态代理
- 基于类的动态代理:cglib
- 现在用的比较多的是 javasist 来生成动态代理【Java字节码】
- 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!
用到的类
需要了解两个类:InvocationHandler 和 Proxy
【InvocationHandler:调用处理程序】
InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
Object invoke(Object proxy, 方法 method, Object[] args);
//参数
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
【Proxy : 代理】
//(动态)生成代理类
public Object getProxy(){
//target是下边案例中的一个变量
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
//调用Proxy类的方法newProxyInstance:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)//返回指定接口的代理类的实例
关于InvocationHandler与Proxy详细介绍,见这篇博客。
例子
抽象角色:同上边例子三。
真实角色:同上边例子三。
代理角色:区别于上边例子三中的静态代理角色。
类MyProxyInvocation,用于生成动态代理实例:
package com.qsdbl.pojo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;
//会使用这个类,动态生成代理类
public class MyProxyInvocation implements InvocationHandler {
//被代理的接口(要实现的业务。抽象角色)
private Object target;
public void setTarget(Object target) {//参数为实现了抽象角色(接口)的真实角色
this.target = target;
}
//(根据target,动态)生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果(执行代理角色的方法时,会调用该方法,并将代理类、方法、参数传递过来)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//新增功能:
//可基于 抽象角色(接口)/真实角色(实现类)增加功能
String targetInterface = (target.getClass().getInterfaces())[0].getSimpleName();//获取抽象角色(接口名)
//target.getClass().getSimpleName()//当前类名(真实角色)
//新增日志功能
if (targetInterface.equals("UserService")){//实现的抽象接口为UserService,才应用日志功能
mylog(targetInterface,method.getName());//新增的日志功能(不是在要代理的类中增加,没有修改源码)
}
//可继续拓展其他的功能
//核心:本质利用反射实现!
Object result = method.invoke(target, args);
return result;
}
//新增日志功能
public void mylog(String targetInterface,String methodName){
System.out.println("【日志记录】:执行了接口\""+targetInterface+"\"的\""+methodName+"\"方法! -- "+new Date());
}
}
客户端访问代理角色:
@Test
public void test3(){
//真实角色
UserService userService = new UserServiceImpl();
//动态生成代理类:
MyProxyInvocation proxyInvocation = new MyProxyInvocation();
proxyInvocation.setTarget(userService);//添加 要代理的角色
//生成代理类proxy
UserService proxy = (UserService)proxyInvocation.getProxy();
//代理"真实角色" 实现业务:
proxy.add();//add方法,是真实角色和抽象角色中的方法。动态角色是动态生成的
//通过代理类 实现日志功能!(新增功能,但是不修改UserServiceImpl代码)
}
//运行结果:
【日志记录】:执行了接口"UserService"的"add"方法! -- Mon Feb 01 21:41:34 CST 2021
添加一个用户!
Process finished with exit code 0
动态代理的好处:
- 可以使得我们的真实角色更加纯粹。不再去关注一些公共的事情。
- 公共的业务由代理来完成。 实现了业务的分工 。
- 公共业务发生扩展时变得更加集中和方便 。
- 一个动态代理 , 一般代理某一类业务。
- 一个动态代理可以代理多个类,代理的是接口!
AOP
什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的频率。
AOP在Spring中的作用
提供声明式事务;允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能。即使与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点,如日志、安全、缓存、事务等等……
- 切面(ASPECT):横切关注点被模块化的特殊对象,即是一个类。
- 通知(Advice):切面必须要完成的工作,即是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知执行的“地点”的定义。
- 连接点(jointPoint):与切入点匹配的执行点。
在SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
即 Aop 在 不改变原有代码的情况下 , 去增加新的功能。
使用Spring实现AOP
【重点】使用AOP织入,需要导入一个依赖包!
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
Spring中有三种方式实现AOP:
-
方式一:Spring API
-
方式二:定义切面
-
方式三:使用注解
1、Spring API
方式一:通过Spring API实现【主要是Spring API接口的实现】
抽象角色(业务接口):同上边例子三。
真实角色(实现类):同上边例子三。
要新增的功能:BeforeLog类与AfterLog类的日志功能,实现不同的Advice接口定义横切逻辑(见上边的Advice表),注意看后边的测试结果。(MethodBeforeAdvice – 方法前,AfterReturningAdvice – 方法后)
package com.qsdbl.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {//增加一个日志功能
//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对象
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
package com.qsdbl.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {//也是增加一个日志功能
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
}
}
Spring核心配置文件:配置aop,定义(新增的功能,即两个日志类)切入点。切入点是UserServiceImpl实现类的所有方法(该类的所有方法,都会被增加两个Log类的日志功能)。
<?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">
<!--注册bean-->
<bean id="userServiceImpl" class="com.qsdbl.service.UserServiceImpl"></bean>
<bean id="beforelog" class="com.qsdbl.log.BeforeLog"/>
<bean id="afterlog" class="com.qsdbl.log.AfterLog"/>
<!-- 方式一:使用原生Spring API接口-->
<!-- 配置aop:需要导入aop的约束(注意看beans标签头部)-->
<aop:config>
<!-- 切入点:execution()表达式。(指定要执行的位置)-->
<!-- 声明一个切入点,表示你要切入哪些方法。UserServiceImpl类所有的方法-->
<aop:pointcut id="mypointcut01" expression="execution(* com.qsdbl.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕-->
<aop:advisor advice-ref="beforelog" pointcut-ref="mypointcut01"/>
<aop:advisor advice-ref="afterlog" pointcut-ref="mypointcut01"/>
</aop:config>
</beans>
代理角色:区别于上边例子三中的静态代理角色,这里由Spring动态生成。(上边的aop配置 - 重点)
测试类:
@Test
public void testAOP(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口(注意强转为接口类型UserService,而不是实现类userServiceImpl)
UserService us = (UserService)context.getBean("userServiceImpl");//获取userServiceImpl实例(真实角色),返回的是代理角色
us.add();
}
//getBean返回的不是userServiceImpl实例(真实角色,实体类),而是Spring容器生成的动态代理实例(代理角色),由于Spring中动态代理代理的是接口(抽象角色,接口),所以这里getBean要指定其类型为UserService接口(即变量us其类型为UserService)
正常的使用bean即可,但是通过观察运行结果可以看到两个Log类的日志功能已经被增加了进去。并且根据Log类所实现的Advice接口实现了不同的横切逻辑。(关于execution表达式,见下边的笔记)
运行结果:
com.qsdbl.service.UserServiceImpl的add方法被执行了
添加一个用户!
执行了com.qsdbl.service.UserServiceImpl的add方法,返回值:null
Process finished with exit code 0
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理。
2、定义切面
方式二:自定义类实现【主要是切面定义】
切面,即要增加的功能(业务),该自定义类没有实现Spring API接口(Advice相关接口)。
Spring配置文件:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="com.tian.service.UserServiceImpl"></bean>
<bean id="log" class="com.tian.log.Log"/>
<bean id="afterlog" class="com.tian.log.AfterLog"/>
<bean id="diy" class="com.tian.log.DiyPointCut"/>
<!--方式二:自定义切面实现AOP-->
<aop:config>
<!--自定义切面,ref要引用的类-->
<aop:aspect ref="diy">
<!--切入点(指定要执行的位置)-->
<aop:pointcut id="point" expression="execution(* com.tian.service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
<!--使用aop标签(before/after)定义横切逻辑-->
</aop:aspect>
</aop:config>
</beans>
【自定义类-切面】
public class DiyPointCut {
public static void before(){
System.out.println("===方法执行前===");
}
public static void after(){
System.out.println("===方法执行后===");
}
}
3、注解
方式三:使用注解实现AOP
Spring配置文件:
<?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"
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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--增加注解的支持-->
<context:annotation-config/>
<!--指定要扫描的包,这个包下的注解会生效(扫描bean)-->
<context:component-scan base-package="com.qsdbl"/>
<!--增加aop注解的支持-->
<aop:aspectj-autoproxy/>
</beans>
<!--
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
-->
类AnnotationPointCut,作为一个切面(@Aspect),其中的方法使用注解定义横切逻辑(切入点。指定要执行的位置)。
package com.qsdbl.diy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
//方式三:使用注解方式实现AOP
@Component//注册bean(到Spring容器)
@Aspect//标注这个类是一个切面
public class AnnotationPointCut {
//定义横切逻辑:@Before,方法前,参数为切入点(指定要执行的位置)
@Before("execution(* com.qsdbl.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("====before,方法执行前====");
}
@After("execution(* com.qsdbl.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("====after,方法执行后====");
}
//在环绕增强中,我们可以给定一个参数(下边的jp),代表我们要获取处理切入的点
@Around("execution(* com.qsdbl.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("around,环绕前");
//扩展
//Signature signature = jp.getSignature();//获取签名(前限定名,包括方法名)
//System.out.println("signature:"+signature);
//System.out.println(jp.getTarget().getClass().getName());//代理接口(抽象角色)
//System.out.println(jp.getSignature().getName());//执行的方法
//执行方法
Object proceed = jp.proceed();
System.out.println("around,环绕后");
//System.out.println("around,proceed:"+proceed);
}
}
测试:
@Test
public void testAOP2(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口(注意)
UserService userService = (UserService)context.getBean("userServiceImpl");//userServiceImpl也要使用注解@Component,注册bean到Spring容器
userService.add();
}
运行结果:
around,环绕前
====before,方法执行前====
添加一个用户!
around,环绕后
====after,方法执行后====
Process finished with exit code 0
execution表达式
execution表达式:【execution(* com.sample.service.impl…*.*(…))】
符号 | 含义 |
---|---|
execution() | 表达式的主体; |
第一个”*“符号 | 表示返回值的类型任意; |
com.sample.service.impl | AOP所切的服务的包名,即,我们的业务部分 |
包名后面的”…“ | 表示当前包及子包 |
第二个”*“ | 表示类名,*即所有类。此处可以自定义, |
.*(…) | 表示任何方法名,括号表示参数,两个点表示任何参数类型 |
springboot中实现
在springboot中实现aop,一般是采用注解方式。
导入依赖
<!-- aop织入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义切面
Spring采用@Aspect注解对POJO进行标注,该注解表明该类不仅仅是一个POJO,还是一个切面。切面是切点和通知的结合,那么定义一个切面就需要编写切点和通知。在代码中,只需要添加@Aspect注解即可。
package com.qsdbl.nazox_demo.configuration;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Configuration;
/**
* @author: 轻率的保罗
* @since: 2022-08-05
* @Description: 测试aop
*/
// 表示当前的类是一个配置类
@Configuration
//该注解只能用在类上,作用:代表当前类是一个切面类,等价于spring.xml中的<aop:config>标签
//所以现在有了<aop:config>切面,还需要 通知 + 切入点
// 切面 == 通知 + 切面
@Aspect
public class MyAdviceConfig {
/**
* @param joinPoint
* @Before:前置通知
* value:切入点表达式 二者加起来构建成为一个切面
* JoinPoint:连接点:可以理解为两个圆形的切点,从这个切点就可以获取到当前执行的目标类及方法
* 前置通知和后置通知的参数的都是 JoinPoint, 前置后置通知都没有返回值
*/
// 方法级别:具体到某个具体的方法
// @Before(value = "execution(* com.liu.aop.service.impl.*.*(..))")
// 表示service包下的所有类所有方法都执行该前置通知
// @Before(value = "within(com.qsdbl.nazox_demo.testAuto.service.*)")
@Before(value = "execution(* com.qsdbl.nazox_demo.testAuto.service..*.*(..))")
public void before(JoinPoint joinPoint) {
System.out.println("before开始执行查询.......");
System.out.println("正在执行的目标类是: " + joinPoint.getTarget());
System.out.println("正在执行的目标方法是: " + joinPoint.getSignature().getName());
}
/**
* 后置通知,属性参数同上面的前置通知
* @param joinPoint 前置通知和后置通知独有的参数
*/
@After(value = "execution(* com.qsdbl.nazox_demo.testAuto.service..*.*(..))")
public void after(JoinPoint joinPoint) {
System.out.println("after查询结束.......");
// 获取执行目标类和方法名等等
}
/**
* @param proceedingJoinPoint 环绕通知的正在执行中的连接点(这是环绕通知独有的参数)
* @return 目标方法执行的返回值
* @Around: 环绕通知,有返回值,环绕通知必须进行放行方法(就相当于拦截器),否则目标方法无法执行
*/
@Around(value = "execution(* com.qsdbl.nazox_demo.testAuto.service..*.*(..))")
public Object aroud(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("aroud环绕通知开始.......");
System.out.println("执行的目标类 = " + proceedingJoinPoint.getTarget());
System.out.println("执行的目标方法 = " + proceedingJoinPoint.getSignature().getName());
// 必须方法目标方法
Object proceed = proceedingJoinPoint.proceed();
System.out.println("aroud环绕通知结束.......");
// 将目标方法的返回值进行返回,否则调用目标方法的方法无法获取到返回值
return proceed;
}
}
上边spring中的注解实现aop,在此处替换该类也能正常运行,因为都是基于“aspectjweaver”实现。
其他
整合MyBatis
MyBatis笔记,点这里复习。
环境搭建
mybatis-spring,官方文档: https://mybatis.org/spring/zh/
步骤:
- 导入相关jar包
- junit
- mybatis相关
- mysql数据库
- spring相关
- aop织入
- mybatis-spring【整合包】
- 编写配置文件
- 测试
依赖:
<dependencies>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<!--aspectj-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!--Spring操作数据库的话,还需要一个spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--mybatis-spring-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
<!--静态资源过滤问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
回顾Mybatis
-
编写实体类(对应一张数据表)。关于Lombok的使用,可以点这里复习一下。
package com.qsdbl.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String passwd; private String type = "common"; private String time; private String timeup; }
-
编写核心配置文件
mybatis-config.xml
。(resources文件夹下)<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--核心配置文件--> <configuration> <!--别名--> <typeAliases> <package name="com.qsdbl.pojo"/> </typeAliases> <!--环境配置--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mypos?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="123Cyj--"/> </dataSource> </environment> </environments> <!--注册映射器(扫描mapper配置文件)--> <mappers> <package name="com.qsdbl.mapper"/> </mappers> </configuration>
-
编写接口(mapper层接口)
package com.qsdbl.mapper; import com.qsdbl.pojo.User; import java.util.List; public interface UserMapper { List<User> selectUsers();//查询user表的所有数据 }
-
编写mapper配置文件(尽量与接口同名。放在resources文件夹下,新建与接口包名同级的目录,
resources/com/qsdbl/mapper/UserMapper.xml
)<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace,绑定一个对应的Dao/Mapper接口(注意包名不要写错)--> <mapper namespace="com.qsdbl.mapper.UserMapper"> <!--select查询语句。id对应接口中的方法名。resultType声明返回的数据类型(这里写实体类User,而不是selectUsers方法返回的List集合。这里的返回类型使用的别名user,是前边核心配置文件中设置的--> <select id="selectUsers" resultType="user"> select * from user </select> </mapper>
-
测试
@Test public void test() throws IOException { //这部分,以前写在工具类中 String resources = "mybatis-config.xml"; InputStream in = Resources.getResourceAsStream(resources); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = sqlSessionFactory.openSession(true); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.selectUsers(); for (User user : users) { System.out.println(user); } } //运行结果:能正常查询数据 User(id=1, name=马克思, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 13:56:32.0) User(id=2, name=小黑, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0) User(id=3, name=pete, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0) User(id=4, name=花花, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0) User(id=5, name=小鸭子, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0) Process finished with exit code 0
Mybatis-Spring(整合)
方式二:SqlSessionDaoSupport(使用起来更加方便)
SqlSessionTemplate
整合mybatis配置
编写一个(Spring)配置文件(名字自定义),用于放Spring整合mybatis的一些配置,单独放在一个文件中不需要修改。sqlSessionFactory中,可完成大部分的mybatis配置。(在Spring配置文件中,也可以跟在MyBatis配置文件中一样通过引入数据库文件的方式配置datasource)
配置文件spring-dao.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">
<!-- Spring整合mybatis的一些配置(将以前要new的实例,注册到Spring的IOC容器中)-->
<!-- DataSource:使用Spring的数据源管理Mybatis的配置,其他的有:c3p0 dbcp druid
我们这里使用Spring提供的JDBC-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mypos?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123Cyj--"/>
</bean>
<!-- sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 绑定mybatis配置文件(原来在mybatis-config.xml中的配置,都可以放在这里进行配置。所以原来的mybatis核心配置文件可以不需要了)-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 注册映射器(原来在mybatis-config.xml中要使用mappers标签进行配置)-->
<property name="mapperLocations" value="classpath:com/qsdbl/mapper/*.xml"/>
</bean>
<!-- SqlSessionTemplate:就是我们使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 只能使用构造器注入sqlSessionFactory,因为SqlSessionTemplate没有set方法无法通过set注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</beans>
Spring主配置文件
编写Spring的“主配置文件”applicationContext.xml。用于放Spring的配置,和导入MyBatis的配置。(下边的“注册bean”,可以通过扫描Dao接口包动态注册到spring容器中)
<?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">
<!-- 导入整合mybatis的一些配置-->
<import resource="spring-dao.xml"/>
<!-- 注册bean(DAO接口的实现类)到Spring的IOC容器-->
<bean id="userMapperImpl" class="com.qsdbl.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
MyBatis配置文件
MyBatis的配置文件还是前边“回顾MyBatis“中的mybatis-config.xml文件,不过进行了精简,将在Spring中配置了的内容去掉(前边的spring-dao.xml文件)。精简后的mybatis-config.xml文件(可保留可去掉,配置都写在“整合MyBatis“的配置文件中即可):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<!--别名-->
<typeAliases>
<package name="com.qsdbl.pojo"/>
</typeAliases>
<!--设置-->
<!-- <settings>-->
<!-- <setting name="logImpl" value="STDOUT_LOGGING"/>-->
<!-- </settings>-->
<!--环境配置,在Spring中进行配置-->
<!--注册映射器,在Spring中进行配置-->
</configuration>
整合mybatis接口
- 普通mybatis项目中,mapper接口+mapper配置文件(配置sql语句)就可以了。但是要整合到Spring中,需要注册bean到Spring的IOC容器中,所以我们需要给mapper接口编写对应的实现类。
- 实现类中使用前边“整合mybatis配置“中注册到Spring容器中的SqlSessionTemplate(可看作sqlSession),完成对数据库的操作。
- 在Spring中注册实现类时使用set注入的方式注入SqlSessionTemplate实例。(见前边的Spring主配置文件中的配置)
mapper接口的实现类:
package com.qsdbl.mapper;
import com.qsdbl.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
//(接口)没有实现类就没法 注册bean到Spring容器中
public class UserMapperImpl implements UserMapper {
// 在原来,我们的所有操作,都使用sqlSession来执行。现在都使用sqlSessionTemplate
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {//Spring配置文件中,通过配置bean,注入SqlSessionTemplate实例
this.sqlSession = sqlSession;
}
public List<User> selectUsers() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);//getMapper,整合到了实体类中。直接使用实体类实例完成数据库操作即可
return mapper.selectUsers();
}
}
测试
测试:直接使用userMapperImpl实现类实例即可(注意对比与原mybatis项目中的使用方式)
//mybatis + spring
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = context.getBean("userMapperImpl", UserMapper.class);//getMapper,整合到了实体类中
for (User user : mapper.selectUsers()) {
System.out.println(user);
}
}
//普通mybatis项目中的使用方式:
@Test
public void test() throws IOException {
//这部分,以前写在工具类中
String resources = "mybatis-config.xml";
InputStream in = Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectUsers();
for (User user : users) {
System.out.println(user);
}
}
//都能查询到数据:
User(id=1, name=马克思, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 13:56:32.0)
User(id=2, name=小黑, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=3, name=pete, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=4, name=花花, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=5, name=小鸭子, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
Process finished with exit code 0
SqlSessionDaoSupport
整合mybatis配置
与上边基本相同,不同点在于SqlSessionTemplate标签的配置,可以保留也可以去掉。
<!-- SqlSessionTemplate:就是我们使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 只能使用构造器注入sqlSessionFactory,因为SqlSessionTemplate没有set方法无法通过set注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
Spring主配置文件
与上边基本相同,不同点在于注册mapper接口到Spring容器时注入的依赖。
<!--旧:-->
<!-- 注册bean到Spring的IOC容器-->
<bean id="userMapperImpl" class="com.qsdbl.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
<!--新:-->
<!--注入sqlSessionFactory,上边的SqlSessionTemplate标签没有使用,可以去掉-->
<bean id="userMapperImpl02" class="com.qsdbl.mapper.UserMapperImpl02">
<!-- 从父类SqlSessionDaoSupport中继承的属性-->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<!-- <property name="sqlSessionTemplate" ref="sqlSession"/>-->
</bean>
MyBatis配置文件
与上边相同。
整合mybatis接口
与上边基本相同,不同点如下:
- 不使用SqlSessionTemplate而是SqlSessionDaoSupport
- 使用方式发生改变,由原来的定义SqlSessionTemplate变量通过set注入改为了继承SqlSessionDaoSupport通过set注入sqlSessionTemplate或sqlSessionFactory
- 若注入sqlSessionFactory,则前边“整合mybatis配置”中的“SqlSessionTemplate标签”可去掉
- 若注入SqlSessionTemplate,则可保留
- 若两个属性都注入,那么
SqlSessionFactory
将被忽略 - 下边的案例中(增加实现类UserMapperImpl02),注入sqlSessionFactory。注意看“Spring主配置文件”中的变化。
package com.qsdbl.mapper;
import com.qsdbl.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl02 extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUsers() {
// SqlSession sqlSession = getSqlSession();//该方法继承自父类(获取到的是SqlSessionTemplate实例)
// UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// return mapper.selectUsers();
return getSqlSession().getMapper(UserMapper.class).selectUsers();//一句话搞定
}
}
测试
与上边相同。获取的bean不一样而已。
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = context.getBean("userMapperImpl02", UserMapper.class);//getMapper,整合到了实体类中
for (User user : mapper.selectUsers()) {
System.out.println(user);
}
}
声明式事务
回顾事务
- 把一组业务当成一个业务来做;要么都成功,要么都失败!
- 事务在项目开发中,十分的重要,涉及到数据的一致性和完整性问题,不能马虎!
- 确保完整性和一致性!
事务ACID原则:
- 原子性
- 一致性
- 隔离性
- 多个业务可能操作同一个资源,防止数据损坏!
- 持久性
- 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久的写到存储器中!
问题
在mapper接口中,新增加方法addUser、deleteUser
package com.qsdbl.mapper;
import com.qsdbl.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
List<User> selectUsers();
//增加一个用户
int addUser(User user);
//删除一个用户
int deleteUser(@Param("id") int id);
}
对应的mapper配置文件中增加SQL语句。delete写成deletes,人为制造错误。
<insert id="addUser" parameterType="user">
insert into user (name,passwd) values (#{name},#{passwd});
</insert>
<delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
</delete>
实现类,添加具体方法实现(注意看selectUsers方法的改变)
package com.qsdbl.mapper;
import com.qsdbl.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl02 extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUsers() {//为了测试需要,对selectUsers进行如下修改:新增用户-小花,并且删除id为3的用户,再返回全部数据
SqlSession sqlSession = getSqlSession();//该方法继承自父类(获取到的是SqlSessionTemplate实例)
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User("小花", "13243");//新增用户,小花
//操作数据库
mapper.addUser(user);//新增用户
mapper.deleteUser(3);//删除id为3的用户(这一步会出错)
return mapper.selectUsers();
}
public int addUser(User user) {
return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
public int deleteUser(int id) {//mapper配置文件中,使用命令deletes,人为制造错误
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
}
//注意,实体类User中要添加对应的构造器:
public User(String name, String passwd) {
this.name = name;
this.passwd = passwd;
}
测试:新增用户-小花,并且删除id为3的用户。
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = context.getBean("userMapperImpl02", UserMapper.class);//getMapper,整合到了实体类中
for (User user : mapper.selectUsers()) {
System.out.println(user);
}
}
//执行的是 com.qsdbl.mapper.UserMapperImpl02 实现类中的selectUsers方法
运行结果
运行出现异常:
org.springframework.jdbc.BadSqlGrammarException:
### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'deletes from user where id = 3' at line 1
### The error may exist in file [/Users/qsdbl/Documents/Java/workspace/Spring5/spring-mybatis/target/classes/com/qsdbl/mapper/UserMapper.xml]
### The error may involve com.qsdbl.mapper.UserMapper.deleteUser-Inline
### The error occurred while setting parameters
### SQL: deletes from user where id = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'deletes from user where id = 3' at line 1
...
运行前的数据:
1,马克思,123456,common,2021-01-27 10:09:21,2021-01-27 13:56:32
2,小黑,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
3,pete,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
4,花花,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
5,小鸭子,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
运行后的数据:
1,马克思,123456,common,2021-01-27 10:09:21,2021-01-27 13:56:32
2,小黑,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
3,pete,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
4,花花,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
5,小鸭子,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
6,小花,13243,common,2021-02-03 12:24:00,2021-02-03 12:24:00
程序运行过程出现了异常,没有正常执行“新增用户小花,并且删除id为3的用户”的操作,仅仅是新增了用户小花。我们希望程序“要么都成功,要么都失败!”,而不是完成一部分。为此我们需要引入事务管理。
引入事务管理
注意:在SpringBoot中开启事务,只需要在方法上添加注解
@Transactional
即可。
事务管理是对于一系列数据库操作进行管理,一个事务包含一个或多个SQL语句,是逻辑管理的工作单元(原子单元)。Spring中的事务管理分为声明式事务、编程式事务。
- 声明式事务:AOP的应用
- 编程式事务:需要在代码中,进行事务的管理
需要的AOP依赖:
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
使用方法
使用Spring中的声明式事务(不改变原有代码的基础上,增加事务),将方法com.qsdbl.mapper.UserMapperImpl02.selectUsers
中的多个(数据库)操作合为一个事务,解决上边的问题。(Spring API方式实现的AOP)
在Spring配置文件中,添加如下配置:我添加在了spring-dao.xml文件中。
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--结合AOP实现事务织入-->
<!--配置事务通知(通过aop织入的功能)-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给哪些方法配置事务-->
<tx:attributes>
<!--配置事务的传播特性(了解即可):propagation(默认REQUIRED)-->
<!--<tx:method name="add*" propagation="REQUIRED"/>-->
<!--<tx:method name="delete*"/>-->
<!--<tx:method name="update*"/>-->
<!--<tx:method name="query*" read-only="true"/>-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<!--切入点:mapper包下的所有类的所有方法-->
<!--<aop:pointcut id="txPointCut" expression="execution(* com.qsdbl.mapper.*.*(..))"/>-->
<!--切入点:仅仅是selectUsers方法-->
<aop:pointcut id="txPointCut" expression="execution(* com.qsdbl.mapper.UserMapperImpl02.selectUsers())"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
要在哪个方法中启用Spring的事务管理,配置切入点即可。
Spring中事务传播行为(默认required):
传播行为 | 解释 |
---|---|
REQUIRED | 业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务.这是spring默认的传播行为. |
SUPPORTS | 如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分,如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行. |
MANDATORY | 只能在一个已存在事务中执行,业务方法不能发起自己的事务,如果业务方法在没有事务的环境下调用,就抛异常. |
REQUIRES_NEW | 业务方法总是会为自己发起一个新的事务,如果方法已运行在一个事务中,则原有事务被挂起,新的事务被创建,直到方法结束,新事务才结束,原先的事务才会恢复执行. |
NOT_SUPPORTED | 声明方法需要事务,如果方法没有关联到一个事务,容器不会为它开启事务.如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行. |
NEVER | 声明方法绝对不能在事务范围内执行,如果方法在某个事务范围内执行,容器就抛异常.只有没关联到事务,才正常执行. |
NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中.如果没有活动的事务,则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保证点.内部事务回滚不会对外部事务造成影响, 它只对DataSourceTransactionManager 事务管理器起效. |
测试
测试:测试部分与前边的一样,只是在Spring配置文件中增加了事务配置。
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = context.getBean("userMapperImpl02", UserMapper.class);//getMapper,整合到了实体类中
for (User user : mapper.selectUsers()) {
System.out.println(user);
}
}
运行结果:
依然是运行时出现异常,但是数据库中并没有新增用户“小花”。实现了“要么都成功,要么都失败!”
org.springframework.jdbc.BadSqlGrammarException:
### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the man
...
将mapper配置文件中的deletes改回正确的delete,再次运行:成功执行了“新增用户-小花,并且删除id为3的用户”
User(id=1, name=马克思, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 13:56:32.0)
User(id=2, name=小黑, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=4, name=花花, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=5, name=小鸭子, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=12, name=小花, passwd=13243, type=common, time=2021-02-03 13:53:04.0, timeup=2021-02-03 13:53:04.0)
Process finished with exit code 0
小结
为什么需要事务?
- 如果不配置事务,可能存在数据提交不一致的情况;
- 如果我们不在Spring中配置声明式事务,就需要在代码中手动配置事务!
- 事务在项目开发中,十分的重要,涉及到数据的一致性和完整性问题,不能马虎!
注解-总结
自动装配:
- @Autowired - 自动装配(常用)
- @Qualifier - 完善@Autowired的自动装配
- @Nullable - 表明该字段可以为null
- @Resource - 自动装配
IOC:
-
Bean
-
@Component - 注册bean到Spring中
- dao【@Repository】
- service【@Service】
- controller【@Controller】
-
@Value - 注入
-
-
@Scope - 作用域
AOP:
@Aspect - 切面