文章目录
Spring5学习【狂神说java】
1、简介
- Spring:春天----》给软件行业带来了春天
- 2002,首次推出了Spring框架
- Spring框架即以
interface21
框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版。 - Rod Johnson,Spring Framework创始人,著名作者。很难想象他的学历,悉尼大学博士,专业是音乐学。
- Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架!
- SSH:Struct2 + Spring + Hibernate!
- SSM:SpringMVC + Spring + Mybatis!
maven 依赖
<!-- 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-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
1.2 、优点
- Spring是一个开源的免费的框架(容器)!
- Spring是一个轻量级的、非入侵式的框架!
- 控制反转(IOC),面向切面编程(AOP)!
- 支持事务的处理,对框架整合的支持!
总结一句话:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!
1.3、组成
1.4、拓展
现代化的Java开发!说白就是基于Spring的开发!
- Spring Boot
- 一个快速开发的脚手架。
- 基于SpringBoot可以快速的开发单个微服务。
- 约定大于配置。
- Spring Cloud
- SpringCloud是基于SpringBoot实现的。
因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring
及SpringMVC
承上启下的作用!
弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱!”
2、IOC(容器)
2.1、IOC理论推导
- UserDao 接口
public interface UserDao {
public void getUser();
}
- UserDaoImpl 实现类
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("获取默认用户数据!");
}
}
- UserService 业务接口
public interface UserService {
public void getUser();
}
- UserServiceImpl 业务实现类
public class UserServiceImpl implements UserService{
private UserDao userDao=new UserDaoMysqlImpl();//
@Override
public void getUser() {
userDao.getUser();
}
}
- 测试
public class UserTest {
public static void main(String[] args) {
//用户实际调用的是业务层,dao层他们不需要接触!
UserServiceImpl userService =new UserServiceImpl();
userService.getUser();
}
}
在这里,其实是有个问题的,比如你在dao包里面在新增一个UserDaoMysqlImpl类,然后实现UserDao,当你想用这个的时候,你必须要在业务层改动,把private UserDao userDao=new UserDaoImpl();
改成private UserDao userDao=new UserDaoMysqlImpl();
用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!如果程序代码量十分大,修改一次的成本代价十分昂贵!
图示
改进
private UserDao userDao;
//利用set进行动态实现值的注入!
public void setUserDao(UserDao userDao) {//程序主动权在用户
this.userDao = userDao;
}
测试
public class UserTest {
public static void main(String[] args) {
//用户实际调用的是业务层,dao层他们不需要接触!
UserServiceImpl userService =new UserServiceImpl();
userService.setUserDao(new UserDaoImpl());
userService.getUser();
}
}
- 之前,程序是主动创建对象!控制权在程序猿手上!
- 使用了set注入后,程序不再具有主动性,而是变成了被动的接收对象!
这种思想,从本质上解决了问题,我们程序猿不用再去管理对象的创建了。系统的耦合性大大降低~,可以更加专注的在业务的实现上!这是IOC的原型!
图示
2.2、IOC本质
控制反转IoC(Inversion of Control),是一种设计思想,**DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。**没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
2.2.1、IOC底层原理
- xml解析
- 工厂模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GUyyuPsp-1658555387326)(D:\杂乱文件\Typora\MySQL学习\spring\ioc过程1.png)]
- 反射
2.2.2、IOC接口
-
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
-
Spring提供IOC容器实现的两种方式:(两个接口)
BeanFactory
:IOC容器基本实现,是Spring内部的使用接口,不提供开发人员使用ApplicationContext:BeanFactory
接口的子接口,提供跟多更强大的功能,一般由开发人员使用
-
Application接口的实现类
FileSystemXmlApplicationContext
【要加载的文件位置】ClassPathXMLApplicationContext
【加载类的位置】
拓展:BeanFactory
与ApplicationContex
区别
- 前者:加载配置文件的时候不会创建对象,在获取对象(使用)才去创建对象
- 后者【推荐】:加载配置文件时候就会在配置文件的对象就会进行创建
2.3、HelloSpring
- 新建maven模块,编写实体类
package com.mr.ming.dao;
/**
* @author: Mr.ming
* @describe: NULL
* @create: 2022/4/16-13:09
*/
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
- 写配置文件
<?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">
<!--使用Spring来创建对象,在Spring这些都称为Bean
类型 变量名 = new 类型();
Hello hello = new Hello();
id = 变量名
class = new的对象
property 相当于给对象中的属性设置一个值!
-->
<bean id="hello" class="com.mr.ming.dao.Hello">
<property name="str" value="spring"></property>
</bean>
</beans>
- 测试类
import com.mr.ming.dao.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author: Mr.ming
* @describe: NULL
* @create: 2022/4/16-13:11
*/
public class HelloTest {
public static void main(String[] args) {
//获取Spring的上下文对象!
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象现在都在Spring中的管理了,我们需要使用,直接去里面取出来就可以!
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
}
}
思考问题?
-
Hello对象是谁创建的?
- Hello对象是由Spring创建的。
-
Hello对象的属性是怎么设置的?
- Hello对象的属性是由Spring容器设置的。
这个过程就叫控制反转:
控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。
反转:程序本身不创建对象,而变成被动的接收对象。
依赖注入:就是利用set方法
来进行注入的。
IOC是一种编程思想,由主动的编程变成被动的接收。
可以通过new ClassPathXmlApplicationContext
去浏览一下底层源码。
OK,到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,一句话搞定:对象由Spring来创建,管理,装配!
2.4、Bean管理
什么时Bean管理
- Bean管理指的是两个操作
- sping创建对象
- spring注入属性
Bean管理操作有两种方式
- 基于xml配置文件实现
<bean id="userDaoMysqlImpl" class="com.mr.ming.dao.UserDaoMysqlImpl"/>
<!--创建对象时,默认执行无参数构造器-->
- 基于注解方式实现
- 依赖注入,就是注入属性
- 第一种:set方法注入
- 第二种:有参构造器注入
- 依赖注入,就是注入属性
2.4.1、IOC创建对象方式
- 使用无参构造器创建对象,默认!
- 假设我们要使用有参构造器创建对象
- 下标赋值
<bean id="userDaoImpl" class="com.mr.ming.dao.UserDaoImpl">
<constructor-arg index="0" value="铭先生学java"/>
</bean>
2. 类型创建【通过类型创建,不建议使用】
<bean id="userDaoImpl" class="com.mr.ming.dao.UserDaoImpl">
<constructor-arg type="java.lang.String" value="铭先生学java"/>
</bean>
3. 参数名创建
<bean id="userDaoImpl" class="com.mr.ming.dao.UserDaoImpl">
<constructor-arg name="name" value="铭先生学java"/>
<constructor-arg name="pwd" value="123"/>
</bean>
总结:
在配置文件加载的时侯,容器中的管理对象就已经初始化了!
2.4.2、IOC操作Bean管理(FactoryBean)
-
Spring有两种类型bean,一种是普通bean,另一种工厂bean(FactoryBean)
- 普通bean:在配置文件中定义bean类型就是返回类型
- 工厂bean:在配置文件定义bean类型可以返回类型不一样
- 第一步:创建工厂bean类(实现FactoryBean)
- 第二步:实现接口里面的方法,在实现的方法中定义返回的bean类型
代码演示
- 工厂bean
public class MyBean implements FactoryBean{
@Override
public Object getObject() throws Exception {
Student student =new Student();
student.setName("铭先生学java");
return student;
}
@Override
public Class<?> getObjectType() {
return null;
}
}
- 实体类
public class Student {
private String name;
private String pwd;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
- 配置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">
<bean id="myBean" class="com.mr.ming.dao.MyBean"/>
</beans>
- 测试类
public class HelloTest {
public static void main(String[] args) {
//获取Spring的上下文对象!
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象现在都在Spring中的管理了,我们需要使用,直接去里面取出来就可以!
// Hello hello = (Hello) context.getBean("hello");
Student myBean = context.getBean("myBean", Student.class);
System.out.println(myBean.toString());
}
}
3、Spring配置
3.1、别名
<!--别名,如果添加了别名,我们也可以使用别名获取到这个对象-->
<alias name="userDaoImpl" alias="user"/>
3.2、Bean的配置
<!--
id:bean的唯一标识符,也就是相当于我们学的对象名
class:bean对象所对应的全限定名:包名+类名
name:也是别名,而且name可以同时取多个别名;
空格 逗号 分号隔开也没问题
-->
<bean id="userDaoImpl" class="com.mr.ming.dao.UserDaoImpl" name="u1 u2,u3;u4">
<constructor-arg name="name" value="铭先生学java"/>
<constructor-arg name="pwd" value="123"/>
</bean>
3.3、import
这个import
,一般用于团队的开发,它可以将多个配置文件,导入合并为一个。假设,现在项目中有多个人开发,这三个人负责不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import
将所有的bean.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="beans.xml"/>
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
</beans>
温馨提示:使用的时候,直接使用总的配置就可以了!
4、依赖注入
4.1、构造器注入
2.4已记录!
4.2、Set方式注入【重点】
- 依赖注入:Set注入
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象的所有属性,有容器来注入
环境搭建
- 复杂类型
package com.mr.ming.dao;
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
- 真实测试对象
package com.mr.ming.dao;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String[] getBooks() {
return books;
}
public void setBooks(String[] books) {
this.books = books;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public Map<String, String> getCard() {
return card;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public Set<String> getGames() {
return games;
}
public void setGames(Set<String> games) {
this.games = games;
}
public String getWife() {
return wife;
}
public void setWife(String wife) {
this.wife = wife;
}
public Properties getInfo() {
return info;
}
public void setInfo(Properties info) {
this.info = 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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.mr.ming.dao.Student">
<property name="name" value="铭先生"/>
</bean>
</beans>
- 测试类
import com.mr.ming.dao.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author: Mr.ming
* @describe: NULL
* @create: 2022/4/19-8:36
*/
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getName());
}
}
- 完善注入信息
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.mr.ming.dao.Address">
<!-- <property name="address" value="广州"/>-->
</bean>
<bean id="student" class="com.mr.ming.dao.Student">
<!-- 第一种注入:普通值注入,value-->
<property name="name" value="铭先生"/>
<!-- 第二种:级联赋值-->
<property name="address" ref="address"/>
<property name="address.address" value="北京"/>
<!-- address类里的address属性的值,这里的address不大写是因为name设置了这个address-->
<!-- 这个还可以内部嵌套bean的方式-->
<!-- <property name="address">-->
<!-- <bean id="address" class="com.mr.ming.dao.Address">-->
<!-- <property name="address" value="广州"/>-->
<!-- </bean>-->
<!-- </property>-->
<!-- 第三种:数组注入-->
<property name="books">
<array>
<value>活着</value>
<value>理想国</value>
<value>百年孤独</value>
</array>
</property>
<!-- 第四种:集合注入-->
<!-- 4.1 List-->
<property name="hobbies">
<list>
<value>写代码</value>
<value>看电影</value>
<value>打篮球</value>
</list>
</property>
<!-- 4.2 map-->
<property name="card">
<map>
<entry key="身份证" value="154324324534316"/>
<entry key="银行卡" value="154201534245321"/>
</map>
</property>
<!-- 4.3 Set注入-->
<property name="games">
<set>
<value>LOL</value>
<value>CF</value>
<value>DNF</value>
</set>
</property>
<!--null-->
<property name="wife">
<null/>
</property>
<!--特殊字符
1.进行特殊字符转义
2.把带特殊符号的内容写到CDATA中
-->
<property name="strData">
<value><![CDATA[<<南京>>]]></value>
</property>
<!-- Properties-->
<property name="info">
<props>
<prop key="driver">20191201</prop>
<prop key="url">201.1201.542.5682</prop>
<prop key="user">铭先生</prop>
<prop key="password">32323</prop>
</props>
</property>
</bean>
</beans>
4.3、拓展方式注入
我们可以使用p命名空间和c命名空间进行注入
代码演示
- 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: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命名空间注入,可以直接注入属性的值:property-->
<bean id="user" class="com.mr.ming.dao.User" p:name="铭先生" p:age="18"/>
<!--c命名空间注入,通过构造器注入:constructor-args-->
<bean id="user2" class="com.mr.ming.dao.User" c:name="铭先生学java" c:age="18"/>
</beans>
- 测试
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
}
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
User user2 = context.getBean("user2", User.class);
System.out.println(user2);
}
注意点:p命名
和c命名空间
不能直接使用,需要导入xml
约束
4.4、bean的作用域
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KoDFXyYC-1658555387327)(D:\杂乱文件\Typora\MySQL学习\spring\bean_scope.png)]
单例模式
- 单例模式(Spring默认机制)
<bean id="user" class="com.mr.ming.dao.User" p:name="铭先生" p:age="18" scope="singleton"/>
- 测试
ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
User user = context.getBean("user", User.class);
User user1 = context.getBean("user", User.class);
System.out.println(user == user1);//true 单例模式下
原型模式(多实例模式)
- 原型模式:每次从容器中get的时候,都会产生一个新对象!
<bean id="user2" class="com.mr.ming.dao.User" c:name="铭先生学java" c:age="18" scope="prototype"/>
- 测试
ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
User user2 = context.getBean("user2", User.class);
User user3 = context.getBean("user2", User.class);
System.out.println(user2 ==user3);//false 原型模式
singleton和prototype区别
- 前者是单实例,prototyoe是多实例
- 设置scope值是singleton时候,加载spring配置文件时就会创建对象;而设置prototype的时候,不是再在spring配置文件的时候创建,在调用getBean方法时创建多实例对象
其他的scope 值
- request
- session
- application
这些只能在web开发中使用到!
4.4、Bean的生命周期
生命周期:从对象创建到对象销毁的过程
bean生命周期
- 通过构造器创建bean实例(无参构造器)
- 为bean的属性设置值和其他bean引用(调用set方法)
- 调用bean的初始化方法(需要进行配置初始化方法)
- bean可以使用了(对象获取到了)
- 当容器关闭的时候,调用bean销毁的方法(需要进行配置销毁的方法)
代码演示
- 实体类
public class Person {
public Person() {
System.out.println("第一步:执行无参构造创建bean实例");
}
private String name;
public void setName(String name) {
System.out.println("第二部:为bean的属性设置值和对其他bean的引用");
this.name = name;
}
public void initMethod(){
System.out.println("第三部:调用bean的初始化方法(需要进行配置初始化方法)");
}
public void destroyMethod(){
System.out.println("第五步:当容器关闭的时候,调用bean销毁的方法(需要进行配置销毁的方法)");
}
}
- 配置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">
<bean id="person" class="com.mr.ming.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="神"/>
</bean>
</beans>
- 测试类
public class MyTest {
public static void main(String[] args) {
FileSystemXmlApplicationContext fsac = new FileSystemXmlApplicationContext("D:\\javaLearnProjects\\spring5NewLearn\\spring-09-BeanLifeTime\\src\\main\\resources\\bean.xml");
Person person = fsac.getBean("person", Person.class);
System.out.println("第四步:获取对象:"+person);
fsac.close();
}
}
拓展:七步生命周期
- 通过构造器创建bean实例(无参构造器)
- 为bean的属性设置值和其他bean引用(调用set方法)
- 把 bean实例传递bean后置处理器的方法
postProcessBeforeInitialization
- 调用bean的初始化方法(需要进行配置初始化方法)
- 把 bean实例传递bean后置处理器的方法
postProcessAfterInitialization
- bean可以使用了(对象获取到了)
- 当容器关闭的时候,调用bean销毁的方法(需要进行配置销毁的方法)
代码演示
public class Person {
public Person() {
System.out.println("第一步:执行无参构造创建bean实例");
}
private String name;
public void setName(String name) {
System.out.println("第二部:为bean的属性设置值和对其他bean的引用");
this.name = name;
}
public void initMethod(){
System.out.println("第四部:调用bean的初始化方法(需要进行配置初始化方法)");
}
public void destroyMethod(){
System.out.println("第七步:当容器关闭的时候,调用bean销毁的方法(需要进行配置销毁的方法)");
}
}
- 实现
BeanPostProcessor
接口完成后置处理器类
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第三步:把bean的实例传递给bean后置处理器postProcessBeforeInitialization");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第五步:把bean的实例传递给bean后置处理器postProcessAfterInitialization");
return 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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.mr.ming.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="神"/>
</bean>
<!-- 配置bean的后置处理器
在xml离注册的所有bean类都会配置上这个后置处理器
-->
<bean id="myBeanPost" class="com.mr.ming.bean.MyBeanPost"/>
</beans>
- 测试类同上
5、Bean的自动装配
- 自动装配是spring满足bean依赖的一种方式。
- spring会在上下文中自动寻找,并自动给bean装配属性!
spring 三种装配方式
- 在xml中显式配置
- 在java中显式配置
- 隐式的自动装配bean【重点】
5.1、测试
环境搭建:创建项目,一个人有两个宠物!
<bean id="cat" class="com.mr.ming.pojo.Cat"/>
<bean id="dog" class="com.mr.ming.pojo.Dog"/>
<bean id="person" class="com.mr.ming.pojo.Person">
<property name="name" value="铭先生"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
5.2、ByName自动装配
<!-- byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean的id!-->
<bean id="person" class="com.mr.ming.pojo.Person" autowire="byName">
<property name="name" value="铭先生"/>
</bean>
5.3、ByType自动装配
<!-- byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean!-->
<bean id="person" class="com.mr.ming.pojo.Person" autowire="byType">
<property name="name" value="铭先生"/>
</bean>
小结:
- ByName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致(属性名称一一样)!
- ByType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
拓展:xml配置连接池
-
直接设置属性
-
<!-- 直接设置属性:直接配置连接池 1.导入德鲁伊的jar包或者依赖 2.配置xml --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/smbms?useUnicode=true&characterEncoding=utf8&useSSL=true"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean>
-
-
外部属性文件配置
-
<!-- 外部属性文件引入 [推荐] 1.导入德鲁伊的jar包或者依赖 2.配置xml: - 引入xmlns:context="http://www.springframework.org/schema/context" -http://www.springframework.org/schema/context https://www.springframework.org/schemacontext/spring-context.xsd" 3.使用<context:property-placeholder location="db.properties"/> 4.EL表达式获取 --> <context:property-placeholder location="db.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driver}"/> <property name="url" value="${prop.url}"/> <property name="username" value="${prop.username}"/> <property name="password" value="${prop.password}"/> </bean> </beans>
-
5.4、使用注解实现自动装配
jdk1.5支持注解,spring2.5就支持注解了!
使用注解须知:
- 导入约束
- 配置注解的支持
<?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>
5.4.1、@Autowired
直接在属性上使用即可!也可以在set方法上使用!
使用Autowired我们就可以不用编写set方法了,前提是你这个自动配置的属性在IOC容器存在,且符合名字ByName!
拓展:
@Nullable
字段标记了这个注解,说明这个字段为null- 如果显式定义了
Autowired
的required
属性为```false``,说明这个对象可以为null,否则不允许为空
public @interface Autowired {//默认为true
boolean required() default true;
}
- 演示:
public class People {
//如果显式定义了Autowired的required属性为false,说明这个对象可以为null,否则不允许为空
@Autowired(required = false)
private Cat cat;
@Autowired
private Dog dog;
private String name;
}
- 如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候,我们可以使用@Qualifier(value = “xxx”)去配置@Autowired的使用,指定一个唯一的bean对象注入!
public class Person {
@Autowired
@Qualifier(value = "cat11")
private Cat cat;
@Autowired
@Qualifier(value = "dog22")
private Dog dog;
private String name;
}
@Autowired原理:
举例:@Autowired
private BookService bookService;
-
先按照类型去容器中找到对应的组件;
bookService = context.getBean(BookService.class)
- ①、找到一个:找到就赋值
- ②、没找到就报异常
- ③、按照类型可以找到多个?找到多个如何装配上?
-
a、类型一样就按照变量名为ID继续匹配
- Ⅰ、匹配上就装配
- Ⅱ、没有匹配?报错
-
原因:因为我们按照变量名作为id继续匹配的
-
使用@Qualifier指定一个新的id
-
找到就匹配
5.4.2、@Resource
- 导入jar包或写入maven依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.2</version>
</dependency>
- 演示
public class Person {
@Resource(name="cat11") //名字匹配 可以 名字不一样,类型匹配 可以
private Cat cat;
@Resource(name="dog11")
private Dog dog;
private String name;
小结
@Resource
和@Autowired
的区别
- 都是用来自动装填的,都可以放在属性字段上
@Autowired
通过byType
的方式实现,而且必须要求这个对象存在!【常用】@Resource
默认通过byName
的方式实现,如果找不到名字,则通过byType实现!如果这两个都找不到的情况下,就报错!【常用】- 执行顺序不同:
@Autowired
通过byType
的方式实现
6、使用注解开发
什么是注解
- 注解是代码特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值……)
- 使用注解,注解作用在类上面,方法上面,属性上面
- 使用注解目的:简化xml配置
- 在Spring4之后,要使用注解开发,必须要导入aop包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AZa3apbs-1658555387327)(D:\杂乱文件\Typora\MySQL学习\spring\注解开发需要的包.png)]
使用注解需要导入约束,配置注解的支持!
<?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">
<!--开启组件扫描
1.如果扫描多个包,可以有逗号把多个隔开
2.扫描包上层目录
-->
<context:component-scan base-package="com.mr.ming"/>
<context:annotation-config/>
</beans>
- bean
- 属性注入
//等价于<bean id="user" class="com.mr.ming.pojo.User"/>
//@Component 组件
@Component
public class User {
//相当于 <property name="name" value="铭先生学java"/>
@Value("铭先生学java")
private String name;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
- 衍生注解
@Conponent
有几个衍生注解,我们在web开发中,会按照mvc三层架构分层
层 | 注解 |
---|---|
dao | @Repository |
service | @Service |
controller | @Controller |
这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean
- 自动装配配置
@Autowired
:自动装配通过类型,名字。如果Autowired
不能唯一自动装配上属性,则需要通过@Qualifier(value = "xxx")
去配置。@Nullable
字段标记了了这个注解,说明这个字段可以为null
;@Resource
:自动装配通过名字,类型。
- 作用域
@Component
@Scope("singleton")//单例模式
public class User {
//相当于 <property name="name" value="铭先生学java"/>
@Value("铭先生学java")
private String name;
}
- 小结
xml与注解:
xml更加万能,适用于任何场合!维护简单方便
注解不是自己类使用不了,维护相对复杂!
xml与注解最佳实践:
xml用来管理bean;
注解只负责完成属性的注入;
我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.mr.ming"/>
<!--开启注解的支持 -->
<context:annotation-config/>
6.1、组件扫描细节
<!--开启组件扫描
1.如果扫描多个包,可以有逗号把多个隔开
2.扫描包上层目录
细节点:
use-default-filters="false"(默认为true) 表示现在不适用默认filter,自己配置filter
include-filter:设置扫描的注解
exclude-filter:设置不要扫描的注解
-->
<context:component-scan base-package="com.mr.ming" use-default-filters="false" >
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
7、使用java的方式配置Spring
我们现在要完全不使用Spring的xml配置了,全权交给Java来做!
JavaConfig
是Spring的一个子项目,在Spring4
之后,它成为了一个核心功能!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WDwGse1Q-1658555387328)(D:\杂乱文件\Typora\MySQL学习\spring\applicationContext.png)]
实体类
package com.mr.ming.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author: Mr.ming
* @describe: NULL
* @create: 2022/4/28-15:09
*/
//这里这个注解的意思,就是说明这个类被Spring接管了,注册到了容器中
@Component
public class User {
private String name;
public String getName() {
return name;
}
@Value("铭先生学java")//属性值注入 它也可以写在属性上
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
配置文件类
package com.mr.ming.config;
import com.mr.ming.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;
/**
* @author: Mr.ming
* @describe: NULL
* @create: 2022/4/28-15:13
*/
// 这个也会Spring容器托管,注册到容器中,因为它本来就是一个@Component
// @Configuration代表这是一个配置类,就和我们之前看的beans.xml
@Configuration
@ComponentScan("com.mr.ming")
@Import(MingConfig2.class) //导入其他的配置文件
public class MingConfig {
// 注册一个bean,就相当于我们之前写的一个bean标签
// 这个方法的名字,就相当于bean标签中id属性
// 这个方法的返回值,就相当于bean标签中的class属性
@Bean
public User getUser(){
return new User(); //就是要分会注入的bean的对象
}
}
测试类
public class MyTest {
public static void main(String[] args) {
//如果完全使用了配置类方式去做,我们就只能通过 AnnotationConfig 上下文来获取容器,通过配置类的class对象加载!
ApplicationContext context = new AnnotationConfigApplicationContext(MingConfig.class);
User user = context.getBean("getUser", User.class);
System.out.println(user);
}
}
这种纯Java的配置方式,在SpringBoot中随处可见!
8、代理模式
为什么要学习代理模式?因为这就是springAOP的底层!【SpringAOP和SpringMVC】
代理模式分类:
- 静态代理
- 动态代理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YIOQX8rs-1658555387328)(D:\杂乱文件\Typora\MySQL学习\spring\proxy.png)]
8.1、静态代理
角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决(中介要帮房东干什么)
- 真实角色:被代理的角色(房东)
- 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作(中介)
- 客户:访问代理对象的人!(你)
代码步骤
- 接口
package com.mr.ming.demo1;
/**
* @author: Mr.ming
* @describe: NULL
* @create: 2022/4/28-22:03
*/
public interface Rent {
public void rent();
}
- 真实角色
package com.mr.ming.demo1;
/**
* @author: Mr.ming
* @describe: NULL
* @create: 2022/4/28-22:03
*/
public class Host implements Rent{
@Override
public void rent() {
System.out.println("出租房子");
}
}
- 代理角色
package com.mr.ming.demo1;
/**
* @author: Mr.ming
* @describe: NULL
* @create: 2022/4/28-22:04
*/
public class Proxy implements Rent{
private Host host;
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHouse();
signAContract();
getFare();
host.rent();
}
//带人看房
public void seeHouse(){
System.out.println("带客户看房");
}
//收取中介费
public void getFare(){
System.out.println("中介费");
}
//签合同
public void signAContract(){
System.out.println("签合同");
}
}
- 客户端访问代理角色
package com.mr.ming.demo1;
/**
* @author: Mr.ming
* @describe: NULL
* @create: 2022/4/28-22:08
*/
public class Client {
public static void main(String[] args) {
Host host =new Host();//出租房子的房东
Proxy proxy =new Proxy(host);//帮房东出租房子
proxy.rent();//找中介去租房子,不用去找房主
}
}
代理模式的好处
- 可以使真实的角色操作更加存粹!不用去关注一些公共的业务
- 公共角色就交给代理角色!实现了业务的分工
- 公共业务发生拓展的时候,方便集中管理
缺点
- 一个真实角色就会产生一个代理角色!代码量会翻倍,开发效率变低!
8.2、加深理解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-shEOx4FU-1658555387329)(D:\杂乱文件\Typora\MySQL学习\spring\静态代理加深理解图.png)]
- 公共业务接口层
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 setUserServiceImpl(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 meg){
System.out.println("[debug]打了一个"+meg+"方法的日志");
}
}
- 客户
public class Client {
public static void main(String[] args) {
UserServiceImpl userService =new UserServiceImpl();
UserServiceProxy proxy =new UserServiceProxy();
proxy.setUserServiceImpl(userService);
proxy.add();
proxy.delete();
proxy.query();
proxy.update();
}
}
相关阅读:面向对象的七大设计原则 - 云+社区 - 腾讯云 (tencent.com)
8.3、动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的!
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口: JDK动态代理
- 基于类:cglib
- java字节码实现:javassist
需要了解两个类:
Proxy
:代理invocationHandler
:调用处理程序
代码步骤
- 接口
public interface Rent {
public void rent();
}
- 真实角色
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
- invocationHandler实现类
package com.mr.ming.demo3;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author: Mr.ming
* @describe: NULL
* @create: 2022/4/30-11:31
*/
//我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质,就是使用反射机制实现!
Object invoke = method.invoke(rent, args);
seeHose();
fee();
return invoke;
}
public void seeHose(){
System.out.println("中介带着看房子!");
}
public void fee(){
System.out.println("中介收取费用!");
}
}
- 测试
public class Client {
public static void main(String[] args) {
Host host =new Host();
ProxyInvocationHandler pih =new ProxyInvocationHandler();
pih.setRent(host);
Rent proxy = (Rent)pih.getProxy();
proxy.rent();
}
}
在此,我们可以提炼出ProxyInvocationHandler作为工具类
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object invoke = method.invoke(target, args);//代理对象当前执行的方法的描述对象(反射)
// method.invoke(对象,实际参数)
return invoke;
}
public void log(String meg){
System.out.println("调用了"+meg+"方法");
}
}
相关阅读:method.invoke()和invoke()简单理解_傅里叶、的博客-CSDN博客_invoke()
动态代理的好处
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共角色就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可!
9、AOP
9.1、什么是AOP
- 面向切面(方面)编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而是的业务逻辑各个方面之间的耦合度降低,提高程序的课重用性,同时提高开发效率。
- 通俗讲:不通过修改源代码方式,在主干功能里面添加新功能
图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzn792AB-1658555387330)(D:\杂乱文件\Typora\MySQL学习\spring\aop登录图示.png)]
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vrxpikbj-1658555387330)(D:\杂乱文件\Typora\MySQL学习\spring\aop.png)]
9.2、AOP底层原理
AOP底层使用动态代理
有两种情况动态代理
- 第一种 有接口情况 使用JDK动态代理
- 创建接口实现类代理对象,增强类的方法
- 图示
- 第二种 没有接口 使用CGLIB动态代理
- 创建子类代理类对象,增强类的方法
- 图示
9.3、AOP在spring中的作用
提供声明式事务;允许用户自定义切面
横切关注点:跨越应用程序多个模块的方法或功能。即使,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等…
切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类。
通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
目标(Target):被通知对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
切入点(PointCut):切面通知执行的“地点”的定义。
连接点(JointPoint):与切入点匹配的执行点。
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
即AOP在不改变原有代码的情况下,去增加新的功能。
9.4、使用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的API接口【主要是SpringAPI接口实现】
- 在service包下,定义UserService业务接口和UserServiceImpl实现类
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("查询了一个用户");
}
}
- 在log包下,定义我们的增强类,一个BeforeLog前置增强和一个AfterLog后置增强类
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("调用了"+method.getName()+"方法,返回结果是"+returnValue);
}
}
public class BeforeLog implements MethodBeforeAdvice {
//method: 要执行的目标对象的方法
//args:参数
//target:目标对象
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被调用了");
}
}
- 最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束,配置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"
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="beforeLog" class="com.mr.ming.log.BeforeLog"/>
<bean id="afterLog" class="com.mr.ming.log.AfterLog"/>
<bean id="userService" class="com.mr.ming.service.UserServiceImpl"/>
<!--方式一:使用原生Spring API接口-->
<!--配置aop:需要导入aop的约束-->
<aop:config>
<!--切入点:expression:表达式,execution(要执行的位置!* * * * *)-->
<aop:pointcut id="pointcut" expression="execution(* com.mr.ming.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加!-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
- 测试类
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口:注意点
UserService userService = context.getBean("userService",UserService.class);
// userService.add();
userService.query();
}
}
方式二:
自定义类来实现AOP【主要是切面定义】
- 在diy包下定义自己的DiyPointCut切入类
public class DiyPointCut {
public void before(){
System.out.println("============方法执行前=============");
}
public void after(){
System.out.println("==============方法执行结束==========");
}
}
- 去spring中配置文件
<!--方式二:自定义类-->
<bean id="diy" class="com.mr.ming.diy.DiyPointCut"/>
<aop:config>
<!--自定义切面,ref 要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.mr.ming.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
- 测试类与上同
方式三:
使用注解实现!
- 在diy包下定义注解实现的AnnotationPointCut增强类
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.mr.ming.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("=======方法执行前======");
}
@After("execution(* com.mr.ming.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("=====方法执行结束=======");
}
//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点;
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
Signature signature = jp.getSignature();// 获得签名
System.out.println("signature:"+signature);
Object proceed = jp.proceed(); //执行方法
System.out.println("环绕后");
System.out.println(proceed);
}
}
- 在Spring配置文件中,注册bean,并增加支持注解的配置。
<!-- 方式三:-->
<bean id="userService" class="com.mr.ming.service.UserServiceImpl"/>
<bean id="annotationPointCut" class="com.mr.ming.diy.AnnotationPointCut"/>
<!--开启注解支持! JDK(默认是 proxy-target-class="false") cglib(proxy-target-class="true")-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
- 测试与上同
10、重学AOP
- 使用JDK动态代理,使用Proxy类的方法创建代理对象
调用Proxy
的newProxyInstance
方法。
-
对
Proxy.newProxyInstance
理解:- 参数1:真实对象的类加载器
- 参数2:真实对象实现的所有的接口,接口是特殊的类,使用Class[]装载多个接口
- 参数3:接口,传递一个匿名内部类对象;(也可传递这个接口的实现类)
-
InvocationHandler
的invoke
方法理解:- proxy 代理对象
- method:代理的方法对象
- args:方法调用时参数
相关阅读:(2条消息) 对Proxy.newProxyInstance的一些理解_韩一聪的博客-CSDN博客_proxy.newproxyinstance
代码演示
- 接口
public interface UserDao {
public int add(int a,int b);
public void update(String id);
}
- 接口实现类
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public void update(String id) {
System.out.println(id);
}
}
- 代理类生成程序
public class ProxyUser implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy(){
// return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前");
Object invoke = method.invoke(target, args);
System.out.println(invoke);
System.out.println("方法执行后");
return invoke;
}
}
- 客户
public class Client {
public static void main(String[] args) {
UserDaoImpl userDao = new UserDaoImpl();
ProxyUser proxyUser = new ProxyUser();
proxyUser.setTarget(userDao);
// UserDao user =(UserDao)Proxy.newProxyInstance(userDao.getClass().getClassLoader(), UserDaoImpl.class.getInterfaces(), proxyUser);
UserDao user= (UserDao) proxyUser.getProxy();
user.add(1,5);
}
}
10.1、AOP术语
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dwlc0Ddc-1658555387333)(D:\杂乱文件\Typora\MySQL学习\spring\AOP术语.png)]
- 连接点:类里面那些方法可以被增强,这些方法成为连接点
- 切入点:实际被真正增强的方法,成为切入点
- 通知:
- 实际增强的逻辑部分被称为通知(增强)
- 通知有多种类型
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知(finally)
- 切面
- 是动作:把通知应用到切入点过程
10.2、AOP操作
- Spring框架一般都是基于AspectJ实现AOP操作
AspectJ
AspectJ
不是spring
的组成部分,他是一个独立的框架,一般把AspectJ
和Spring
框架一起使用,进行AOP操作
- 基于AspectJ实现AOP操作
- 基于xml配置文件实现
- 基于注解方式实现(使用)
- 导入所需jar包或者maven依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.19</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.aspectj</groupId>-->
<!-- <artifactId>aspectjweaver</artifactId>-->
<!-- <version>1.9.0</version>-->
<!-- </dependency>-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
- 切入点表达式
- 切入点表达式作用:指导对那个类里面的哪个方法进行增强
- 语法结构:
execution([权限修饰符][返回值类型][类权路径][方法名称]([参数列表]))
代码演示
- 接口
public interface UserDao {
public void add();
}
- 真实对象
@Component
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("增加一个用户");
}
}
- 增强类
@Component
@Aspect
public class UserProxy {
// 前置通知
@Before(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void before(){
System.out.println("前置通知");
}
//后置通知,有没有异常都会执行
@After(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void after(){
System.out.println("最终通知");
}
//返回通知,有异常不执行
@AfterReturning(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void afterReturning(){
System.out.println("返回通知");
}
//异常通知
@AfterThrowing(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void afterThrowing(){
System.out.println("异常通知");
}
//环绕通知,之前,之后都执行,如果有异常之后不执行
@Around(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知之前");
//被增强方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕通知之后");
}
}
- 没有报错输出:
环绕通知之前
前置通知
增加一个用户
返回通知
最终通知
环绕通知之后
- 报错输出
环绕通知之前
前置通知
异常通知
最终通知
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.mr.ming.service.UserDaoImpl.add(UserDaoImpl.java:15)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
升级:
抽取相同切入点
使用@PointCut
注解,然后其他通知引用这个注解注在的方法就行
@Pointcut(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void pointCut(){
}
// 前置通知
@Before(value = "pointCut()")
public void before(){
System.out.println("前置通知");
}
10.3、增强类优先级
- 在增强类上面添加注解
@Order
(数字类型值) - 数字类型值越小,优先级越高
- 负数、0、正数都可
- 如果不写数值,那就默认最后顺序
- 默认order注解的值
@Component
@Aspect
@Order
public class OProxy {
@Before(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void before(){
System.out.println("11111111111");
}
}
- 设置值为0
@Component
@Aspect
@Order(0)
public class PersonProxy {
@Before(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void before(){
System.out.println("person before");
}
}
- 设置值为-1
@Component
@Aspect
@Order(-1)
public class UserProxy {
@Pointcut(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void pointCut(){
}
// 前置通知
@Before(value = "pointCut()")
public void before(){
System.out.println("前置通知");
}
//后置通知,有没有异常都会执行
@After(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void after(){
System.out.println("最终通知");
}
//返回通知,有异常不执行
@AfterReturning(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void afterReturning(){
System.out.println("返回通知");
}
//异常通知
@AfterThrowing(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void afterThrowing(){
System.out.println("异常通知");
}
//环绕通知,之前,之后都执行,如果有异常之后不执行
@Around(value = "execution(* com.mr.ming.service.UserDaoImpl.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知之前");
//被增强方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕通知之后");
}
}
输出结果
环绕通知之前
前置通知
person before
11111111111
增加一个用户
返回通知
最终通知
环绕通知之后
10.4、AspectJ配置文件操作AOP
- 在spring配置文件中注册两个对象
- 在sping配置文件中配置切入点
- 设置切入点
- 配置切面
- 增强作用在具体方法上
代码演示
创建两个类,增强类和被增强类,创建方法
- 被增强类
public class Book {
public void buy(){
System.out.println("book buy");
}
}
- 增强类
public class BookProxy {
public void before(){
System.out.println("前置通知");
}
}
- 配置文件
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- <context:component-scan base-package="com.mr.ming.service"></context:component-scan>-->
<!-- <aop:aspectj-autoproxy></aop:aspectj-autoproxy>-->
<!--注册对象-->
<bean id="book" class="com.mr.ming.Book"/>
<bean id="bookProxy" class="com.mr.ming.BookProxy"/>
<!--配置切入点-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pointCut" expression="execution(* com.mr.ming.Book.*(..))"/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体方法上-->
<aop:before method="before" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
</beans>
10.5、完全注解开发
- 创建配置类
@Configuration//配置类
@ComponentScan(basePackages = {"com.mr.ming"})//扫描的包
@EnableAspectJAutoProxy(proxyTargetClass = true)//开启aop操作
public class BookConfig {
}
- 真实角色
@Component
public class Book {
public void buy(){
System.out.println("book buy");
}
}
- AOP操作
@Component
@Aspect
public class BookProxy {
@Before(value = "execution(* com.mr.ming.Book.buy(..))")
public void before(){
System.out.println("前置通知");
}
}
11、jdbcTemplate
- 概述
Spring框架对JDBC进行封装,使用jdbcTemplate方便实现对数据库操作
-
准备
- 导入依赖:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
- 在spring的配置文件中配置数据库连接池
配置文件
prop.driver=com.mysql.cj.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=utf8&useSSL=true
prop.username=root
prop.password=123456
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"
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:property-placeholder location="db.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driver}"/>
<property name="url" value="${prop.url}"/>
<property name="username" value="${prop.username}"/>
<property name="password" value="${prop.password}"/>
</bean>
</beans>
- 配置jdbcTemplate对象,注入DataSource
<!--配置jdbcTemplate对象-->
<bean id="hdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
- 搭建环境
配置文件,开始包扫描
<!--开启组件扫描-->
<context:component-scan base-package="com.mr.ming"/>
数据库层
CREATE TABLE `t_book` (
`user_id` BIGINT NOT NULL,
`username` VARCHAR(100) NOT NULL,
`userStatus` VARCHAR(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb3
创建实体类层(pojo)
@Component
public class Book {
private int user_id;
private String userName;
private String userStatus;
public int getUser_id() {
return user_id;
}
public void setUser_id(int user_id) {
this.user_id = user_id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserStatus() {
return userStatus;
}
public void setUserStatus(String userStatus) {
this.userStatus = userStatus;
}
@Override
public String toString() {
return "Book{" +
"user_id=" + user_id +
", userName='" + userName + '\'' +
", userStatus='" + userStatus + '\'' +
'}';
}
}
创建Dao层
//接口
@Repository
public interface BookDao {
public int add(Book book);
public int delete(int id);
public int update(Book book,int id);
public int selectCount();
public Book selectReturnBook(int id);
public List<Book> selectBookList();
public int[] addMore(List<Object[]> arr);
public int[] deleteMore(List<Object[]> ids);
public int[] updateMore(List<Object[]> arr);
}
//dao层的接口实现类
@Repository
public class BookDaoImpl implements BookDao{
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public int add(Book book) {
String sql ="INSERT INTO t_book VALUES(?,?,?)";
int update = jdbcTemplate.update(sql, book.getUser_id(), book.getUserName(), book.getUserStatus());
return update;
}
@Override
public int delete(int id) {
String sql ="DELETE FROM t_book WHERE user_id=?";
int update = jdbcTemplate.update(sql, id);
return update;
}
@Override
public int update(Book book,int id) {
String sql ="UPDATE t_book SET userName=?,userStatus=? WHERE user_id=?";
int update = jdbcTemplate.update(sql, book.getUserName(), book.getUserStatus(), id);
return update;
}
@Override
public int selectCount() {
String sql ="SELECT COUNT(*) FROM t_book";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
@Override
public Book selectReturnBook(int id) {
String sql ="SELECT * FROM t_book WHERE user_id=?";
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
return book;
}
@Override
public List<Book> selectBookList() {
String sql ="SELECT * FROM t_book";
List<Book> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return query;
}
@Override
public int[] addMore(List<Object[]> arr) {
String sql="INSERT INTO t_book VALUE(?,?,?)";
int[] addAll = jdbcTemplate.batchUpdate(sql, arr);
return addAll;
}
@Override
public int[] deleteMore(List<Object[]> ids) {
String sql ="DELETE FROM t_book WHERE user_id=?";
int[] deleteMore = jdbcTemplate.batchUpdate(sql, ids);
return deleteMore;
}
@Override
public int[] updateMore(List<Object[]> arr) {
String sql ="UPDATE t_book SET userName=?,userStatus=? WHERE user_id=?";
int[] updateMore = jdbcTemplate.batchUpdate(sql, arr);
return updateMore;
}
}
创建service层
//service层接口
@Service
public interface BookService {
public void add(Book book);
public void delete(int id);
public void update(Book book,int id);
public void selectCount();
public void selectReturnBook(int id);
public void selectBookList();
public void addMore(List<Object[]> arr);
public void deleteMore(List<Object[]> ids);
public void updateMore(List<Object[]> arr);
}
//接口实现类
@Service
public class BookServiceImpl implements BookService {
@Resource
private BookDao bookDao;
@Override
public void add(Book book) {
int add = bookDao.add(book);
if(add>0){
System.out.println("操作成功");
}else {
System.out.println("操作失败");
}
}
@Override
public void delete(int id) {
int delete = bookDao.delete(id);
if(delete>0){
System.out.println("操作成功");
}else {
System.out.println("操作失败");
}
}
@Override
public void update(Book book, int id) {
int update = bookDao.update(book, id);
if(update>0){
System.out.println("操作成功");
}else {
System.out.println("操作失败");
}
}
@Override
public void selectCount() {
int count = bookDao.selectCount();
System.out.println("数据/条:"+count);
}
@Override
public void selectReturnBook(int id) {
Book book = bookDao.selectReturnBook(id);
System.out.println(book);
}
@Override
public void selectBookList() {
List<Book> books = bookDao.selectBookList();
System.out.println(books);
}
@Override
public void addMore(List<Object[]> arr) {
int[] addAll = bookDao.addMore(arr);
System.out.println(Arrays.toString(addAll));
}
@Override
public void deleteMore(List<Object[]> ids) {
int[] deleteMore = bookDao.deleteMore(ids);
System.out.println(Arrays.toString(deleteMore));
}
@Override
public void updateMore(List<Object[]> arr) {
int[] updateMore = bookDao.updateMore(arr);
System.out.println(Arrays.toString(updateMore));
}
}
测试类
public class MyTest {
public static void main(String[] args) {
var context = new ClassPathXmlApplicationContext("applicationContext.xml");
// Book book = new Book();
// book.setId(2);
// book.setUserName("铭先生学java");
// book.setUserStatus("奋进");
BookService bookService = context.getBean("bookServiceImpl", BookService.class);
// bookService.update(book,2);
// bookService.selectCount();
// bookService.selectReturnBook(3);
// bookService.delete(3);
// bookService.selectBookList();
//批量添加
// Object[] arr1 ={3,"铭先生学计组","努力"};
// Object[] arr2 ={4,"铭先生学高数","认真"};
// Object[] arr3 ={5,"铭先生学编程","踏实"};
//
// List list =new ArrayList();
// list.add(arr1);
// list.add(arr2);
// list.add(arr3);
//
// bookService.addMore(list);
// //批量删除
// Object[] arr1 ={3};
// Object[] arr2 ={4};
// Object[] arr3 ={5};
//
// List list =new ArrayList();
// list.add(arr1);
// list.add(arr2);
// list.add(arr3);
//
// bookService.deleteMore(list);
//批量修改
Object[] arr1 ={"铭先生学计组","努力",5};
Object[] arr2 ={"铭先生学高数","认真",4};
Object[] arr3 ={"铭先生学编程","踏实",3};
List list =new ArrayList();
list.add(arr1);
list.add(arr2);
list.add(arr3);
bookService.updateMore(list);
}
}
- 应用相关知识点
jdbcTemplate.update(String sql,Object args)
来实现添加、修改、删除操作- 参数一:sql语句
- 参数二:可变形参,设置sql的值
@Override
public int add(Book book) {
String sql ="INSERT INTO t_book VALUES(?,?,?)";
int update = jdbcTemplate.update(sql, book.getUser_id(), book.getUserName(), book.getUserStatus());
return update;
}
jdbcTemplate.queryForObject(String,Class requiredType)
来实现查询数据条数操作(查询表里有多少条记录,返回某个值)- 参数一:sql语句
- 参数二:返回类型Class
@Override
public int selectCount() {
String sql ="SELECT COUNT(*) FROM t_book";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
jdbcTemplate.queryForObject(String sql,RowMapper rowMapper,Object... args)
查询返回对象- 参数一:sql语句
- 参数二:RowMapper,是接口,返回不同类型数据,使用这个接口里面实现完成数据封装
- 参数三:sql语句值
- 场景:查询图书详情
@Override
public Book selectReturnBook(int id) {
String sql ="SELECT * FROM t_book WHERE user_id=?";
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
return book;
}
jdbcTemplate.query(String sql,RowMapper rowMapper,Object... args)
(查询返回集合)- 参数一:sql语句
- 参数二:RowMapper,是接口,返回不同类型数据,使用这个接口里面实现完成数据封装
- 参数三:sql语句值
- 场景:查询图书列表分页
@Override
public List<Book> selectBookList() {
String sql ="SELECT * FROM t_book";
List<Book> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return query;
}
jdbcTemplate.batchUpdate(String sql,List<Object[]> batchArgs)
批量操作- 参数一:sql语句
- 参数二:List集合,添加多条记录数据
- 批量增、删、改
@Override
public int[] addMore(List<Object[]> arr) {
String sql="INSERT INTO t_book VALUE(?,?,?)";
int[] addAll = jdbcTemplate.batchUpdate(sql, arr);
return addAll;
}
@Override
public int[] deleteMore(List<Object[]> ids) {
String sql ="DELETE FROM t_book WHERE user_id=?";
int[] deleteMore = jdbcTemplate.batchUpdate(sql, ids);
return deleteMore;
}
@Override
public int[] updateMore(List<Object[]> arr) {
String sql ="UPDATE t_book SET userName=?,userStatus=? WHERE user_id=?";
int[] updateMore = jdbcTemplate.batchUpdate(sql, arr);
return updateMore;
}
注意:错误与收获
在这里,我着实遇到了一个困难,我的实体类的属性名和数据库表的名字是不一样的,结果在操作的时候,它没报错,但是呢?与数据库表属性相同的实体类属性则可以显示出它的值,二其他不相同的属性则全为默认值。最后,将这两种统一才正常输出!
12、事务概念
12.1、什么是事务
- 事务:数据库操作最基本单元,逻辑上一组操作,要么成功,如果有一个失败所有操作都失败
- 典型场景:银行转账
12.2、再次回顾事务
- 把一组业务当成一个业务来做;要么都成功,要么都失败!
- 事务在项目开发中,十分的重要,涉及到数据的一致性问题,不能马虎!
- 确保完整性和一致性。
事务ACID原则:
-
原子性(atomicity)
- 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用。
-
一致性(consistency)
- 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中。
-
隔离性(isolation)
- 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏.
-
持久性(durability)
- 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中。
12.3、再谈事务的四个特性/原则(ACID)
-
原子性;要么都成功。要么都失败
-
一致性:操作之前和操作之后总量不变
-
隔离性:多事务操作之间不会相互影响
-
持久性:事务提交之后,表中数据发生变化保存起来
12.4、事务操作(搭建事务操作环境)
- 创建数据库表,添加记录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PxpDJCdJ-1658555387334)(D:\杂乱文件\Typora\MySQL学习\spring\转账环境1.png)]
- 创建service,搭建dao,完成对象创建和注入关系
- JdbcTemplate注入DataSource
- dao注入jdbcTemplate
- service注入dao
dao层
@Repository//接口
public interface UserDao {
public int addMoney(int money,int id);
public int reduceMoney(int money,int id);
}
@Repository//接口实现类
public class UserDaoImpl implements UserDao{
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public int addMoney(int money,int id) {
String sql ="UPDATE account SET money=money+? WHERE id=?";
int add = jdbcTemplate.update(sql, money, id);
return add;
}
@Override
public int reduceMoney(int money,int id) {
String sql ="UPDATE account SET money=money-? WHERE id=?";
int reduce = jdbcTemplate.update(sql, money, id);
return reduce;
}
}
service
@Service
public interface UserService {
public void addMoney(int money,int id);
public void reduceMoney(int money,int id);
public void accountMoney();
}
@Service
public class UserServiceImpl implements UserService{
@Resource
private UserDao userDao;
@Override
public void addMoney(int money, int id) {
int addMoney = userDao.addMoney(money, id);
if(addMoney>0){
System.out.println("操作成功!");
}else {
System.out.println("操作失败!");
}
}
@Override
public void reduceMoney(int money, int id) {
int reduceMoney = userDao.reduceMoney(money, id);
if(reduceMoney>0){
System.out.println("操作成功!");
}else {
System.out.println("操作失败!");
}
}
@Override
public void accountMoney() {
addMoney(100,1);
reduceMoney(100,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
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:component-scan base-package="com.mr.ming"/>
<!--数据库连接池-->
<context:property-placeholder location="db.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driver}"/>
<property name="url" value="${prop.url}"/>
<property name="username" value="${prop.username}"/>
<property name="password" value="${prop.password}"/>
</bean>
<!--配置jdbcTemplate对象-->
<bean id="hdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
prop.driver=com.mysql.cj.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=utf8&useSSL=true
prop.username=root
prop.password=123456
配置文件
public class MyTest {
public static void main(String[] args) {
var context= new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userServiceImpl", UserService.class);
userService.accountMoney();
}
}
上述代码,如果正常秩序没有问题的,但是如果代码执行过程中出现异常,有问题
模拟异常
@Override
public void accountMoney() {
addMoney(100,1);
//模拟异常
int i =1/0;
reduceMoney(100,2);
}
}
//在这个时候就出现了问题,1号增加了100块钱,但相应的2号的钱却没有减!
解决
- 第一步 开启事务
- 第二步 进行业务操作
- 第三步 没有发生异常,事务提交
- 第四步 出现异常,事件回滚
12.5、事务操作(Spring事务管理介绍)
-
事务添加到JavaEE三层结构里面的Service层(业务逻辑层)
-
在spring进行事务管理操作
- 编程式事务管理
- 声明式事务管理(使用)
-
声明式事务管理
- 基于注解方式(使用)
- 基于XML配置文件方式
- spring声明式事务管理,底层使用AOP原理
- Spring事务管理API
- 提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
- 图示:(
PlatformTransactionManager
)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yE9zlDFw-1658555387335)(D:\杂乱文件\Typora\MySQL学习\spring\事务api接口.png)]
12.6、事务操作(注解声明式事务管理)
- 在spring配置文件配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
-
在spring配置文件,开启事务注解
-
在Spring配置文件引入名称空间tx
-
<?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:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
-
开启事务注解
-
<!--开启事务注解--> <tx:annotation-driven transaction-manager="transactionManager"/>
-
-
在Service类上面(获取service类里面方法上面)添加事务注解
@Transactional
这个注解可以添加到类上面,也可以添加到方法上面- 如果把这个注解添加到类上面,这个类里面所有的方法都添加事务
- 如果把这个注解添加到方法上面,为这个方法添加事务
12.7、事务操作(声明式事务管理参数配置)
在service类上面添加注解@Transactional,在这个注解里面可以配置事务的相关参数
propagation
:事务传播行为- 多事务之间进行调用,这个过程种事务时如何进行管理的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1hBafQS-1658555387335)(D:\杂乱文件\Typora\MySQL学习\spring\声明式事务参数配置1.png)]
事务的传播行为可以由传播属性指定。spring定义了7种类传播行为
相关阅读:看完就明白_spring事务的7种传播行为_gnixlee的博客-CSDN博客_事务传播
-
ioslation:事务隔离级别
- 事务的特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
-
有三个读问题:脏读、不可重复读、虚(幻)读
脏读
不可重复读
虚读
一个未提交事务读取到另一个提交事务添加数据
解决:通过设置事务隔离级别,解决读问题
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
READ UNCOMMITTED(读未提交) | 有 | 有 | 有 |
READ COMMITTED(读已提交) | 无 | 有 | 有 |
REPEATABLE READ(可重复读) | 无 | 无 | 有 |
SERIALIZABLE(串行华) | 无 | 无 | 无 |
-
timeout:超时时间
- 事务需要在一定时间内提交,如果不提交进行回滚
- 默认值为-1(不回滚),设置时间以秒单位进行计算
-
readOnly:是否只读
- 读:查询操作。写:添加修改删除操作
- readOnly默认值为false,表示可以查询,可以添加修改删除
- 设置readOnly值为true,只能查询
-
rollbackFor:回滚
- 设置出现那些异常进行事务回滚
-
noRollbackFor:不回滚
- 设置出现那些异常不进行事务回滚
代码演示
@Transactional(readOnly = true,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
12.7、事务操作(XML声明式事务管理)
在Spring配置文件中进行配置
- 第一步:配置事务管理器
- 第二步:配置通知
- 第三步:配置切入点和切面
<!--1.创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2.配置通知-->
<tx:advice id="txAdvice">
<!--配置事务参数-->
<tx:attributes>
<!--代表account开头的方法,当然你也可以写定 某个具体方法-->
<tx:method name="account*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--3. 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.mr.ming.service.UserServiceImpl.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
12.8、事务管理(完全注解声明式事务管理)
使用配置类来代替xml配置文件
package com.mr.ming;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @author: Mr.ming
* @describe: NULL
* @create: 2022/5/8-12:30
*/
@Component // 配置类
@ComponentScan(basePackages = "com.mr.ming") // 组件扫描
@EnableTransactionManagement // 开启事务
public class TransactionConfig {
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=utf8&useSSL=true");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
//创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DruidDataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
测试
@Test
public void text(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfig.class);
UserService userService = context.getBean("userServiceImpl", UserService.class);
userService.accountMoney();
}
}
13、Spring5框架新特性
- 整个Spring5框架的代码基于JDK8,运行时兼容JDK9,许多不建议使用的类和方法在代码库中删除
13.1、日志封装
- Spring5.0框架自带了通用的日志封装
- Spring5已经移除
Log4jConfigListener
,官方建议使用Log4j2
- Spring5框架整合Log4j2
- Spring5已经移除
- 导入jar包或者引入依赖
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.17.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.0-alpha7</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>
然后直接放在配置文件,那运行你的代码,就行。以下式效果演示:
2022-05-08 13:09:10.085 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
拓展:自定义日志
public class UserLog {
private static final Logger log = LoggerFactory.getLogger(UserLog.class);
public static void main(String[] args) {
log.info("hello log4j2");
log.warn("hello log4j2");
}
}
13.2、@Nullable注释
-
@Nullable
注解可以使用在方法、属性、参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空-
// 注解用在方法上面,方法返回值可以为空 @Nullable String getId(); // 注解使用在方法参数里面,方法参数可以为空 public void registerBean(@Nullable String beanName){ } // 注解使用在属性上面,属性值可以为空 @Nullable private String bookName;
-
13.3、lamda表达式(函数式编程)
-
Spring5核心容器支持函数式风格GenericApplicationContext
-
public class MyTest { public static void main(String[] args) { // 创建GenericApplicationContext对象 GenericApplicationContext genericApplicationContext = new GenericApplicationContext(); // 调用context的方法对象注册 genericApplicationContext.refresh(); genericApplicationContext.registerBean("user",User.class,()->new User()); // 获取在spring注册的对象 User user = genericApplicationContext.getBean("user", User.class); System.out.println(user); } }
-
13.4、支持整合JUnit5
-
整合JUnit4
-
第一步 引入Spring相关针对测试依赖
- spring-test
-
第二步 创建测试类,使用注解方式完成
-
// 单元测试框架 @RunWith(SpringJUnit4ClassRunner.class) // 加载配置文件 @ContextConfiguration("classpath:bean2.xml") public class JTest4 { @Autowired private UserService userService; @Test public void test(){ userService.accountMoney(); } }
-
-
-
整合JUnit5
- 引入JUnit5依赖
- 创建测试类,使用注解完成
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean2.xml")
public class JTest4 {
@Autowired
private UserService userService;
@Test
public void test1(){
userService.accountMoney();
}
}
使用复合注解替代上面两个注解完成整合
@SpringJUnitConfig(locations = "classpath:bean2.xml")
13.5、WebFlux基本概念
因为这个的前置知识中的springMVC springBoot等等还没学,所有这一节的内容,放到这些前置知识学完后,在来学!