本笔记适用于尚硅谷spring5教程,
本人并不推荐该教程,建议观看动力节点老杜spring6教程
Spring的特征
Spring的常用模块
Spring的主要jar包
bean注入与装配的的方式有很多种,可以通过xml,getset方式,构造函数或者注解等。简单易用的方式就是使用Spring的注解了,Spring提供了大量的注解方式,让项目阅读和开发起来更加方便。
Spring 启示录
https://www.yuque.com/docs/share/866abad4-7106-45e7-afcd-245a733b073f?# 《Spring6》
密码:mg9b
OCP开闭原则
- 这样一来就违背了开闭原则OCP。
- 开闭原则是这样说的:
- 在软件开发过程中应当对扩展开放,对修改关闭。
- 也就是说,如果在进行功能扩展的时候,添加额外的类是没问题的,但因为功能扩展而修改之前运行正常的程序。
- 这是忌讳的,不被允许的。
- 因为一旦修改之前运行正常的程序,就会导致项目整体要进行全方位的重新测试。
- 这是相当麻烦的过程。
- 导致以上问题的主要原因是:代码和代码之间的耦合度太高。
- 如下图所示:
- 可以很明显的看出,上层是依赖下层的。
- UserController依赖UserServiceImpl,而UserServiceImpl依赖UserDaoImplForMySQL,这样就会导致下面只要改动,上面必然会受牵连(跟着也会改),所谓牵一发而动全身。
- 这样也就同时违背了另一个开发原则:依赖倒置原则。
1.2 依赖倒置原则DIP
- 依赖倒置原则(Dependence Inversion Principle),简称DIP,主要倡导面向抽象编程,面向接口编程,不要面向具体编程,让上层不再依赖下层,下面改动了,上面的代码不会受到牵连。
- 这样可以大大降低程序的耦合度,耦合度低了,扩展力就强了,同时代码复用性也会增强。(软件七大开发原则都是在为解耦合服务)
- 你可能会说,上面的代码已经面向接口编程了呀:
- 确实已经面向接口编程了,但对象的创建是:new UserDaoImplForOracle()显然并没有完全面向接口编程,还是使用到了具体的接口实现类。
- 什么叫做完全面向接口编程?什么叫做完全符合依赖倒置原则呢?
- 请看以下代码:
- 如果代码是这样编写的,才算是完全面向接口编程,才符合依赖倒置原则。
- 那你可能会问,这样userDao是null,在执行的时候就会出现空指针异常呀。
- 你说的有道理,确实是这样的,所以我们要解决这个问题。解决空指针异常的问题,其实就是解决两个核心的问题:
- 第一个问题:谁来负责对象的创建。【也就是说谁来:new UserDaoImplForOracle()/new UserDaoImplForMySQL()】
- 第二个问题:谁来负责把创建的对象赋到这个属性上。【也就是说谁来把上面创建的对象赋给userDao属性】
- 如果我们把以上两个核心问题解决了,就可以做到既符合OCP开闭原则,又符合依赖倒置原则。
- Spring框架可以做到。在Spring框架中,它可以帮助我们new对象,并且它还可以将new出来的对象赋到属性上。
- 换句话说,Spring框架可以帮助我们创建对象,并且可以帮助我们维护对象和对象之间的关系。比如:
- Spring可以new出来UserDaoImplForMySQL对象,也可以new出来UserDaoImplForOracle对象,并且还可以让new出来的dao对象和service对象产生关系(产生关系其实本质上就是给属性赋值)。
- 很显然,这种方式是将对象的创建权/管理权交出去了,不再使用硬编码的方式了。同时也把对象关系的管理权交出去了,也不再使用硬编码的方式了。
- 像这种把对象的创建权交出去,把对象关系的管理权交出去,被称为控制反转。
1.3 控制反转IoC
- 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计思想,可以用来降低代码之间的耦合度,符合依赖倒置原则。
- 控制反转的核心是:将对象的创建权交出去,将对象和对象之间关系的管理权交出去,由第三方容器来负责创建与维护。
- 控制反转常见的实现方式:依赖注入(Dependency Injection,简称DI)
- 通常,依赖注入的实现由包括两种方式:
- set方法注入
- 构造方法注入
- 而Spring框架就是一个实现了IoC思想的框架。
- IoC可以认为是一种全新的设计模式,但是理论和时间成熟相对较晚,并没有包含在GoF中。(GoF指的是23种设计模式)
课堂笔记
-
OCP开闭原则
- 什么是OCP?
OCP是软件七大开发原则当中最基本的一个原则:开闭原则
对什么开?对扩展开放。
对什么闭?对修改关闭。 - OCP原则是最核心的,最基本的,其他的六个原则都是为这个原则服务的。
- OCP开闭原则的核心是什么?
只要你在扩展系统功能的时候,没有修改以前写好的代码,那么你就是符合OCP原则的。
反之,如果在扩展系统功能的时候,你修改了之前的代码,那么这个设计是失败的,违背OCP原则。 - 当进行系统功能扩展的时候,如果动了之前稳定的程序,修改了之前的程序,之前所有程序都需要进行重新测试。这是不想看到的,因为非常麻烦。
- 什么是OCP?
-
依赖倒置原则(DIP原则)
- 什么是依赖倒置原则?
面向接口编程,面向抽象编程,不要面向具体编程。 - 依赖倒置原则的目的?
降低程序的耦合度,提高扩展力。 - 什么叫做符合依赖倒置?
上 不依赖 下,就是符合。 - 什么叫做违背依赖倒置?
上 依赖 下,就是违背。
只要“下”一改动,“上”就受到牵连。
- 什么是依赖倒置原则?
-
当前程序的设计,显然既违背OCP,又违背DIP,怎么办?
可以采用“控制反转”这种编程思想来解决这个问题。 -
什么是控制反转?
控制反转:IoC(Inversion of Control)
反转是什么呢?
反转的是两件事:
第一件事:我不在程序中采用硬编码的方式来new对象了。(new对象我不管了,new对象的权利交出去了。)
第二件事:我不在程序中采用硬编码的方式来维护对象的关系了。(对象之间关系的维护权,我也不管了,交出去了。)控制反转:是一种编程思想。或者叫做一种新型的设计模式。由于出现的比较新,没有被纳入GoF23种设计模式范围内。
-
Spring框架
- Spring框架实现了控制反转IoC这种思想
Spring框架可以帮你new对象。
Spring框架可以帮你维护对象和对象之间的关系。 - Spring是一个实现了IoC思想的容器。
- 控制反转的实现方式有多种,其中比较重要的叫做:依赖注入(Dependency Injection,简称DI)。
- 控制反转是思想。依赖注入是这种思想的具体实现。
- 依赖注入DI,又包括常见的两种方式:
第一种:set注入(执行set方法给属性赋值)
第二种:构造方法注入(执行构造方法给属性赋值) - 依赖注入 中 “依赖”是什么意思? “注入”是什么意思?
依赖:A对象和B对象的关系。
注入:是一种手段,通过这种手段,可以让A对象和B对象产生关系。
依赖注入:对象A和对象B之间的关系,靠注入的手段来维护。而注入包括:set注入和构造注入。
- Spring框架实现了控制反转IoC这种思想
-
注意术语:
OCP:开闭原则(开发原则)
DIP:依赖倒置原则(开发原则)
IoC:控制反转(一种思想,一种新型的设计模式)
DI:依赖注入(控制反转思想的具体实现方式)
环境配置
基于Maven
Spring IOC
IOC容器是什么
###什么是IOC(控制反转)
- IOC:即:控制反转,将对象创建和对象之间的调用过程,交由Spring进行管理。
- 使用IOC的目的是为了解耦,即:降低程序之间的耦合度,简单来说,就是降低A类和B类之间的耦合度。
- 刚刚介绍的入门案例,就是利用了IOC容器技术实现。
IOC底层原理技术
IOC底层主要使用了:xml解析、工厂模式、反射进行实现
引出IOC
假设程序中有UserService、UserDao两个类,需求是在UserService类中调用UserDao类中的方法。
实现如下:普通类与类之间的调用方式实现
public class UserDao{
public void show(){
System.out.println(123);
}
}
12345
public class UserService{
public static void main(String[] args){
UserDao userDao = new UserDao();
userDao.show();// 123
}
}
123456
分析:按照以上方式实现,存在高耦合度的问题,当UserDao的类路径发生变化时,所有与UserDao产生关联(耦合)的类,都需要去更改,从而导致高耦合,很明显,这不是程序所追求的极致状态。
对以上的假设进行优化。
实现如下:通过使用工厂模式实现。
public class UserDao{
public void show(){
System.out.println(123);
}
}
12345
public class UserFactor{
public static UserDao getUserDao(){
return new UserDao;
}
}
12345
public class UserService{
public static void main(String[] args){
UserDao userDao = UserFactor.getUserDao();
userDao.show();// 123
}
}
123456
分析:通过使用工厂模式优化后,耦合度有一定的降低,但依然没有达到程序所追求的极致状态。补充说明:任何程序中的类与类之间的关系都不可能做到完全的无耦合,但可以通过程序去实现较低的耦合
对以上的假设再次进行优化。
实现如下:通过使用xml解析、工厂模式、反射实现
- 创建bean.xml文件,用于配置User对象的创建。
<?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">
<!--配置User对象的创建-->
<bean id="dao" class="com.lonelysunset.UserDao"></bean>
</beans>
12345678
- 创建工厂方法。
class UserFactor{
public static UserDao getDao(){
String classValue = class属性值;// 1.xml解析 //TODO 这里有一点不理解哈,如何解析xml并获取到指定类对象的全类名路径?听说是使用dom4j技术?等以后有机会了解再说吧。
Class clazz = Class.forName(classValue);// 2.通过反射创建对象
return (UserDao)cl azz.newInstance();
}
}
分析IOC接口
-
IOC思想基于IOC容器完成,IOC容器底层或者本质上就是对象工厂。
-
Spring提供了IOC容器的两种实现方式。即:两个接口。
-
BeanFactory:即IOC容器的基本实现,其主要用于提供给Spring内部使用的接口,并不向开发人员提供。
// 当使用BeanFactory加载配置文件时,不会去创建对象,而是当我们在获取对象时,才会去创建对象 //即仅加载 BeanFactory context = new ClassPathXmlApplicationContext("bean1.xml");
-
ApplicationContext:BeanFactory接口的子接口,提供了更多更强大的功能,一般主要由开发人员进行使用。
// 当使用ApplicationContext加载配置文件时,会将所有的配置文件中的对象进行创建 ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
-
-
ApplicationContext接口有实现类,其主要实现类为*
FileSystemXmlApplicationContext
*和ClassPathXmlApplicationContext
,如下所示:
-
当使用FileSystemXmlApplicationContext实现类获取xml配置文件时,需要传入xml配置文件的全路径。
//TODO 实际测试中,这里会报错,不知道啥原因,具体没有往下探究,可能是路径写错了?应该不会啊😓。 ApplicationContext context = new FileSystemXmlApplicationContext("/Users/wufuqiang/IdeaProjects/spring5/src/main/resources/bean1.xml"); 12
-
当使用ClassPathXmlApplicationContext实现类获取xml配置文件时,需要传入xml配置文件的名称即可。
// 这种方式,应该是测试的时候使用最多的,瞧瞧多方便啊,直接获取xml配置文件名称就行 ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
-
当使用FileSystemXmlApplicationContext实现类获取xml配置文件时,需要传入xml配置文件的全路径。
//TODO 实际测试中,这里会报错,不知道啥原因,具体没有往下探究,可能是路径写错了?应该不会啊😓。 ApplicationContext context = new FileSystemXmlApplicationContext("/Users/wufuqiang/IdeaProjects/spring5/src/main/resources/bean1.xml");
-
当使用ClassPathXmlApplicationContext实现类获取xml配置文件时,需要传入xml配置文件的相对源码路径即可。
// 这种方式,应该是测试的时候使用最多的,瞧瞧多方便啊,直接获取xml配置文件名称就行 ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
分析IOC中的Bean管理
什么是Bean管理?
Bean管理,一般指的是两个操作,即:Spring创建对象和Spring注入属性。
bean管理操作由两种方式
- 基于xml配置文件方式实现。
- 基于注解方式实现。(推荐方式,也是后期开发企业项目的最佳方式)
基于xml方式
1.创建对象
<?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">
<!--配置User对象的创建-->
<bean id="user" class="com.lonelysunset.pojo.User"></bean>
</beans>
12345678
分析<bean id="" class=""></bean>
标签:
-
在Spring配置文件中,使用标签,并在标签中添加对应的属性,从而实现对象的创建。
-
id属性:即表示唯一标识。
-
class属性:即表示类的全路径,比如com.solitarysunset.pojo.User
-
-
创建对象的时候,默认是执行无参构造完成对象创建的。
-
如果我们在User中创建有参构造,则系统将不再为User分配无参构造,此时再通过xml创建对象,则会报错。
2.注入属性
DI:即IOC的一种具体实现,称之为依赖注入,主要用于注入属性,其注入属性的前提必须在创建对象的基础上完成。
第一种注入方式:使用set方法进行注入
1.创建类,定义属性和对应的set方法。
/**
* 演示使用set方式进行注入属性
* @Author wufuqiang
* @Date 2022/7/25
* @Version 1.0
* @Description:
*/
public class Book
{
private String name;
private String author;
public void setAuthor(String author)
{
this.author = author;
}
public void setName(String name)
{
this.name = name;
}
}
2.在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">
<bean id="book" class="com.lonelysunset.pojo.Book">
<!--使用property完成属性注入
name:类里面的属性名称
value:向属性注入值
-->
<property name="author" value="老子"/>
<property name="name" value="道德经"/>
</bean>
</beans>
第二种注入方式:使用构造器进行注入
- 创建类,定义属性和有参构造。
/**
* 演示使用构造实现注入
* @Author wufuqiang
* @Date 2022/7/25
* @Version 1.0
* @Description:
*/
public class Cat
{
private String name;
private Integer age;
public Cat(String name, Integer age)
{
this.name = name;
this.age = age;
}
}
- 在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">
<bean id="cat" class="com.lonelysunset.pojo.Cat">
<constructor-arg name="name" value="小红"/>
<constructor-arg name="age" value="18"/>
</bean>
</beans>
####使用p名称空间注入属性,基于set注入(了解即可)
p命名标签可以给属性传值,相当于property
c命名标签可以给参数传值,相当于constructor-arg
-
使用p名称空间注入,可以简化基于xml配置方式。
-
第一步,添加p名称空间在配置文件中。
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
-
第二步,在bean标签中注入属性。
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="book" class="com.lonelysunset.pojo.Book" p:name="金瓶梅" p:author="未知"></bean> </beans>
####在xml中注入其它类型属性
- 设置null值。
- 设置包含特殊符号的属性值。
<?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="book" class="com.lonelysunset.pojo.Book">
<property name="name">
<!--设置name为null值-->
<null/>
</property>
<property name="author">
<!--属性值中设置包含特殊符号的内容,只需要将内容写在<![CDATA[内容]]>即可-->
<value><![CDATA[~无名氏~]]></value>
</property>
</bean>
</beans>
####xml实操-外部bean
假设已有UserDao、UserService两个接口,以及UserDaoImpl、UserServiceImpl实现类,要求在UserServiceImpl中注入UserDao,调用UserServiceImpl中的add方法时,同时能够调用UserDao中的sub方法。
- 创建UserDao、UserDaoImpl。
public interface UserDao
{
void sub();
}
public class UserDaoImpl implements UserDao
{
@Override
public void sub()
{
System.out.println("sub.......");
}
}
- 创建UserService、UserServiceImpl,并在UserServiceImpl声明UserDao类型的属性。
public interface UserService
{
void add();
}
1234
public class UserServiceImpl implements UserService
{
private UserDao userDao;
// 这里涉及到了多态,形参类型是UserDao,但是实参应该是UserDao接口的实现类,所以在xml注入的时候,实际注入的应该是UserDaoImpl实现类,而不是把接口注入进去
public void setUserDao(UserDao userDao)
{
this.userDao = userDao;
}
@Override
public void add()
{
System.out.println("add......");
// 这里就是动态绑定咯,编译类型是UserDao,运行类型是UserDaoImpl,所以调用的是实现类中的方法
userDao.sub();
}
}
- 使用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">
<!--创建UserServiceImpl和UserDaoImpl对象-->
<bean id="userServiceImpl" class="com.lonelysunset.pojo.UserServiceImpl">
<!--
注入userDao对象
name属性:类里面属性名称。
ref属性:创建userDao对象bean标签的id值。
-->
<property name="userDao" ref="userDaoImpl"/>
</bean>
<bean id="userDaoImpl" class="com.lonelysunset.pojo.UserDaoImpl"></bean>
</beans>
- main方法
@Test
public void test4(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
// 接收类型使用其父类类型接收,还是多态,没什么可说的
UserService userServiceImpl = (UserService) context.getBean("userServiceImpl");
userServiceImpl.add();
}
// 结果:
add......
sub.......
####xml实操-内部bean
不常用,了解下即可
- 适用于一对多关系,比如部门和员工之间的关系,一个部门有多个员工,一个员工只能属于一个部门。
- 创建Dept和Emp实体类。
public class Dept
{
private String dname;
public void setDname(String dname)
{
this.dname = dname;
}
@Override
public String toString()
{
return "Dept{" + "dname='" + dname + '\'' + '}';
}
}
123456789101112131415
public class Emp
{
private String ename;
private String gender;
// 我觉得这种注入方式就已经很反人类了,所以在xml中使用内部bean,一样反人类。
private Dept dept;
public void setEname(String ename)
{
this.ename = ename;
}
public void setGender(String gender)
{
this.gender = gender;
}
public void setDept(Dept dept)
{
this.dept = dept;
}
@Override
public String toString()
{
return "Emp{" + "ename='" + ename + '\'' + ", gender='" + gender + '\'' + ", dept=" + dept + '}';
}
}
- 使用xml方式创建实体类对象,并完成对象属性内部bean的注入。
<?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-->
<bean id="emp" class="com.lonelysunset.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="Lucy"/>
<property name="gender" value="女"/>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.lonelysunset.bean.Dept">
<property name="dname" value="超神学院"/>
</bean>
</property>
</bean>
</beans>
1234567891011121314151617
- main方法
@Test
public void test5(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml");
Emp emp = (Emp) context.getBean("emp");
System.out.println(emp);// Emp{ename='Lucy', gender='女', dept=Dept{dname='超神学院'}}
}
####xml实操-级联赋值
第一种写法
类似外部bean,强调属性注入,而不是对象创建。
第二种写法
- 需要在Emp类中为Dept对象属性生成一个get方法,从而达到覆盖的效果,类依然采用刚刚创建的Dept和Emp。
public class Emp(){
...
public Dept getDept()
{
return dept;
}
...
}
- 在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="emp" class="com.lonelysunset.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="rose"/>
<property name="gender" value="女"/>
<!--级联赋值-->
<property name="dept" ref="dept"/>
<property name="dept.dname" value="安保部"/>
</bean>
<bean id="dept" class="com.lonelysunset.bean.Dept">
<property name="dname" value="技术部"/>
</bean>
</beans>
- 测试结果。
@Test
public void test7(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean9.xml");
Emp emp = (Emp) context.getBean("emp");
System.out.println(emp);// Emp{ename='rose', gender='女', dept=Dept{dname='安保部'}}
}
####在xml中注入集合属性
- 分别注入数组类型、List类型、Map类型、Set类型的属性。
- 创建类,定义数组、List、Map、Set类型属性,并生成对应的set方法。
// 创建Stu实体类
public class Stu
{
private String[] courses;
private List<String> list;
private Map<String, String> map;
private Set<String> set;
public void setCourses(String[] courses)
{
this.courses = courses;
}
public void setList(List<String> list)
{
this.list = list;
}
public void setMap(Map<String, String> map)
{
this.map = map;
}
public void setSet(Set<String> set)
{
this.set = set;
}
@Override
public String toString()
{
return Arrays.asList(courses)+"-"+list+"-"+map+"-"+set;
}
}
- 在xml中创建stu对象,并注入属性。
<?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="stu" class="com.lonelysunset.CollectionsType.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>Java课程</value>
<value>PHP课程</value>
</array>
</property>
<!--list集合类型属性注入-->
<property name="list">
<list>
<value>SpringCloud从入门到放弃</value>
<value>SpringSecurity从入门到入土</value>
</list>
</property>
<!--map集合类型属性注入-->
<property name="map">
<map>
<entry key="花果山" value="孙悟空"/>
<entry key="高老庄" value="猪八戒"/>
</map>
</property>
<!--set集合类型属性注入-->
<property name="set">
<set>
<value>如来</value>
<value>杀心观音</value>
</set>
</property>
</bean>
</beans>
- 测试结果。
@Test
public void test8(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean10.xml");
Stu stu = (Stu) context.getBean("stu");
System.out.println(stu);// [Java课程, PHP课程]-[SpringCloud从入门到放弃, SpringSecurity从入门到入土]-{花果山=孙悟空, 高老庄=猪八戒}-[如来, 杀心观音]
}
####在集合内部设置对象类型值
- 创建Student、Course类。
public class Student
{
private List<Course> courseList;
public void setCourseList(List<Course> courseList)
{
this.courseList = courseList;
}
@Override
public String toString()
{
return "Student{" + "courseList=" + courseList + '}';
}
}
123456789101112131415
public class Course
{
private String name;
public void setName(String name)
{
this.name = name;
}
@Override
public String toString()
{
return "Course{" + "name='" + name + '\'' + '}';
}
}
123456789101112131415
- 在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="student" class="com.lonelysunset.CollectionsType.Student">
<!--在集合中注入对象类型的值-->
<property name="courseList">
<list>
<ref bean="course1"/>
<ref bean="course2"/>
</list>
</property>
</bean>
<!--创建多个Course对象-->
<bean id="course1" class="com.lonelysunset.CollectionsType.Course">
<property name="name" value="Spring5框架"/>
</bean>
<bean id="course2" class="com.lonelysunset.CollectionsType.Course">
<property name="name" value="MyBatis框架"/>
</bean>
</beans>
123456789101112131415161718192021
- 测试结果。
@Test
public void test9(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean11.xml");
Student stu = (Student) context.getBean("student");
System.out.println(stu);// Student{courseList=[Course{name='Spring5框架'}, Course{name='MyBatis框架'}]}
}
####将集合内部的值提取成公共部分使用
- 创建一个Book类。
public class Book
{
private List<String> books;
public void setBooks(List<String> books)
{
this.books = books;
}
@Override
public String toString()
{
return "Book{" + "books=" + books + '}';
}
}
123456789101112131415
- 在xml文件中,创建book对象,并将Book类中的books属性提取成公共部分。
- 在xml文件中引入util命名空间,和p命名空间差不多,copy就行。
<?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 http://www.springframework.org/schema/util/spring-util.xsd">
<!--1.提取list集合类型,并完成属性注入-->
<util:list id="bookList">
<value>Spring5框架</value>
<value>MyBatis框架</value>
<value>SpringMVC框架</value>
</util:list>
<!--2.完成属性注入-->
<bean id="bookClass" class="com.lonelysunset.CollectionsType.Book">
<property name="books" ref="bookList"/>
</bean>
</beans>
123456789101112131415161718
- 测试结果。
@Test
public void test10(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean12.xml");
com.lonelysunset.CollectionsType.Book bookClass = (com.lonelysunset.CollectionsType.Book) context.getBean("bookClass");
System.out.println(bookClass);// Book{books=[Spring5框架, MyBatis框架, SpringMVC框架]}
}
####xml自动装配
什么是自动装配?
根据指定装配规则,通过配置属性名或属性类型,Spring自动将匹配的属性值进行注入。
分析:回想一下,以前在xml配置文件中,是通过外部bean或内部bean注入对象属性值,现在通过自动装配去实现,这里介绍根据属性名称注入。
实现自动装配
bean标签中的autowire属性,配置自动装配。
autowire属性常用两个值:
byName根据属性名称注入,要求注入的bean的id值和属性名称一致
byType根据属性类型注入,要求注入的bean的class值和属性类型一致
演示自动装配过程-byName
- 创建Dpet、Emp类,并在Emp类中注入Dept对象引用。
public class Dept
{
private String name;
public void setName(String name)
{
this.name = name;
}
@Override
public String toString()
{
return "Dept{" + "name='" + name + '\'' + '}';
}
}
123456789101112131415
public class Emp
{
private String name;
// 员工类中注入部门类,奇奇怪怪
private Dept dept;
public void setName(String name)
{
this.name = name;
}
public void setDept(Dept dept)
{
this.dept = dept;
}
@Override
public String toString()
{
return "Emp{" + "name='" + name + '\'' + ", dept=" + dept + '}';
}
}
12345678910111213141516171819202122
- 在xml配置文件中创建bean对象。
<?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">
<!--
分析:回想一下,以前在xml配置文件中,是通过外部bean或内部bean注入对象属性值,现在
通过自动装配去实现,这里介绍根据属性名称注入。
实现自动装配
bean标签中的autowire属性,配置自动装配。
autowire属性常用两个值:
byName根据属性名称注入,要求注入的bean的id值和属性名称一致
byType根据属性类型注入,要求注入的bean的class值和属性类型一致
-->
<bean id="emp" class="com.lonelysunset.autowire.Emp" autowire="byName">
<property name="name" value="小吴"/>
</bean>
<bean id="dept" class="com.lonelysunset.autowire.Dept">
<property name="name" value="技术部"/>
</bean>
</beans>
123456789101112131415161718192021
- 测试结果。
@Test
public void test15(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean17.xml");
com.lonelysunset.autowire.Emp emp = (com.lonelysunset.autowire.Emp) context.getBean("emp");
System.out.println(emp);// Emp{name='小吴', dept=Dept{name='技术部'}}
}
演示自动装配-byType
- 创建Dpet、Emp类,并在Emp类中注入Dept对象引用。
public class Dept
{
private String name;
public void setName(String name)
{
this.name = name;
}
@Override
public String toString()
{
return "Dept{" + "name='" + name + '\'' + '}';
}
}
123456789101112131415
public class Emp
{
private String name;
private Dept dept;
public void setName(String name)
{
this.name = name;
}
public void setDept(Dept dept)
{
this.dept = dept;
}
@Override
public String toString()
{
return "Emp{" + "name='" + name + '\'' + ", dept=" + dept + '}';
}
}
123456789101112131415161718192021
- 在xml配置你文件中创建bean对象。
<?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">
<!--
分析:回想一下,以前在xml配置文件中,是通过外部bean或内部bean注入对象属性值,现在
通过自动装配去实现,这里介绍根据属性类型注入。
实现自动装配
bean标签中的autowire属性,配置自动装配。
autowire属性常用两个值:
byName根据属性名称注入,要求注入的bean的id值和属性名称一致
byType根据属性类型注入,要求注入的bean的class值和属性类型一致
-->
<bean id="emp" class="com.lonelysunset.autowire.Emp" autowire="byType">
<property name="name" value="小吴"/>
</bean>
<bean id="dept" class="com.lonelysunset.autowire.Dept">
<property name="name" value="技术部"/>
</bean>
</beans>
123456789101112131415161718192021
- 测试结果。
@Test
public void test16(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean18.xml");
com.lonelysunset.autowire.Emp emp = (com.lonelysunset.autowire.Emp) context.getBean("emp");
System.out.println(emp);// Emp{name='小吴', dept=Dept{name='技术部'}}
}
####IOC操作Bean管理-外部属性文件引入
这里了解一下即可。
主要用于公共文件的引入,比如数据库配置文件之类,但是在真实开发场景中用的也不多,毕竟后期都是使用SpringBoot中的yml进行配置。这里就是顺带一说。毕竟Java都是经典白学嘛。😊
原始方式配置数据库信息
<?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使用了数据库连接池,如果数据库配置信息需要修改,则需要重复修改这类文件。不便捷。
-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/userDb"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
采用外部属性文件引入的方式配置数据库信息
- 创建外部属性文件,properties格式文件,写入数据库配置信息。
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userDb
prop.userName=root
prop.password=root
- 将外部properties属性文件引入到spring配置文件中。首先需要引入context命名空间。
<?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 http://www.springframework.org/schema/context/spring-context.xsd">
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--其实也没啥,就是动态些-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"/>
<property name="url" value="${prop.url}"/>
<property name="username" value="${prop.userName}"/>
<property name="password" value="${prop.password}"/>
</bean>
</beans>
IOC操作Bean管理-bean作用域
- 在Spring中,默认情况下,设置创建bean是单实例对象。举例说明:
- 创建Computer类。
public class Computer
{
private String name;
public void setName(String name)
{
this.name = name;
}
}
- 创建bean对象。
<?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="computer" class="com.lonelysunset.bean.Computer">
</bean>
</beans>
- 测试结果。
@Test
public void test12()
{
ApplicationContext context = new ClassPathXmlApplicationContext("bean14.xml");
Computer computer1 = (Computer) context.getBean("computer");
Computer computer2 = (Computer) context.getBean("computer");
System.out.println(computer1==computer2);// true,两个是同一个对象,说明两个对象引用指向的是同一块内存地址,同一块堆中的区域。所以说,默认情况下,是单例模式。
}
- 如何设置多实例?需要在Spring配置文件的bean标签里面使用scope属性,指定范围,并设置scope属性值为prototype,表示多实例对象。举例:
<?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">
<!--
scope属性,表示设置bean为单例或多例
默认值为singleton,表示单实例对象。
prototype表示多实例对象。
-->
<bean id="computer" class="com.lonelysunset.bean.Computer" scope="prototype">
</bean>
</beans>
- 测试结果。
@Test
public void test13()
{
ApplicationContext context = new ClassPathXmlApplicationContext("bean15.xml");
Computer computer1 = (Computer) context.getBean("computer");
Computer computer2 = (Computer) context.getBean("computer");
System.out.println(computer1==computer2);// false,其实也可以将hashCode打出来看一下,肯定是不一样的。
}
bean作用域结论
- singleton为单实例,prototype为多实例。
- 设置scope属性值为singleton后,会在加载spring配置文件时创建单实例对象。
- 设置scope属性值为prototype后,并不会在加载spring配置文件时创建对象,而是在调用getBean方法时创建多个不同的对象。
Bean的生命周期
生命周期:从对象创建到对象销毁的过程。
bean的生命周期:
(1)通过构造器创建bean实例(无参构造)
(2)为bean的属性设置值和对其他bean引用(调用set方法)
(3)调用bean的初始化方法(配置初始化方法)
(4)使用bean(对象获取到了)
(5)当容器关闭,调用bean的销毁方法(需要配置销毁的方法)
如果加上后置处理器,有七个步骤:
(1)通过构造器创建bean实例(无参构造)
(2)为bean的属性设置值和对其他bean引用(调用set方法)
(3)把bean实例传递bean后置处理器的方法
(4)调用bean的初始化方法(配置初始化方法)
(5)把bean实例传递bean后置处理器的方法
(6)使用bean(对象获取到了)
(7)当容器关闭,调用bean的销毁方法(需要配置销毁的方法)
实现:创建类实现接口BeanPostProcessor,创建后置处理器
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之前执行的方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之后执行的方法");
return bean;
}
}
12345678910111213
配置好后,会为所有对象配置上后置处理器,分别在调用bean初始化之前和初始化之后执行。
基于注解方式
什么是注解?
- 注解是代码的特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值…)
- 注解主要作用于类、方法和属性上面。
- 使用注解的目的是为了简化开发,简化xml配置。就目前位为止,以上都是通过xml配置文件方式去创建对象,以及设置对应的属性值。那真实开发场景一个项目中肯定是有很多类的,那这个配置文件得有多少行哦,从而导致后期难以维护。
Spring针对Bean管理-创建对象提供了四个注解
- @Component
- @Service
- @Controller
- @Repository
以上4个注解的功能都是完全一样的,都是用来创建bean实例的。只不过在开发中为了更有效的区分,通常我们建议在接口层使用哦@Controller,业务处理层使用@Service,数据访问层使用@Repository,而其它不容易区分的Bean实例,则使用@Component标识
基于注解方法实现对象的创建
重点,以上全是白学,毕竟开发的真实场景中,需要用注解嘛!😊
1.引入依赖
补充:通常,我们如果使用Spring5框架进行开发,应将Spring5所涉及的依赖全部引入,其实吧,后面学到SpringMVC时,直接引入SpringMVC就行了。
<!--4.spring-aop部分-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
2.开启组件扫描
补充:开启组件扫描的前提是,需要引入context命名空间。
开启组件扫描:
1.如果需要扫描多个包,则多个包之间使用逗号进行分割即可。
2.另一种方式也可以实现扫描多个包,直接扫描多个包的公共上层目录即可。
<?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 http://www.springframework.org/schema/context/spring-context.xsd">
<!--
开启组件扫描:
1.如果需要扫描多个包,则多个包之间使用逗号进行分割即可。
2.另一种方式也可以实现扫描多个包,直接扫描多个包的公共上层目录即可。
-->
<context:component-scan base-package="com.example.annotation,....."></context:component-scan>
</beans>
3.创建类,并在类上添加注解
/**
* 注解中的value可以省略不写,默认值就是类的名称,首字母小写
* 如下,如果不写,默认就是userService
*/
//@Service(value = "userService")
@Service
public class UserService
{
public void show()
{
System.out.println("show。。。");
}
}
测试结果:
@Test
public void test18(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean21.xml");
com.lonelysunset.annotation.service.UserService userService = (com.lonelysunset.annotation.service.UserService) context.getBean("userService");
userService.show();// show。。。
}
开启组件扫描细节补充
设置注解白名单或注解黑名单
细节1:
添加use-default-filters="false"属性,表示不使用默认的filter。
通过自己配置filter,即context:exclude-filter,表示指定扫描哪些内容。
如下配置解释:只扫描com.lonelysunset.annotation包下,所有被@org.springframework.stereotype.Service的注解标识的类。
<?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 http://www.springframework.org/schema/context/spring-context.xsd">
<!--
细节1:
添加use-default-filters="false"属性,表示不使用默认的filter。
通过自己配置filter,即context:exclude-filter,表示指定扫描哪些内容。
如下配置解释:只扫描com.lonelysunset.annotation包下,所有被@Service的注解标识的类。
-->
<context:component-scan base-package="com.example.annotation,....." use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
</beans>
细节2:
通过添加配置filter,即context:exclude-filter,表示指定不扫描哪些内容。
如下配置解释:com.lonelysunset.annotation包下,所有被@Service的注解标识的类都不进行扫描。
<?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 http://www.springframework.org/schema/context/spring-context.xsd">
<!--
默认情况下,只要配置了context:exclude-filter,则表示使用filter过滤器扫描包
细节2:
通过添加配置filter,即context:exclude-filter,表示指定不扫描哪些内容。
如下配置解释:com.lonelysunset.annotation包下,所有被@Service的注解标识的类都不进行扫描。
-->
<context:component-scan base-package="com.lonelysunset.annotation">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
</beans>
###基于注解方式实现属性注入
####@Autowired:根据属性类型进行自动装配
第一步,把service和dao对象创建,在service和dao类添加注解
第二步,在service注入dao对象,在service类添加dao类型属性,在属性上面使用注解@Autowired
@Service
public class UserService
{
// 不需要添加set方法
@Autowired
private UserDao userDao;
public void show()
{
System.out.println("show。。。");
userDao.add();
}
}
public interface UserDao
{
public void add();
}
@Repository
public class UserDaoImpl implements UserDao
{
@Override
public void add()
{
System.out.println("userDao 中的 add...");
}
}
####@Qualified:根据属性名称进行注入
@Qualified 注解要和 @Autowired 一起使用。
@Qualified表示以名称注入,@Autowired表示以类型注入,如果一个接口有多个实现类,那么要用@Qualified指定名称进行注入。
解释:已知@Autowried根据属性类型完成属性注入,而@Qualifier根据属性名称完成属性注入,当UserDao有多个实现类时,则Spring使用@Autowried时,将无法区分我们到底需要注入哪一个属性,因此需要使用@Qualifier注解声明区分。
- 演示使用:
- 创建类和接口,并完成对象的创建和属性的注入。
public interface UserDao
{
public void add();
}
1234
//@Repository(value = "userDaoImpl1")
@Repository
public class UserDaoImpl implements UserDao
{
@Override
public void add()
{
System.out.println("userDaoImpl1 中的 add...");
}
}
12345678910
//@Repository(value = "userDaoImpl2")
@Repository
public class UserDaoImpl2 implements UserDao
{
@Override
public void add()
{
System.out.println("userDaoImpl2 中的 add...");
}
}
12345678910
@Service
public class UserService
{
@Autowired
@Qualifier(value = "userDaoImpl2")
private UserDao userDao;
public void show()
{
System.out.println("show。。。");
userDao.add();
}
}
12345678910111213
- 测试结果。
@Test
public void test19(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean24.xml");
com.lonelysunset.annotation.service.UserService userService = (com.lonelysunset.annotation.service.UserService) context.getBean("userService");
userService.show();
/**
* 运行结果:
* show。。。
* userDaoImpl2 中的 add...
*/
}
####@Resource:可以根据类型注入,可以根据名称注入
@Resource:根据类型进行注入
@Resource(name=“userDaoImpl1”):根据名称进行注入
@Resource // 根据属性类型注入
private UserDao userDao;
@Resource(value="userDao1") // 根据属性名称注入
private UserDao userDao1;
@Value:注入普通类型属性
@Value(value="abc")
private String name;
完全注解开发
1.创建配置类,引用@Configuration注解,用于替代xml配置文件
@Configuration // 声明将SpringConfig类作为配置了,替代xml配置文件
//@ComponentScan(basePackages = {"com.lonelysunset"}) // 开启组件扫描
@ComponentScan("com.lonelysunset")
public class SpringConfig
{
}
- 测试结果。
@Test
public void test20(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
com.lonelysunset.annotation.service.UserService userService = (com.lonelysunset.annotation.service.UserService) context.getBean("userService");
userService.show();
/**
* 运行结果:
* show。。。
* userDaoImpl2 中的 add...
*/
}
Spring Aop
面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。
通俗描述:不通过修改源码方式,在主干功能里面添加新功能。
我们的程序有一个登录功能,通常在没有鉴权系统的登录模块会直接判断用户名和密码,如果都通过了,则直接放行进入系统,那么后期产品希望给程序加上一个新的功能-鉴权系统,方便程序自动区分哪些用户具有哪些权限,可以做哪些事情。这时我们可以使用AOP来解决,即通过不修改源代码的基础上,实现此功能。
AOP底层原理
AOP底层使用动态代理。
-
第一种,有接口的情况,使用JDK动态代理
创建接口实现类代理对象,增强类的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SUfoTVBH-1678844373961)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230313164254785.png)]
-
第二种,没有接口的情况,使用CGLIB动态代理
创建子类的代理对象,增强类的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pc2t3TNg-1678844373963)(C:\Users\ADMIN\AppData\Roaming\Typora\typora-user-images\image-20230313164307695.png)]
JDK动态代理的实现:使用Proxy类里面的方法创建代理对象
现在想要增强这个add方法
调用 newProxyInstance方法,方法有三个参数:
- 第一个参数:类加载器
- 第二个参数:增强方法所在的类,这个类实现的接口,支持多个接口
- 第三个参数:实现这个接口InvocationHandler,创建代理对象,写增强方法
AOP术语
-
连接点:类中可以被增强的方法,称为连接点。
-
切入点:实际被真正增强的方法,称为切入点。
-
通知(增强):实际增强的逻辑部分,称为通知。
通知有多种类型:前置通知(@Before):方法执行前进行
后置通知(@AfterReturning):return后执行
环绕通知(@Around):
异常通知(@AfterThrowing):异常后执行
最终通知(@After):最终必定执行
执行顺序:
around...before before... add...方法 around...after after... afterRetutning... around...before before... after... afterThrowing...
-
切面:是一个动作,把通知应用到切入点的过程。
Spring框架一般基于AspectJ实现AOP操作。
AspectJ不是Spring组成部分,一般把AspectJ和Spring框架一起使用,进行AOP操作。
##基于AspectJ实现AOP操作-两种方式
- 基于xml配置文件实现。
- 基于注解方式实现。(推荐,主流方式)
引入依赖
<!--AspectJ-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.8.M1</version>
</dependency>
##切入点表达式
- 切入点表达式的作用:用于对那个类里面的哪个方法进行增强。
- 语法结构:
execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
- 举例1:对com.atguigu.dao.BookDao类里面的add进行增强。//*表示权限任意,返回类型可不写
execution(* com.atguigu.dao.BookDao.add(.. ))
//*表示任意返回类型,访问权限可不写
- 举例2:对com.atguigu.dao.BookDao类里面的所有方法进行增强。
execution(* com.atguigu.dao.BookDao.*(..))
- 举例3:对com.atguigu.dao包里面的所有类,类里面的所有方法进行增强。
execution(* com.atguigu.dao.*.*(..))
AOP操作-注解方式
- 创建类,并在类中定义方法。
- 使用注解创建User类的bean对象。
@Component
public class User
{
public void add(){
System.out.println("add...");
}
}
1234567
3.创建配置类。
4.开启注解扫描。
5.开启AspectJ生成代理对象。
@Configuration
@ComponentScan("com.lonelysunset.aopanno")
@EnableAspectJAutoProxy(proxyTargetClass = true) // 开启AspectJ生成代理对象
public class SpringConfig
{
}
1234567
6.创建增强类。
7.编写增强逻辑。
8.在增强类中,在通知方法上面添加通知类型注解,使用切入点表达式进行配置。
@Component
@Aspect // 表示生成代理对象
public class UserProxy
{
// @Before注解表示前置通知
@Before(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void before(){
System.out.println("before...");
}
// @AfterReturning注解表示后置通知,表示切点返回结果时执行。如果有异常,则无法返回结果,不能执行。
@AfterReturning(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning...");
}
// @AfterThrowing注解表示异常通知,表示切入点有异常时,才执行,执行后。
@AfterThrowing(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing...");
}
// @Around注解表示环绕通知,表示在切入点前后环绕执行
@Around(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable
{
System.out.println("环绕之前...");
proceedingJoinPoint.proceed();
System.out.println("环绕之后...");
}
// @After注解表示最终通知,就算切入点有异常,也会执行
@After(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void after(){
System.out.println("after...");
}
}
123456789101112131415161718192021222324252627282930313233
9.测试结果。
public class TestAop
{
public static void main(String[] args)
{
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
User user = context.getBean("user", User.class);
user.add();
/**
* 运行结果:
* 环绕之前...
* before...
* add...
* 环绕之后...
* after...
* afterReturning...
*/
}
Others:使用xml扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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 http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/context/beans/spring-context.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="aoanno"></context:component-scan>
<!--开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
##相同的切入点抽取
参考以上现有代码进行抽取。
@Component
@Aspect // 表示生成代理对象
public class UserProxy
{
// 抽取相同的切入点
@Pointcut(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void pointDemo()
{
}
// @Before注解表示前置通知
@Before(value = "pointDemo()")
public void before()
{
System.out.println("before...");
}
// @AfterReturning注解表示后置通知,表示切点返回结果时执行。如果有异常,则无法返回结果,不能执行。
@AfterReturning(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void afterReturning()
{
System.out.println("afterReturning...");
}
// @AfterThrowing注解表示异常通知,表示切入点有异常时,才执行,执行后。
@AfterThrowing(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void afterThrowing()
{
System.out.println("afterThrowing...");
}
// @Around注解表示环绕通知,表示在切入点前后环绕执行
@Around(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable
{
System.out.println("环绕之前...");
proceedingJoinPoint.proceed();
System.out.println("环绕之后...");
}
// @After注解表示最终通知,就算切入点有异常,也会执行
@After(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void after()
{
System.out.println("after...");
}
}
##增强类的优先级
- 在增强类上面添加@Order(数字类型值),数字类型值越小,优先级越高。
@Component
@Aspect
@Order(1)
public class PersonProxy
{
@Before(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void personProxy(){
System.out.println("优先级最高的增强类...");
}
}
12345678910
- 测试结果。
public class TestAop
{
public static void main(String[] args)
{
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
User user = context.getBean("user", User.class);
user.add();
/**
* 运行结果:
* 优先级最高的增强类...
* 环绕之前...
* before...
* add...
* 环绕之后...
* after...
* afterReturning...
*/
}
}
AOP操作-基于xml
- 创建两个类,一个增强类和一个被增强类,然后创建对应的方法。
public class Book
{
public void buy(){
System.out.println("购买一本书...");
}
}
public class BookProxy
{
public void buyBookBefore(){
System.out.println("我得先摸摸口袋有没有钱");
}
}
2.在Spring配置文件中创建两个类的对象。
3.在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 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 id="book" class="com.lonelysunset.aopxml.Book">
</bean>
<bean id="bookProxy" class="com.lonelysunset.aopxml.BookProxy"/>
<!--配置aop增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.lonelysunset.aopxml.Book.buy(..))"/>
<!--配置切面-->
<!--什么是配置切面?将通知引用在切入点上,就是切面-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上-->
<!--简单理解:通知作用在切入点,这里配置的是前置通知-->
<aop:before method="buyBookBefore" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
</beans>
4.测试结果:
@Test
public void test21(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean25.xml");
com.lonelysunset.aopxml.Book book = context.getBean("book", com.lonelysunset.aopxml.Book.class);
book.buy();
/**
* 运行结果:
* 我得先摸摸口袋有没有钱
* 购买一本书...
*/
}
补充
这视频没讲cglib
JDBC Template
Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作
1、导入依赖
<!-- jdbcTemplate-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.27</version>
</dependency>
<!-- orm-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- tx-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
##2、准备工作
(1)在 spring 配置文件配置数据库连接池
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/user_db"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
(2)配置 JdbcTemplate 对象,注入 DataSource
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入jdbc源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
(3)创建 service 类,创建 dao 类,在 dao 注入 jdbcTemplate 对象
* 配置文件
<!-- 组件扫描 -->
<context:component-scan base-package="dao,service"></context:component-scan>
⚫ Service
@Service
public class BookService {
//注入 dao
@Autowired
private BookDao bookDao;
}
⚫ Dao
@Repository
public class BookDaoImpl implements BookDao {
//注入 JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
##JdbcTemplate 操作数据库(添加)
1、对应数据库创建实体类
2、编写 service 和 dao
@Repository
public class BookDaoImpl implements BookDao {
//注入 JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
//添加的方法
@Override
public void add(Book book) {
//1 创建 sql 语句
String sql = "insert into t_book values(?,?,?)";
//2 调用方法实现
Object[] args = {book.getUserId(), book.getUsername(),
book.getUstatus()};
int update = jdbcTemplate.update(sql,args); System.out.println(update);
}
}
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.
3、测试类
@Test
public void testJdbcTemplate() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService",
BookService.class);
Book book = new Book();
book.setUserId("1");
book.setUsername("java");
book.setUstatus("a");
bookService.addBook(book);
}
1.2.3.4.5.6.7.8.9.10.11.12.
##JdbcTemplate 操作数据库(修改和删除)
1、修改
@Override
public void updateBook(Book book) {
String sql = "update t_book set username=?,ustatus=? where user_id=?";
Object[] args = {book.getUsername(), book.getUstatus(),book.getUserId()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
1.2.3.4.5.6.7.
2、删除
@Override
public void delete(String id) {
String sql = "delete from t_book where user_id=?";
int update = jdbcTemplate.update(sql, id);
System.out.println(update);
}
1.2.3.4.5.6.
##JdbcTemplate 操作数据库(查询返回某个值)
//查询表记录数
@Override
public int selectCount() {
String sql = "select count(*) from t_book";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
1.2.3.4.5.6.7.
##JdbcTemplate 操作数据库(查询返回对象)
##JdbcTemplate 操作数据库(查询返回集合)
##JdbcTemplate 操作数据库(批量操作)
1、批量操作:操作表里面多条记录
2、JdbcTemplate 实现批量添加操作
//批量添加
@Override
public void batchAddBook(List<Object[]> batchArgs) {
String sql = "insert into t_book values(?,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
//批量添加测试
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3","java","a"};
Object[] o2 = {"4","c++","b"};
Object[] o3 = {"5","MySQL","c"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
//调用批量添加
bookService.batchAdd(batchArgs);
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.
3、JdbcTemplate 实现批量修改操作
//批量修改
@Override
public void batchUpdateBook(List<Object[]> batchArgs) {
String sql = "update t_book set username=?,ustatus=? where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
//批量修改
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"java0909","a3","3"};
Object[] o2 = {"c++1010","b4","4"};
Object[] o3 = {"MySQL1111","c5","5"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
//调用方法实现批量修改
bookService.batchUpdate(batchArgs);
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.
4、JdbcTemplate 实现批量删除操作
复制
//批量删除
@Override
public void batchDeleteBook(List<Object[]> batchArgs) {
String sql = "delete from t_book where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
//批量删除
List<Object[]> batchArgs = new ArrayList<>();Object[] o1 = {"3"};
Object[] o2 = {"4"};
batchArgs.add(o1);
batchArgs.add(o2);
//调用方法实现批量删除
bookService.batchDelete(batchArgs);
#声明式事务
##事务概念
1、什么事务
(1)事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操
作都失败
(2)典型场景:银行转账
- lucy 转账 100 元 给 mary
- lucy 少 100,mary 多 100
2、事务四个特性(ACID)
(1)原子性:
原子性是指事务是一个不可再分割的工作单元,事务中的操作要么都发生,要么都不发生。
可采用“A向B转账”这个例子来说明解释
在DBMS中,默认情况下一条SQL就是一个单独事务,事务是自动提交的。只有显式的使用start transaction开启一个事务,才能将一 个代码块放在事务中执行。
(2)一致性:
一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
如A给B转账,不论转账的事务操作是否成功,其两者的存款总额不变(这是业务逻辑的一致性,至于数据库关系约束的完整性就更好理解了)。
保障机制(也从两方面着手):数据库层面会在一个事务执行之前和之后,数据会符合你设置的约束(唯一约束,外键约束,check约束等)和触发器设置;此外,数据库的内部数据结构(如 B 树索引或双向链表)都必须是正确的。业务的一致性一般由开发人员进行保证,亦可转移至数据库层面。
(3)隔离性:
多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。
在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
事务最复杂问题都是由事务隔离性引起的。完全的隔离性是不现实的,完全的隔离性要求数据库同一时间只执行一条事务,这样会严重影响性能。
关于隔离性中的事务隔离等级(事务之间影响),参见相应下文
(4)持久性:
这是最好理解的一个特性:持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。(完成的事务是系统永久的部分,对系统的影响是永久性的,该修改即使出现致命的系统故障也将一直保持)
write ahead logging:SQL Server中使用了WAL(Write-Ahead Logging)技术来保证事务日志的ACID特性,在数据写入到数据库之前,先写入到日志,再将日志记录变更到存储器中。
##搭建事务操作环境
1、创建数据库表,添加记录
2、创建 service,搭建 dao,完成对象创建和注入关系
(1)service 注入 dao,在 dao 注入 JdbcTemplate,在 JdbcTemplate 注入 DataSource
@Service
public class UserService {
//注入 dao
@Autowired
private UserDao userDao;
}
@Repository
public class UserDaoImpl implements UserDao {
@Autowired private JdbcTemplate jdbcTemplate;
}
3、在 dao 创建两个方法:多钱和少钱的方法,在 service 创建方法(转账的方法)
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//lucy 转账 100 给 mary
//少钱
@Override
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update(sql,100,"lucy");
}
//多钱
@Override
public void addMoney() {
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update(sql,100,"mary");
}
}
@Service
public class UserService {
//注入 dao
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney() {
//lucy 少 100
userDao.reduceMoney();
//mary 多 100
userDao.addMoney();
}
}
**4、上面代码,如果正常执行没有问题的,但是如果代码执行过程中出现异常,有问题
(1)上面问题如何解决呢?
- 使用事务进行解决
(2)事务操作过程
##Spring 事务管理介绍
1、事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
2、在 Spring 进行事务管理操作
有两种方式:编程式事务管理和声明式事务管理(使用)
3、声明式事务管理
(1)基于注解方式(使用)
(2)基于 xml 配置文件方式
4、在 Spring 进行声明式事务管理,底层使用 AOP 原理
5、Spring 事务管理 API
提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
mysql一般用DataSourcexxxxxx实现类
##注解-声明式事务管理
1、在 spring 配置文件配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
2、在 spring 配置文件,开启事务注解
(1)在 spring 配置文件引入名称空间 tx
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
(2)开启事务注解
<!--开启事务注解-->
<tx:annotation-driven transactionmanager="transactionManager"></tx:annotation-driven>
3、在 service 类上面(或者 service 类里面方法上面)添加事务注解
(1)@Transactional,这个注解添加到类上面,也可以添加方法上面
(2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务
(3)如果把这个注解添加方法上面,为这个方法添加事务
@Service
@Transactional
public class UserService
##注解-声明式事务管理参数配置
在 service 类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数
###propagation:事务传播行为
多事务方法直接进行调用,这个过程中事务 是如何进行管理的
- REQUIRED:【合体】如果当前(上层调用者方法)没有事务,则自己新建一个事务,如果当前(上层调用者方法)存在事务,则加入这个事务。spring默认使用该传播类型。
- REQUIRES_NEW:【分成内外事务分别执行,嵌套执行】无论当前有没有事务,都会新建事务
- SUPPORTS:【右边文字很清楚,视频讲得依托答辩,早知道看老杜的了,搭配上面两个使用】如果有事务在运行,当前方法就在这个事务内运行,否则它可以不运行在事务中。
###ioslation:事务隔离级别
事务特性之一隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
有三个读问题:脏读、不可重复读、虚(幻)读
**(1)脏读:**一个未提交事务读取到另一个未提交事务的数据
事务A 事务B
use bjpowernode;
use bjpowernode;
start transaction;
select * from t_user;
start transaction;
insert into t_user values(‘zhangsan’);
select * from t_user;
mysql> select * from t_user;
+------+------+------------+---------------------+
| id | name | birth | create_time |
+------+------+------------+---------------------+
| 2 | abc | 2000-10-11 | 2023-01-22 17:46:43 |
| 1 | abc | 1990-10-01 | 2023-01-22 17:47:25 |
+------+------+------------+---------------------+
2 rows in set (0.01 sec)
mysql> select * from t_user;
+------+----------+------------+---------------------+
| id | name | birth | create_time |
+------+----------+------------+---------------------+
| 2 | abc | 2000-10-11 | 2023-01-22 17:46:43 |
| 1 | abc | 1990-10-01 | 2023-01-22 17:47:25 |
| NULL | zhangsan | NULL | NULL |
+------+----------+------------+---------------------+
3 rows in set (0.00 sec)
**(2)不可重复读:**一个未提交事务读取到另一提交事务修改数据
【花了一节课时间将,甚至画图了,还讲得那么烂,b友还那么维护这老师?要我是真花钱的,我真就退款了,还是看看以前做的笔记吧,来自动力节点的老师】
mysql> set global transaction isolation level read committed;
事务A 事务B
use bjpowernode;
use bjpowernode;
start transaction;
start transaction;
select * from t_user;
insert into t_user values(‘zhangsan’);
select * from t_user;
commit;
select * from t_user;
mysql> select * from t_user;
+------+----------+------------+---------------------+
| id | name | birth | create_time |
+------+----------+------------+---------------------+
| 2 | abc | 2000-10-11 | 2023-01-22 17:46:43 |
| 1 | abc | 1990-10-01 | 2023-01-22 17:47:25 |
| NULL | zhangsan | NULL | NULL |
+------+----------+------------+---------------------+
3 rows in set (0.00 sec)
mysql> select * from t_user;
+------+----------+------------+---------------------+
| id | name | birth | create_time |
+------+----------+------------+---------------------+
| 2 | abc | 2000-10-11 | 2023-01-22 17:46:43 |
| 1 | abc | 1990-10-01 | 2023-01-22 17:47:25 |
| NULL | zhangsan | NULL | NULL |
+------+----------+------------+---------------------+
3 rows in set (0.00 sec)
mysql> select * from t_user;
+------+--------------+------------+---------------------+
| id | name | birth | create_time |
+------+--------------+------------+---------------------+
| 2 | abc | 2000-10-11 | 2023-01-22 17:46:43 |
| 1 | abc | 1990-10-01 | 2023-01-22 17:47:25 |
| NULL | zhangsan | NULL | NULL |
| NULL | dengshengjun | NULL | NULL |
+------+--------------+------------+---------------------+
4 rows in set (0.00 sec)
(3)虚读/幻读:一个未提交事务读取不到另一添加数据的提交事务
mysql> set global transaction isolation level repeatable read;
事务A 事务B
use bjpowernode;
use bjpowernode;
start transaction;
start transaction;
select * from t_user;
insert into t_user values(‘lisi’);
insert into t_user values(‘wangwu’);
commit;
select * from t_user;
mysql> select * from t_user;
+------+--------------+------------+---------------------+
| id | name | birth | create_time |
+------+--------------+------------+---------------------+
| 2 | abc | 2000-10-11 | 2023-01-22 17:46:43 |
| 1 | abc | 1990-10-01 | 2023-01-22 17:47:25 |
| NULL | zhangsan | NULL | NULL |
| NULL | dengshengjun | NULL | NULL |
+------+--------------+------------+---------------------+
4 rows in set (0.00 sec)
mysql> select * from t_user;
+------+--------------+------------+---------------------+
| id | name | birth | create_time |
+------+--------------+------------+---------------------+
| 2 | abc | 2000-10-11 | 2023-01-22 17:46:43 |
| 1 | abc | 1990-10-01 | 2023-01-22 17:47:25 |
| NULL | zhangsan | NULL | NULL |
| NULL | dengshengjun | NULL | NULL |
+------+--------------+------------+---------------------+
4 rows in set (0.00 sec)
###解决:通过设置事务隔离级别,解决读问题
###其它参数
timeout:超时时间
(1)事务需要在一定时间内进行提交,如果不提交进行回滚
(2)默认值是 -1 无限制,设置时间以秒单位进行计算
readOnly:是否只读
(1)读:查询操作,写:添加修改删除操作
(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作
(3)设置 readOnly 值是 true,设置成 true 之后,只能查询
rollbackFor:回滚
(1)设置出现哪些异常进行事务回滚
(2)使用:Exception.class
noRollbackFor:不回滚
(1)设置出现哪些异常不进行事务回滚
xml-声明时事务管理
第一步,配置事务管理器
第二步,配置通知
第三步,配置切入点和切面
<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
<tx:method name="account*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.company.spring5.service.PersonService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
##完全注解配置
@Configuration //配置类
@ComponentScan(basePackages = "com.company") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/db4?useSSL=false&characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("qwer`123");
return dataSource;
}
//创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//到ioc容器中根据类型找到dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
Spring5新特性
日志框架整合
Spring5框架自带了通用的日志封装。
Spring5移除了Log4jConfigListener,官方建议使用Log4j2.
第一步,引入依赖,基于maven(35条消息) maven项目添加log4j2_maven引入log4j2_飞上天的橘子的博客-CSDN博客
<!-- log4j 新版本依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>2.9.1</version>
</dependency>
第二步,在资源目录创建log4j2.xml(文件名固定)
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
<!--先定义所有的appender-->
<appenders>
<!--输出日志信息到控制台-->
<console name="Console" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
<loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
@Nullable注解和函数式风格
@Nullable注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空。
(1)注解用在方法上面,方法返回值可以为空。
(2)注解用在方法参数里面,方法参数可以为空。
(3)注解使用在属性上面,属性值可以为空。
弹幕说可以防止Spring注入失败或创建对象失败
函数式风格GenericApplicationContext
Spring5核心容器支持函数式风格创建对象,交给Spring进行管理。
@Test
public void test5() {
//1 创建GenericApplicationContext对象
GenericApplicationContext context = new GenericApplicationContext();
//2 调用context的方法对象注册
context.refresh();
context.registerBean("user1", User.class, ()->new User());
//3 获取在spring注册的对象
User user = (User) context.getBean("user1");
System.out.println(user);
}
JUnit5测试
Spring5整合Junit5
第一步,添加依赖
<!-- spring test-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
第二步,测试
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean7.xml")
public class Test5 {
@Autowired
private PersonService personService;
@Test
public void test() {
personService.accountMoney();
}
}
这样省去了之前创建ApplicationContext对象和getBean的操作。
简化,使用一个复合注解替代上面两个注解
小知识
@Bean
来源:@Bean 注解全解析 - 程序员cxuan - 博客园 (cnblogs.com)
Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。
SpringIOC 容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建,在一个方法上使用@Bean注解就表明这个方法需要交给Spring进行管理。
快速搭建一个maven项目并配置好所需要的Spring 依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
在src根目录下创建一个AppConfig
的配置类,这个配置类也就是管理一个或多个bean 的配置类,并在其内部声明一个myBean的bean,并创建其对应的实体类
@Configuration
public class AppConfig {
// 使用@Bean 注解表明myBean需要交给Spring进行管理
// 未指定bean 的名称,默认采用的是 "方法名" + "首字母小写"的配置方式
@Bean
public MyBean myBean(){
return new MyBean();
}
}
public class MyBean {
public MyBean(){
System.out.println("MyBean Initializing");
}
}
在对应的test文件夹下创建一个测试类SpringBeanApplicationTests
,测试上述代码的正确性
public class SpringBeanApplicationTests {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.getBean("myBean");
}
}
输出 : MyBean Initializing
随着SpringBoot的流行,我们现在更多采用基于注解式的配置从而替换掉了基于XML的配置,所以本篇文章我们主要探讨基于注解的@Bean以及和其他注解的使用。