简介
- 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整合。
1 |
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> |
优点
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接口实现,已经发生了革命性的变化!
1 |
private 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,其他相关的也会被导入)
1 |
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> |
- 在resources文件夹下,创建beans.xml配置文件,采用XML方式配置Bean:(官方建议配置文件起名为“applicationContext.xml“)
1 |
|
- 添加bean对象:(beans.xml中)
1 |
<!--使用Spring来创建对象,在Spring这些都称之为Bean--> |
上边用到的实体类Hello,java代码如下:
1 |
package com.qsdbl.pojo; |
- 测试:
1 |
|
思考问题?
Hello对象是谁创建的?
hello对象是由Spring创建的。
Hello对象的属性是怎么设置的?
hello对象的属性是由Spring容器设置的。
这个过程就叫做控制反转:
控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。
反转:程序本身不创建对象,而变成被动的接收对象。
依赖注入:就是利用set方法来进行注入。
==IoC是一种编程思想,由主动的编程编程被动的接收。==
可以通过new ClassPathXmlApplicationContext
去浏览一下底层源码。
OK,到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IoC,一句话搞定:对象由Spring来创建,管理,装配!
IOC创建对象的方式
无参构造器
Spring容器,默认使用无参构造器创建对象
1 |
package com.qsdbl.pojo; |
测试:(beans.xml没有修改,跟上边一样)
1 |
|
运行结果:
1 |
使用无参构造器,创建对象 |
有参构造器
在beans.xml文件中,指定使用有参构造器创建对象。
使用有参构造器,有三种方式赋值:(在constructor-arg
标签中设置)
index
index指(有参构造器的)==参数下标== , 下标从0开始。(可以写多个参数,若构造器可以接收多个参数)
1
2
3
4
5
6
7
8
9//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 0type
type根据==参数类型==设置。(不建议使用)
1
2
3
4
5
6
7
8
9//修改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 0name
根据==参数名称==来设置。
1
2
3
4
5
6
7
8
9//修改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设置的别名。
1 |
<bean id="hello" class="com.qsdbl.pojo.Hello"> |
bean配置
bean就是java对象,由Spring创建和管理。使用==bean标签==进行配置(注册bean到Spring中)
==id==是bean的唯一标识符,如果没有配置id,name就是默认标识符
- 如果配置了id,又配置了name,那么==name==是别名。name可以设置多个别名,可以用逗号,分号,空格隔开
- 如果不配置id和name,可以根据
applicationContext.getBean(xxx.class)
获取对象;
==class==是bean对象所对应的类型的全限定名=包名+类名
1 |
<bean id="hello" name="helloworld h1,h2;h3" class="com.qsdbl.pojo.Hello"> |
import
这个import,一般用于团队开发使用,他可以==将多个配置文件,导入合并为一个==。
假设,现在项目中有多个人开发,这三个人负责不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的!
- 张三
- 李四
- 王五
- applicationContext.xml
1 |
|
使用的时候,直接使用总的配置就可以了。
依赖注入
我的对象的『依赖』是注入进来的,而和它的构造方式解耦了。构造它这个『控制』操作也交给了第三方,也就是控制反转。
==个人理解==:依赖可以理解为类各属性的具体值,现在创建对象不是提前将各属性值写死在程序中(new的方式)而是交给框架,调用者通过调用框架将”依赖“注入再创建。对类的属性进行赋值(依赖注入),可以通过构造器或setter方法。
构造器注入
前边已经介绍过。(IOC创建对象的方式)
Set方式注入【重点】
- 依赖注入:Set注入!
- 依赖:bean对象的创建依赖于容器!
- 注入:bean对象中的所有属性,由容器来注入!
环境搭建
引用数据类型(Lombok的使用,可以参考这篇博客)
1
2
3
4
5
6
7package com.qsdbl.pojo;
import lombok.Data;
public class Address {
private String address;
}测试对象Student
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.qsdbl.pojo;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import lombok.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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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类
1
2
3
4
5
6
7
8
9
10
11
12
13
14import com.qsdbl.pojo.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
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))
详细介绍见官方文档。
1 |
<bean id="address" class="com.qsdbl.pojo.Address"> |
测试结果:(数据均成功注入到对象中)
1 |
Student(name=码代码的冰果果, address=Address(address=北京市), books=[红楼梦, 西游记], hobbys=[听歌, 看电影], card={身份证=62341234123, 银行卡=23452352345}, games=[和平精英, 王者荣耀], wife=null, info={学号=2019252343, 性别=男}) |
注入-拓展
可以使用==p命名空间==(属性)和==c命名空间==(构造器)进行注入(可以理解为一种便捷方式)。详细介绍见官方文档。
演示:(Spring配置文件中)
1 |
|
注意点:使用p命名空间和c命名空间需要导入==xml约束==!(头部)
1 |
xmlns:p="http://www.springframework.org/schema/p" |
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默认机制)
一般单线程下使用。
1
2
3
4
5
6
7
8
9<!--默认就是单例模式-->
<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的时候,都会产生一个新对象!
一般多线程下使用。
1
2
3<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的值。
1 |
|
测试:
1 |
|
测试结果:(Spring配置文件中的第7行,Address对象设置了默认值“北京市”)
1 |
Address(address=北京市) |
自动装配
==byName==:会自动在容器上下文中查找,和自己对象set方法后面的值(set方法名后边部分,小写开头)对应的bean的id!
例如:address属性,set方法名为setAddress,byName自动装配查找的bean id为address(小写开头)
1 |
<bean id="address" class="com.qsdbl.pojo.Address" scope="prototype"> |
==byType==:需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
1 |
<bean id="address" class="com.qsdbl.pojo.Address" scope="prototype"> |
小结
- 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“更好”。
步骤
使用注解须知:
导入约束:context约束
1
2
3
4<!--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"配置注解的支持:
<context:annotation-config/>
1 |
|
@Autowired
直接在属性上使用即可!也可以在set方法上使用!
使用Autowired我们可以不用编写Set方法了,前提是你这个自动装配的属性在IOC(Spring)容器中存在(属性的类型,在Spring配置文件中已经进行了bean配置),Autowired默认使用byType方式进行自动装配。
测试代码:
1 |
public class Person { |
@Nullable
拓展:
1 |
public People(@Nullable String name){ |
@Qualifier
如果@Autowired自动装配(默认使用byType方式)的环境比较复杂,自动装配无法通过一个注解【@AutoWired】完成的时候,我们可以使用@Qualifier(value
= "xxx")
去配合使用(byName方式),指定一个唯一的bean对象(bean的id、name)注入!
1 |
public class Person { |
@Resource
1 |
public class Person { |
小结
@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项目不需要此配置):
1 |
<!--增加注解的支持--> |
类、方法上使用注解,将bean注册到spring容器中:
1 |
package com.qsdbl.pojo; |
拓展:衍生的注解
==@Component==有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!
- dao【==@Repository==】
- service【==@Service==】
- controller【==@Controller==】
这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean!
属性注入
属性如何注入。使用@Value
注解,添加在属性上或者setter方法上。
1 |
package com.qsdbl.pojo; |
自动装配
从spring容器中获取我们注册进去的bean来使用。
使用注解实现自动装配,使用@Autowired
注解,添加在属性上或者setter方法上。自动装配可能不是很好理解,可以复习一下前边关于Bean的笔记。
- @Autowired - 自动装配(常用)
- @Qualifier - 指定Bean的name,完善@Autowired的自动装配
- @Nullable - 表明该字段可以为null
- @Resource - 自动装配
作用域
使用注解配置bean的作用域,使用@Scope
注解,添加在类上。(更多作用域,点这里查看)
1 |
package com.qsdbl.pojo; |
小结
xml与注解:
- xml更加万能,适用于任何场合!维护简单方便。
- 注解,不是自己的类使用不了,维护相对复杂!
xml与注解最佳实践:
- xml用来管理bean;
- 注解只负责完成属性的注入;
- 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持。导入context约束和添加如下配置:
1 |
<!--指定要扫描的包,这个包下的注解会生效--> |
JavaConfig
使用java的方式配置Spring。我们现在要完全不使用Spring的xml配置了,全权交给java来做!(使用java类代替xml配置文件)
javaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能。
实体类:
1 |
// 这里这个注解的意思,就是说明这个类被Spring托管了,注册到了容器中 |
配置类 - 配置文件:
1 |
import com.kuang.pojo.User; |
测试类:
1 |
public class MyTest { |
这种纯java的配置方式,在SpringBoot中随处可见!(这里了解即可,到SpringBoot中再详细学习)
= = = = AOP = = = =
代理模式
为什么要学习代理模式?因为这就是SpringAOP的底层!【SpringAOP 和 SpringMVC 面试必问】
代理模式的分类:
- 静态代理
- 动态代理
静态代理
- 抽象角色 : 一般使用接口或者抽象类来实现。
- 真实角色 : 被代理的角色。
- 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 。
- 客户 : 使用代理角色来进行一些操作 。
例子一
- 接口
1 |
// 抽象角色:租房 |
- 真实角色
1 |
// 真实角色:房东 |
- 代理角色
1 |
public class Proxy implements Rent{ |
- 客户端访问代理角色
1 |
// 客户 |
例子二
例子二:传送门
例子三
- 抽象角色(接口)
1 |
// 抽象角色:增删改查业务(要实现的业务) |
- 真实角色
1 |
// 真实角色 |
- 代理角色
1 |
public class UserServiceProxy implements UserService{ |
- 客户端访问代理角色
1 |
public class Client { |
小结
- 【开闭原则】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方法。
1 |
Object invoke(Object proxy, 方法 method, Object[] args); |
【Proxy : 代理】
1 |
//(动态)生成代理类 |
关于InvocationHandler与Proxy详细介绍,见这篇博客。
例子
抽象角色:同上边例子三。
真实角色:同上边例子三。
代理角色:区别于上边例子三中的静态代理角色。
类MyProxyInvocation,用于生成动态代理实例:
1 |
package com.qsdbl.pojo; |
客户端访问代理角色:
1 |
|
动态代理的好处:
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 .
- 公共业务发生扩展时变得更加集中和方便 .
- 一个动态代理 , 一般代理某一类业务.
- 一个动态代理可以代理多个类,代理的是接口!
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织入,需要导入一个依赖包!
1 |
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> |
Spring中有三种方式实现AOP:
方式一:Spring API
方式二:定义切面
方式三:使用注解
Spring API
方式一:通过Spring API实现【主要是Spring API接口的实现】
抽象角色(业务接口):同上边例子三。
真实角色(实现类):同上边例子三。
要新增的功能:BeforeLog类与AfterLog类的日志功能,实现不同的Advice接口定义横切逻辑(见上边的Advice表),注意看后边的测试结果。(MethodBeforeAdvice – 方法前,AfterReturningAdvice – 方法后)
1 |
package com.qsdbl.log; |
Spring核心配置文件:配置aop,定义(新增的功能,即两个日志类)切入点。切入点是UserServiceImpl实现类的所有方法(该类的所有方法,都会被增加两个Log类的日志功能)。
1 |
|
代理角色:区别于上边例子三中的静态代理角色,这里由Spring动态生成。(上边的aop配置 - 重点)
测试类:
1 |
|
==正常的使用bean即可==,但是通过观察运行结果可以看到两个Log类的日志功能已经被增加了进去。并且根据Log类所实现的Advice接口实现了不同的横切逻辑。(关于execution表达式,见下边的笔记)
运行结果:
1 |
com.qsdbl.service.UserServiceImpl的add方法被执行了 |
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理。
定义切面
方式二:自定义类实现【主要是切面定义】
切面,即要增加的功能(业务),该自定义类没有实现Spring API接口(Advice相关接口)。
Spring配置文件:
1 |
|
【自定义类-切面】
1 |
public class DiyPointCut { |
注解
方式三:使用注解实现AOP
Spring配置文件:
1 |
|
类AnnotationPointCut,作为一个切面(@Aspect),其中的方法使用注解定义横切逻辑(切入点。指定要执行的位置)。
1 |
package com.qsdbl.diy; |
测试:
1 |
|
运行结果:
1 |
around,环绕前 |
execution表达式
execution表达式:【execution(* com.sample.service.impl..*.*(..))】
符号 | 含义 |
---|---|
execution() | 表达式的主体; |
第一个”*“符号 | 表示返回值的类型任意; |
com.sample.service.impl | AOP所切的服务的包名,即,我们的业务部分 |
包名后面的”..“ | 表示当前包及子包 |
第二个”*“ | 表示类名,*即所有类。此处可以自定义, |
.*(..) | 表示任何方法名,括号表示参数,两个点表示任何参数类型 |
= = = = 其他 = = = =
整合MyBatis
MyBatis笔记,点这里复习。
环境搭建
mybatis-spring,官方文档: https://mybatis.org/spring/zh/
步骤:
- 导入相关jar包
- junit
- mybatis相关
- mysql数据库
- spring相关
- aop织入
- mybatis-spring【整合包】
- 编写配置文件
- 测试
依赖:
1 |
<dependencies> |
回顾Mybatis
编写实体类(对应一张数据表)。关于Lombok的使用,可以点这里复习一下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.qsdbl.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.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文件夹下)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
<!--核心配置文件-->
<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层接口)
1
2
3
4
5
6
7package 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
)1
2
3
4
5
6
7
8
9
10
11
12
13
<!--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>测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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:
1 |
|
Spring主配置文件
编写Spring的“主配置文件”applicationContext.xml。用于放Spring的配置,和导入MyBatis的配置。(下边的“注册bean”,可以通过扫描Dao接口包动态注册到spring容器中)
1 |
|
MyBatis配置文件
MyBatis的配置文件还是前边“回顾MyBatis“中的mybatis-config.xml文件,不过进行了精简,将在Spring中配置了的内容去掉(前边的spring-dao.xml文件)。精简后的mybatis-config.xml文件(可保留可去掉,配置都写在“整合MyBatis“的配置文件中即可):
1 |
|
整合mybatis接口
- 普通mybatis项目中,mapper接口+mapper配置文件(配置sql语句)就可以了。但是要整合到Spring中,需要注册bean到Spring的IOC容器中,所以我们需要给mapper接口编写对应的==实现类==。
- 实现类中使用前边“整合mybatis配置“中注册到Spring容器中的SqlSessionTemplate(可看作sqlSession),完成对数据库的操作。
- 在Spring中注册实现类时使用set注入的方式注入SqlSessionTemplate实例。(见前边的Spring主配置文件中的配置)
mapper接口的实现类:
1 |
package com.qsdbl.mapper; |
测试
测试:直接使用userMapperImpl实现类实例即可(注意对比与原mybatis项目中的使用方式)
1 |
//mybatis + spring |
SqlSessionDaoSupport
整合mybatis配置
与上边基本相同,不同点在于SqlSessionTemplate标签的配置,可以保留也可以去掉。
1 |
<!-- SqlSessionTemplate:就是我们使用的sqlSession--> |
Spring主配置文件
与上边基本相同,不同点在于注册mapper接口到Spring容器时注入的依赖。
1 |
<!--旧:--> |
MyBatis配置文件
与上边相同。
整合mybatis接口
与上边基本相同,不同点如下:
- 不使用SqlSessionTemplate而是SqlSessionDaoSupport
-
使用方式发生改变,由原来的==定义SqlSessionTemplate变量通过set注入==改为了==继承SqlSessionDaoSupport通过set注入sqlSessionTemplate或sqlSessionFactory==
- 若注入sqlSessionFactory,则前边“整合mybatis配置”中的“SqlSessionTemplate标签”可去掉
- 若注入SqlSessionTemplate,则可保留
- 若两个属性都注入,那么
SqlSessionFactory
将被忽略 - 下边的案例中(增加实现类UserMapperImpl02),注入sqlSessionFactory。注意看“Spring主配置文件”中的变化。
1 |
package com.qsdbl.mapper; |
测试
与上边相同。获取的bean不一样而已。
1 |
|
声明式事务
回顾事务
- 把一组业务当成一个业务来做;要么都成功,要么都失败!
- 事务在项目开发中,十分的重要,涉及到数据的一致性和完整性问题,不能马虎!
- 确保完整性和一致性!
事务ACID原则:
- 原子性
- 一致性
- 隔离性
- 多个业务可能操作同一个资源,防止数据损坏!
- 持久性
- 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久的写到存储器中!
问题
在mapper接口中,新增加方法addUser、deleteUser
1 |
package com.qsdbl.mapper; |
对应的mapper配置文件中增加SQL语句。delete写成deletes,人为制造错误。
1 |
<insert id="addUser" parameterType="user"> |
实现类,添加具体方法实现(注意看selectUsers方法的改变)
1 |
package com.qsdbl.mapper; |
测试:新增用户-小花,并且删除id为3的用户。
1 |
|
运行结果
1 |
运行出现异常: |
程序运行过程出现了异常,没有正常执行“新增用户小花,并且删除id为3的用户”的操作,仅仅是新增了用户小花。我们希望程序“要么都成功,要么都失败!”,而不是完成一部分。为此我们需要引入事务管理。
引入事务管理
注意:在SpringBoot中开启事务,只需要在方法上添加注解
@Transactional
即可。
事务管理是对于一系列数据库操作进行管理,一个事务包含一个或多个SQL语句,是逻辑管理的工作单元(原子单元)。Spring中的事务管理分为声明式事务、编程式事务。
- 声明式事务:AOP的应用
- 编程式事务:需要在代码中,进行事务的管理
需要的AOP依赖:
1 |
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> |
使用方法
使用Spring中的声明式事务(不改变原有代码的基础上,增加事务),将方法com.qsdbl.mapper.UserMapperImpl02.selectUsers
中的多个(数据库)操作合为一个事务,解决上边的问题。(Spring API方式实现的AOP)
在Spring配置文件中,添加如下配置:我添加在了spring-dao.xml文件中。
1 |
<!--配置声明式事务--> |
==要在哪个方法中启用Spring的事务管理,配置切入点即可。==
Spring中事务传播行为(默认required):
传播行为 | 解释 |
---|---|
REQUIRED | 业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务.这是spring默认的传播行为. |
SUPPORTS | 如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分,如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行. |
MANDATORY | 只能在一个已存在事务中执行,业务方法不能发起自己的事务,如果业务方法在没有事务的环境下调用,就抛异常. |
REQUIRES_NEW | 业务方法总是会为自己发起一个新的事务,如果方法已运行在一个事务中,则原有事务被挂起,新的事务被创建,直到方法结束,新事务才结束,原先的事务才会恢复执行. |
NOT_SUPPORTED | 声明方法需要事务,如果方法没有关联到一个事务,容器不会为它开启事务.如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行. |
NEVER | 声明方法绝对不能在事务范围内执行,如果方法在某个事务范围内执行,容器就抛异常.只有没关联到事务,才正常执行. |
NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中.如果没有活动的事务,则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保证点.内部事务回滚不会对外部事务造成影响, 它只对DataSourceTransactionManager 事务管理器起效. |
测试
测试:测试部分与前边的一样,只是在Spring配置文件中增加了事务配置。
1 |
|
运行结果:
依然是运行时出现异常,但是数据库中并没有新增用户“小花”。实现了“要么都成功,要么都失败!”
1 |
org.springframework.jdbc.BadSqlGrammarException: |
将mapper配置文件中的deletes改回正确的delete,再次运行:成功执行了“新增用户-小花,并且删除id为3的用户”
1 |
User(id=1, name=马克思, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 13:56:32.0) |
小结
为什么需要事务?
- 如果不配置事务,可能存在数据提交不一致的情况;
- 如果我们不在Spring中配置声明式事务,就需要在代码中手动配置事务!
- 事务在项目开发中,十分的重要,涉及到数据的一致性和完整性问题,不能马虎!
注解-总结
自动装配:
- @Autowired - 自动装配(常用)
- @Qualifier - 完善@Autowired的自动装配
- @Nullable - 表明该字段可以为null
- @Resource - 自动装配
IOC:
Bean
@Component - 注册bean到Spring中
- dao【==@Repository==】
- service【==@Service==】
- controller【==@Controller==】
@Value - 注入
@Scope - 作用域
AOP:
@Aspect - 切面