Spring

Spring5

1、Spring框架概述

Spring 是轻量级的开源的 JavaEE 框架(jar包数量和体积都比较小)

Spring 可以解决企业应用开发的复杂性

  • Spring的两个核心部分:IOC、Aop

    • IOC:控制反转,把创建对象过程交给 Spring 进行管理

    • Aop:面向切面,不修改源代码进行功能增强

  • Spring的特点:

    • 方便解耦,简化开发

    • Aop编程支持

    • 方便程序测试

    • 方便和其他框架进行整合

    • 方便进行事务操作

    • 降低API开发难度

 

1.1、Spring的一个例子

  • 第一步:下载Spring

Spring下载地址

  • 第二步:创建普通java工程,导入相关jar包

需要导入beans, core, context, expression, commons-logging5个jar包

(Spring5模块中Core Container部分)

Spring5模块

  • 第三步:创建普通类,类中创建普通方法

 public class User {
     public void add(){
         System.out.println("add");
    }
 }
  • 第四步:创建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">
 
     <!--配置User对象创建-->
     <bean id="user" class="com.spring5.User"></bean>
 
 </beans>
  • 第五步:测试

 public class TestSpring5 {
 
     @Test
     public void testAdd(){
         // 1.加载spring配置文件
         ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
         // 2.获取配置,创建对象
         User user = context.getBean("user", User.class);
 
         System.out.println(user);
         user.add();
    }
 
 }

项目结构:

截屏2022-05-01 15.50.37

 

 

2、IOC容器

2.1、IOC原理

IOC:Inversion of Control,控制反转,面向对象的一种设计原则。将对象创建和对象之间调用的过程交给Spring进行管理,使耦合度降低。

  • IOC底层原理:

    • IOC使用的技术:xml解析、工厂模式、反射

    • 传统方法(先new对象再调用对象.方法)耦合度太高,使用工厂模式可以降低耦合度但仍不是最低,IOC在工厂模式的基础上使用xml解析和反射使耦合度降到最低 截屏2022-05-01 16.45.02 截屏2022-05-01 16.46.00 截屏2022-05-01 16.52.26

2.2、IOC接口(BeanFactory)

IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂

IOC容器实现的两种方式(两个接口):

  • BeanFactory:

    • IOC 容器的基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用

    • 加载配置文件时候不会创建对象,在获取对象(使用时,即上文第五步中的2.)才去创建对象

  • ApplicationContext:

    • BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用

    • 加载xml配置文件时候就会把在配置文件中的对象进行创建

    • 理论上使用时再创建对象更好,但实际web项目中,通常将耗时耗资源的操作放在最开始一起执行,所以使用ApplicationContext更合适

ApplicationContext的实现类

idea中快捷键ctrl+h查看

  • 最主要的两个实现类:用于加载配置文件 截屏2022-05-01 17.03.37

    • FileSystemXmlApplicationContext:使用xml文件的绝对路径

    • ClassPathXmlApplicationContext:使用xml文件的类加载路径

BeanDefinition

Bean对象存在依赖嵌套等关系,所以设计者设计了BeanDefinition,它用来对Bean对象及关系定义;

我们在理解时只需要抓住如下三个要点:

  • BeanDefinition 定义了各种Bean对象及其相互的关系

  • BeanDefinitionReader 这是BeanDefinition的解析器

  • BeanDefinitionHolder 这是BeanDefination的包装类,用来存储BeanDefinition,name以及aliases等。

2.3、IOC操作Bean管理(基于xml)

Bean管理:包括两个操作,Spring创建对象和Spring注入属性

Bean管理操作的两种方式:基于xml、基于注解

基于xml方式创建对象

在spring配置文件中使用bean标签,并添加对应属性,就可以实现对象创建(默认使用无参构造创建对象)

<!--配置User对象创建-->
<bean id="user" class="com.spring5.User"></bean>
属性名用途
id 配置和使用对象的唯一标识
class 对象所在类的全路径

基于xml方式注入属性

DI:依赖注入,也就是注入属性

第一种注入方式:使用set方法

  1. 创建类,定义属性和对应的set方法

    public class Book {
    // 创建属性
    private String bname;
    private String bauthor;

    // 创建属性对应的set方法
    public void setBname(String bname) {
    this.bname = bname;
    }

    public void setBauthor(String bauthor) {
    this.bauthor = bauthor;
    }
    }
  2. 在spring配置文件配置对象创建和属性注入

        <!--set方法注入属性-->
    <bean id="book" class="com.spring5.Book">
    <!--使用property完成属性注入
    name:类里面的属性名称
    value:向属性注入的值
    -->
    <property name="bname" value="book1"></property>
    <property name="bauthor" value="author1"></property>
    </bean>

     

第二种注入方式:使用有参构造

  1. 创建类,定义属性,创建属性对应的有参构造

    public class Order {

    private String oname;
    private String address;

    // 有参构造器
    public Order(String oname, String address) {
    this.oname = oname;
    this.address = address;
    }
    }
  2. 在spring配置文件中进行配置

        <!--有参构造注入属性-->
    <bean id="order" class="com.spring5.Order">
    <constructor-arg name="oname" value="abc"></constructor-arg>
    <constructor-arg name="address" value="china"></constructor-arg>
    <!--也可以通过索引值注入-->
    <!--<constructor-arg index="0" value="abc"></constructor-arg>-->
    </bean>

 

简化的xml注入方式:p名称空间注入(实际上还是set方法)

  1. 在配置文件中添加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">
  2. 在bean标签内进行属性注入

    <!--p名称空间注入属性-->
    <bean id="book" class="com.spring5.Book" p:bname="book2" p:bauthor="author2"></bean>
xml注入其他类型属性

设置特殊属性值

<!--设置null值-->
<property name="address"><null/></property>

<!--设置包含特殊符号的属性值,例如:<<NanJing>> -->
<property name="address">
<value><![CDATA[<<NanJing>>]]></value>
</property>

外部bean:直接在beans标签内部直接定义的bean对象,外部bean可以被多个bean对象引用

将对象作为属性注入

例如:在service类中调用dao类的方法

  • 原始方式:在UserService类中创建UserDao对象,通过对象调用方法

  • 外部bean注入方式

    • 在UserService类中创建UserDao类型的属性,生成set方法

    • 配置xml文件:创建service和dao对象、向userService中注入userDao对象

//================UserService类=====================
public class UserService {

// 创建UserDao类型的属性,生成set方法
private UserDao userDao;

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

public void add(){
System.out.println("service add ...");
userDao.update();

// 原始方式:创建UserDao对象,调用方法
// UserDao userDao = new UserDaoImpl();
// userDao.update();
}
}
//================UserDao接口=========================
public interface UserDao {
public void update();
}
//================UserDao接口的实现类=====================
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("dao update ...");
}
}
//======================测试==========================
@Test
public void testService(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
<!--======================bean2.xml=========================-->
<!--外部bean-->
<!--1.创建service和dao对象-->
<bean id="userDaoImpl" class="com.spring5.dao.UserDaoImpl"></bean><!--注意这里class是实现类-->
<bean id="userService" class="com.spring5.service.UserService">
<!--2.向userService中注入userDao对象
name:userService类中的属性名称
ref:注入属性的对象的bean id
-->
<property name="userDao" ref="userDaoImpl"></property>
</bean>

内部bean:将一个 bean 用作另一个 bean 的属性

在某个bean标签的内部定义的bean对象,内部bean只能被某个对象的某个属性引用。

例如:一对多关系,员工和部门

//======================部门类==========================
public class Dept {

private String dname;

public void setDname(String dname) {this.dname = dname;}

@Override
public String toString() {
return "Dept{" +
"dname='" + dname + '\'' +
'}';
}
}
//======================员工类==========================
public class Emp {

private String ename;
private String gender;
// 所属部门
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;}

public void detail(){
System.out.println(ename + gender + dept);
}
}
//======================测试==========================
@Test
public void testEmp(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Emp emp = context.getBean("emp", Emp.class);
emp.detail();
}
<!--======================bean2.xml=========================-->
<!--内部bean-->
<bean id="emp" class="com.spring5.bean.Emp">
<!--设置基本属性-->
<property name="ename" value="Niko"></property>
<property name="gender" value="male"></property>
<!--设置对象类型的属性-->
<property name="dept">
<!--在属性中创建对象并设置创建出来的对象的属性-->
<bean id="dept" class="com.spring5.bean.Dept">
<property name="dname" value="D1"></property>
</bean>
</property>
</bean>

级联赋值:在一个bean对象中注入另一个bean对象(外部bean)的属性并对该属性进行赋值。

方法一:

<!--======================bean2.xml=========================-->
<!--级联赋值-->
<bean id="emp" class="com.spring5.bean.Emp">
<!--设置基本属性-->
<property name="ename" value="Niko"></property>
<property name="gender" value="male"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.spring5.bean.Dept">
<property name="dname" value="D2"></property>
</bean>

方法二:

// 在emp中添加dept对象的get方法
// 所属部门
private Dept dept;

public Dept getDept() {
return dept;
}
    <!--级联赋值-->
<bean id="emp" class="com.spring5.bean.Emp">
<!--设置基本属性-->
<property name="ename" value="Niko"></property>
<property name="gender" value="male"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
<property name="dept.dname" value="D3"></property>
</bean>
<bean id="dept" class="com.spring5.bean.Dept">
<!--<property name="dname" value="D2"></property>-->
</bean>
xml注入集合类型属性
// 创建类,定义类型属性,生成对应的set方法
public class Stu {

// 1.数组类型属性
private String[] courses;
// 2.list集合类型属性
private List<String> list;
// 3.map集合类型属性
private Map<String, String> maps;

public void setList(List<String> list) {this.list = list;}
public void setMaps(Map<String, String> maps) {this.maps = maps;}
public void setCourses(String[] courses) {this.courses = courses;}

public void test(){
System.out.println(Arrays.toString(courses));
System.out.println(list);
System.out.println(maps);
}
}
//======================测试==========================
@Test
public void testCol(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Stu stu = context.getBean("stu", Stu.class);
stu.test();
}
<!--======================bean2.xml=========================-->
<!--集合类型的属性注入-->
<bean id="stu" class="com.spring5.collectiontype.Stu">
<!--注入数组类型-->
<property name="courses">
<array>
<value>course01</value>
<value>course02</value>
</array>
</property>
<!--注入list类型-->
<property name="list">
<list>
<value>student01</value>
<value>student02</value>
</list>
</property>
<!--注入map类型-->
<property name="maps">
<map>
<entry key="key01" value="value01"></entry>
<entry key="key02" value="value02"></entry>
</map>
</property>
</bean>

注入的集合为多个对象的集合

<!--======================bean2.xml=========================-->
<!--首先创建多个course对象-->
<bean id="course1" class="com.spring5.collectiontype.Course">
<property name="cname" value="c01"></property>
</bean>
<bean id="course2" class="com.spring5.collectiontype.Course">
<property name="cname" value="c02"></property>
</bean>
<!--集合类型的属性注入-->
<bean id="stu" class="com.spring5.collectiontype.Stu">
<!--注入值为对象的list集合类型-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>

提取集合作为公共部分(让不同的类都能使用这个集合)

<!--======================bean3.xml=========================-->
<?xml version="1.0" encoding="UTF-8"?>
<!--1.引入名称空间util-->
<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">

<!--2.在公共部分声明属性-->
<util:list id="bookList">
<value>book1</value>
<value>book2</value>
<value>book3</value>
</util:list>

<!--3.将公共属性注入到不同类中-->
<bean id="book" class="com.spring5.collectiontype.Book">
<property name="list" ref="bookList"></property>
</bean>
<bean id="book" class="com.spring5.collectiontype.Stu">
<property name="bookList" ref="bookList"></property>
</bean>

</beans>

 

2.4、FactoryBean

普通bean:配置文件中定义的bean类型class就是返回类型(配置文件中的bean即为创建对象操作)

FactoryBean:配置文件定义的类型class和返回类型不同

// 例如:配置文件中定义的是MyBean类型,实际返回的对象类型为Course
//======================MyBean==========================
// 实现FactoryBean接口
public class MyBean implements FactoryBean<Course> {
// 定义返回的bean的类型
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("course000");
return course;
}

@Override
public Class<?> getObjectType() {
return null;
}

@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
//======================测试==========================
@Test
public void testMyBean(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
Course course = context.getBean("mybean", Course.class);
System.out.println(course);
}
<!--======================bean4.xml=========================-->
<bean id="mybean" class="com.spring5.factorybean.MyBean"></bean>

 

2.5、Bean的作用域

在spring中默认情况下bean是单实例对象(多次创建的对象内存地址相同,说明是同一个对象)

截屏2022-05-03 13.50.26

通过改变配置文件中的scope可以设置单实例或多实例:

  • scope = singleton时,加载 spring 配置文件时就创建单实例对象

  • scope = prototype时,在调用 getBean 方法时创建多实例对象

		<!--prototype表示多实例对象,singleton表示单实例对象-->
<bean id="book" class="com.spring5.collectiontype.Book" scope="prototype">
<property name="list" ref="bookList"></property>
</bean>

 

2.6、Bean的生命周期

生命周期:从对象创建到对象销毁的过程

  • Bean的生命周期:

    1. 通过构造器创建bean实例(无参构造)

    2. 设置bean属性值,直接设置或引用其他bean(调用set方法)

    3. 调用bean中初始化方法(需要配置)

    4. 使用bean(获取到了对象)

    5. 当容器关闭时,调用bean的销毁方法(需要配置)

//======================Orders==========================
public class Orders {

private String oname;

// 第一步:执行无参构造创建bean实例
public Orders() {
System.out.println("step1");
}

// 第二步:调用set方法设置属性值
public void setOname(String oname) {
this.oname = oname;
System.out.println("step2");
}

// 第三步:创建初始化方法
// 需要在配置文件配置init-method后才能执行
public void initMehtod(){
System.out.println("step3");
}

// 第五步:创建销毁方法
public void destoryMethod(){
System.out.println("step5");
}
}
//======================测试/使用==========================
@Test
public void testOrders(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
// 第四步:获取bean实例对象
Orders orders = context.getBean("orders", Orders.class);
System.out.println("step4");
System.out.println(orders);

// 手动执行bean的销毁方法
// 由于close方法在ApplicationContext接口的子接口的实现类中,所以需要强转
((ClassPathXmlApplicationContext)context).close();
}
<!--======================bean4.xml=========================-->
<bean id="orders" class="com.spring5.bean.Orders" init-method="initMehtod" destroy-method="destoryMethod">
<property name="oname" value="order01"></property>
</bean>

bean的后置处理器:创建类,实现接口BeanPostProcessor,创建后置处理器;重写的两个方法分别在初始化之前和初始化之后执行

截屏2022-05-03 15.42.54

2.7、自动装配

自动装配:根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入

//======================Dept1==========================
public class Dept1 {

@Override
public String toString() {
return "Dept1{}";
}
}
//======================Emp1==========================
public class Emp1 {

public Dept1 dept1;

public void setDept1(Dept1 dept1) {
this.dept1 = dept1;
}

@Override
public String toString() {
return "Emp1{" +
"dept1=" + dept1 +
'}';
}

public void test(){
System.out.println("dept1 = " + dept1);
}
}
//======================测试==========================
@Test
public void testEmp1(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
Emp1 emp1 = context.getBean("emp1", Emp1.class);
emp1.test();
}

 

<!--======================bean5.xml=========================-->
<!--手动装配-->
<!--<bean id="dept1" class="com.spring5.autowire.Dept1"></bean>-->
<!--<bean id="emp1" class="com.spring5.autowire.Emp1">-->
<!-- <property name="dept1" ref="dept1"></property>-->
<!--</bean>-->

<!--自动装配
使用bean标签属性autowire配置自动装配
byName:根据属性名称注入,注入值的bean id和类属性名称应一致
byType:根据属性类型注入
-->
<bean id="dept1" class="com.spring5.autowire.Dept1"></bean>
<bean id="emp1" class="com.spring5.autowire.Emp1" autowire="byName"></bean>

 

2.8、外部属性文件

  • 引入外部属性文件:

    1. 引入context名称空间

    2. 引入外部属性文件

例如:配置数据库信息

//======================测试==========================
@Test
public void testDruid() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
DataSource dataSource = context.getBean("dataSource", DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
<!--======================bean6.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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!--直接配置druid连接池-->
<!--<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">-->
<!--<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>-->
<!--<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8&amp;rewriteBatchedStatements=true"></property>-->
<!--<property name="username" value="root"></property>-->
<!--<property name="password" value="12345678"></property>-->
<!--</bean>-->

<!--引入外部属性文件-->
<!--1.引入context名称空间-->
<!--2.引入外部属性文件-->
<context:property-placeholder location="classpath:/resources/jdbc.properties"/>
<!--3.配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--使用表达式${名称}获取属性值-->
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>

</beans>
#====================src/resources/jdbc.properties====================
# 这里注意username是mysql关键词,直接使用会报错errorCode1045 state28000,所以需要改成jdbc.username,其他同理
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true
jdbc.user=root
jdbc.password=12345678

 

2.9、IOC操作Bean管理(基于注解)

  • 什么是注解:

    • 注解是代码特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值...)

    • 注解可以作用在类、方法、属性上

    • 使用注解的目的:简化xml配置,直接用注解就可以替代

基于注解创建对象

  • Spring针对Bean管理中用于创建对象的注解(功能相同):

    • @Component:创建对象并标识为普通组件

    • @Service:创建对象并标识为业务层组件

    • @Controller:创建对象并标识为控制层组件

    • @Repository:创建对象并标识为持久层组件

  • 基于注解方式实现对象创建的步骤:

    1. 导入aop依赖spring-aop-5.2.6.RELEASE.jar

    2. 开启组件扫描(即扫描注解)(需要先引入context名称空间)

    3. 创建类,在类上添加创建对象的注解

//======================UserService1==========================
// 注解中的value即bean的id,即配置和使用对象的唯一标识
// value可以省略不写,默认为首字母小写的类名称 UserService --> userService
@Component(value = "userService") // 相当于<bean id="userService" class""/>
public class UserService1 {

public void add(){
System.out.println("service add......");
}

}
//======================测试==========================
@Test
public void testUserService01(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml");
UserService1 userService1 = context.getBean("userService", UserService1.class);
userService1.add();
}
<?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名称空间-->
<!--开启组件扫描
如果要扫描多个包,可以用逗号隔开
或者也可以扫描包的上层包目录
-->
<context:component-scan base-package="com.spring5"></context:component-scan>

</beans>
  • 开启组件扫描细节配置(设置扫描过滤器)

    <!--示例1
use-default-filters="false" 表示不使用默认的filter而使用自己配置的filter
context:include-filter 设置想要扫描的内容
-->
<context:component-scan base-package="com.spring5" use-default-filters="false">
<!--只扫描使用Controller的注解-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<!--示例2
context:include-filter 设置不想扫描的内容
-->
<context:component-scan base-package="com.spring5" use-default-filters="false">
<!--不扫描使用Controller的注解-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

基于注解注入属性

  • Spring基于注解进行属性注入的常用注解:

    • @AutoWired:根据属性类型自动装配

    • @Qualifier:根据属性名称进行注入

    • @Resource:可以根据类型,也可以根据名称注入

    • @Value:注入普通类型属性

  • 使用@AutoWired基于注解进行属性注入:

    (以UserDao接口, UserDaoImpl实现类, UserService1类为例)

    1. 创建service和dao对象:在service和dao类中添加创建对象注解

    2. 在service中注入dao对象:在service类中添加dao类型属性,在属性上使用注解

//======================UserService1==========================
// 添加创建对象注释
@Service
public class UserService1 {

// 定义dao类型属性(不需要添加set方法)
// 添加注入注解
@Autowired
private UserDao userDao;

public void add(){
System.out.println("service add......");
userDao.update();
}
}
//======================UserDao==========================
public interface UserDao {
public void update();
}
//======================UserDaoImpl==========================
@Repository
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("dao update ...");
}
}
//======================测试==========================
@Test
public void testUserService01(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml");
UserService1 userService1 = context.getBean("userService1", UserService1.class);
userService1.add();
}
		<!--引入context名称空间-->
<!--开启组件扫描
如果要扫描多个包,可以用逗号隔开
或者也可以扫描包的上层包目录
-->
<context:component-scan base-package="com.spring5"></context:component-scan>
  • 使用@Qualifier基于注解进行属性注入:

    @Qualifier需要和@Autowired一起使用

    1. 创建service和dao对象:在service和dao类中添加创建对象注解

    2. 在service中注入dao对象:在service类中添加dao类型属性,在属性上使用注解,设置和dao对象相同的value值

//======================UserService1==========================
// 创建对象注释
@Service
public class UserService1 {

// 定义dao类型属性(不需要添加set方法)
// 添加注入注解
@Autowired // 根据类型进行注入
@Qualifier(value = "userDaoImpl1") // 根据名称进行注入
private UserDao userDao;

public void add(){
System.out.println("service add......");
userDao.update();
}
}
//======================UserDaoImpl==========================
@Repository(value = "userDaoImpl1") // 设置dao对象的名称
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("dao update ...");
}
}
  • 使用@Resource基于注解进行属性注入:

    如果@Resource无法使用,需要先导入jar包javax.annotation-api-1.3.2.jar

//======================UserService1==========================
// 创建对象注释
@Service
public class UserService1 {

// @Resource // 根据类型注入
@Resource(name = "userDaoImpl1") // 根据名称进行注入
private UserDao userDao;

public void add(){
System.out.println("service add......");
userDao.update();
}
}
//======================UserDaoImpl==========================
@Repository(value = "userDaoImpl1") // 设置dao对象的名称
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("dao update ...");
}
}
  • 使用@Value基于注解进行属性注入:

    @Value可以注入普通类型的属性

//======================UserService1==========================
// 创建对象注释
@Service
public class UserService1 {

@Value(value = "abc")
private String name;

public void add(){
System.out.println(name);
}
}

完全注解开发

  1. 创建配置类,替代xml配置文件

//======================SpringConfig==========================
@Configuration // 声明这个类是配置类,用于替代xml配置文件
@ComponentScan(basePackages = {"com.spring5"}) // 设置扫描位置
// 相当于<context:component-scan base-package="com.spring5"></context:component-scan>
public class SpringConfig {

}
  1. 改写测试类

    @Test
public void testUserService02(){
// 加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService1 userService1 = context.getBean("userService1", UserService1.class);
userService1.add();
}

实际开发中一般使用Springboot进行完全注解开发

3、Aop

3.1、AOP原理

  • 什么是AOP:

    • 面向切面(方面)编程,对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。

    • 通过不修改源代码的方式,在主干功能里面添加新功能

    • AOP的例子:在登陆功能中添加一个权限判断

      截屏2022-05-04 13.05.25

  • AOP底层原理:

    • AOP底层使用动态代理,动态代理有两种情况

      1. 有接口:使用JDK动态代理

        通过创建接口实现类的代理对象,增强类的方法

        截屏2022-05-04 13.12.06

      2. 没有接口:使用CGLIB动态代理

        通过创建子类的代理对象,增强类的方法

        截屏2022-05-04 13.14.21

3.2、JDK动态代理

  1. 使用Proxy类里面的方法创建代理对象,调用newProxyInstance方法

    截屏2022-05-04 13.41.43

    newProxyInstance方法的三个参数:

    • 类加载器

    • 增强方法所在的类实现的接口,支持多个接口

    • 实现接口InvocationHandler,创建代理对象,写增强的方法

  2. 编写JDK动态代理代码

    1. 创建接口,定义方法

    2. 创建接口实现类,实现方法

    3. 使用Proxy类创建接口代理对象

//======================JDKProxy.java==========================
public class JDKProxy {

public static void main(String[] args) {
// 创建一个接口数组,包含被增强的方法所在的类所实现的接口
Class[] interfaces = {UserDao.class};

// 方法一:匿名内部类
// Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
// @Override
// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// return null;
// }
// })

// 创建接口实现类的代理对象
UserDaoImpl userDao = new UserDaoImpl(); // 创建一个实现类对象用于传入代理
// 接口对象=实现类的代理对象(增强后的实现类对象)
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));

int result = dao.add(1, 2);
System.out.println("result = " + result);
}

}

// 方法二:创建代理对象,实现InvocationHandler接口
class UserDaoProxy implements InvocationHandler{

// 1.传入被代理的对象(通过有参构造器传入)
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}

// 2.增强的逻辑(用于增强方法)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// method表示被增强的方法,args表示传入的参数

// 增强方法之前
System.out.println("before... " + method.getName() + " ars... " + Arrays.toString(args));
// method.invoke 执行被增强的方法
Object res = method.invoke(obj, args);
// 增强方法之后
System.out.println("after..." + obj);

return res;
}
}
//======================UserDao接口==========================
public interface UserDao {
public int add(int a, int b);
public String update(String id);
}
//======================UserDaoImpl.java==========================
public class UserDaoImpl implements UserDao{

@Override
public int add(int a, int b) {
return a+b;
}

@Override
public String update(String id) {
return id;
}
}

3.3、AOP术语

  • 连接点:类中可以被增强的方法

  • 切入点:实际被增强的方法

  • 通知(增强):实际增强的逻辑部分

    • 通知有多种类型:

      • 前置通知:增强部分在方法执行前执行

      • 后置通知:增强部分在方法执行后执行

      • 环绕通知:增强部分在方法执行前后都有执行

      • 异常通知:增强部分在方法出现异常时执行

      • 最终通知:类似finally,无论如何增强部分最后都会执行

  • 切面:把通知应用到切入点的过程就是切面(切面是一个动作)(即加入增强功能)

3.4、AOP操作的准备工作

  • Spring 框架一般都是基于 AspectJ 实现 AOP 操作

    AspectJ 不是 Spring 组成部分,是独立的 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用进行 AOP 操作

  • 基于 AspectJ 实现 AOP 操作

    1. 基于xml配置文件实现

    2. 基于注解方式实现

  • 需要引入的相关依赖

    截屏2022-05-04 16.30.22

  • 切入点表达式

    • 切入点表达式的作用:说明要对哪个类的哪个方法进行增强

    • 语法结构:execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))

      // 例子1:对com.spring5.dao.BookDao 类里面的 add 进行增强
      execution(* com.spring5.dao.BookDao.add(..)) // *表示任意权限,返回类型省略

      // 例子2:对com.spring5.dao.BookDao 类中的所有方法进行增强
      execution(* com.spring5.dao.BookDao.*(..))

      // 例子3:对com.spring5.dao 包中的所有类的所有方法进行增强
      execution(* com.spring5.dao.*.* (..))

 

3.5、AOP操作(使用AspectJ注解)

  • 使用AspectJ注解进行AOP操作的步骤:

    1. 创建类,在类中定义方法

    2. 创建增强类(编写增强逻辑)

      1. 在增强类中创建方法,让不同方法代表不同通知(增强)类型

    3. 进行通知的配置

      1. 在spring配置文件中开启注解扫描

      2. 使用注解创建User和UserProxy类的对象

      3. 在增强类上添加注解@Aspect,生成代理对象

      4. 在spring配置文件中开启生成代理对象(对包含注解@Aspect的类生成代理对象)

    4. 配置不同类型的通知

      1. 在增强类中通知方法上面添加通知类型注解,并使用切入点表达式配置

//======================User.java==========================
// 被增强的类
@Component // 使用注解创建类的对象
public class User {

public void add(){
System.out.println("add...");
}
}
//======================UserProxy.java==========================
// 增强的类
@Component // 使用注解创建类的对象
@Aspect //生成代理对象
public class UserProxy {

// 前置通知:在方法执行之前执行
// @Before表示前置通知 valur后加切入点表达式
@Before(value = "execution(* com.spring5.aopanno.User.add(..))")
public void before(){
System.out.println("before...");
}

// 后置(返回)通知:在方法返回结果之后执行(有异常不执行)
@AfterReturning(value = "execution(* com.spring5.aopanno.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning...");
}

// 最终通知:在方法执行之后执行
@After(value = "execution(* com.spring5.aopanno.User.add(..))")
public void after(){
System.out.println("after...");
}

// 异常通知:有异常才执行
@AfterThrowing(value = "execution(* com.spring5.aopanno.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing...");
}

// 环绕通知
@Around(value = "execution(* com.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 在方法执行前,前置通知之前执行
System.out.println("aroundBefore...");
// 执行被增强的方法
proceedingJoinPoint.proceed();
// 在方法执行后,最终通知之前执行(有异常不执行)
System.out.println("aroundAfter...");
}

}
//======================测试==========================
@Test
public void testAopAnno(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);
user.add();
}
/* 测试结果:
aroundBefore...
before...
add...
aroundAfter...
after...
afterReturning...
*/
<!--==================bean1.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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<!--1.引入context和aop名称空间-->

<!--2.开启注解扫描-->
<context:component-scan base-package="com.spring5"></context:component-scan>

<!--3.开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>
  • 使用@Pointcut在增强的类中抽取相同的切入点

    // 抽取相同切入点
@Pointcut(value = "execution(* com.spring5.aopanno.User.add(..))")
public void pointdemo(){
}
// 直接使用方法名称代替切入点表达式
@Before(value = "pointdemo()")
public void before(){
System.out.println("before...");
}
  • 当有多个增强类对同一个方法进行增强时,可以设置增强类的优先级

    在增强类上面添加注解@Order(数字类型值),值越小优先级越高(注意是对设置)

@Component
@Aspect
@Order(2)
public class PersonProxy {

// 抽取相同切入点
@Pointcut(value = "execution(* com.spring5.aopanno.User.add(..))")
public void pointdemo(){
}
// 直接使用方法名称代替切入点表达式
@Before(value = "pointdemo()")
@Order(3)
public void personBefore(){
System.out.println("personBefore...");
}
}
  • 完全使用注解开发

    创建配置类,不需要创建xml配置文件

@Configuration // 表示当前类为配置类
@ComponentScan(basePackages = {"com.spring5"}) // 开启注解扫描
@EnableAspectJAutoProxy(proxyTargetClass = true) // 生成代理对象
public class ConfigAop {
}

 

3.6、AOP操作(使用AspectJ配置文件)

了解即可

  • 使用AspectJ配置文件进行AOP操作的步骤:

    1. 创建两个类,增强类和被增强类,创建方法

    2. 在spring配置文件中创建两个类对象

    3. 在spring配置文件中配置切入点

//======================Book.java==========================
public class Book {

public void buy(){
System.out.println("buy...");
}
}
//======================BookProxy.java==========================
public class Book {

public void buy(){
System.out.println("buy...");
}
}
//======================测试==========================
@Test
public void testAopXml(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book = context.getBean("book", Book.class);
book.buy();
}
<!--==================bean2.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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<!--1.引入aop名称空间-->

<!--2.创建对象-->
<bean id="book" class="com.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.spring5.aopxml.BookProxy"></bean>

<!--3.配置aop增强-->
<aop:config>
<!--切入点(被增强方法)-->
<aop:pointcut id="p" expression="execution(* com.spring5.aopxml.Book.buy(..))"/>
<!--配置切面(增强类)-->
<aop:aspect ref="bookProxy">
<!--增强类的增强方法before作用在具体方法(切入点)上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>

</beans>

 

4、JdbcTemplate

4.1、概念和准备

  • 什么是JdbcTemplate

    • JdbcTemplate 是 Spring 框架对 JDBC 的封装,使用 JdbcTemplate 可以方便的实现对数据库操作

  • 准备工作

    • 引入相关依赖

      截屏2022-05-04 19.09.41

    • 在spring配置文件中配置数据库连接池

    • 配置JdbcTemplate对象,注入DataSource

    • 创建service类、dao类,在service中注入dao,在dao中注入jdbcTemplate对象(service被dao增强,dao被jdbcTemplate增强)

<!--==================bean3.xml=========================-->
<!--通过外部属性文件配置数据库连接池-->
<!--1.引入context名称空间-->
<!--2.引入外部属性文件-->
<context:property-placeholder location="classpath:/com/spring5/resources/jdbc.properties"/>
<!--3.配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--使用表达式${名称}获取属性值-->
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>

<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!--开启组件扫描-->
<context:component-scan base-package="com.spring5"></context:component-scan>
//======================BookService.java==========================
@Service
public class BookService {

// 注入dao类
@Autowired
private BookDao bookDao;
}
//======================BookDao接口==========================
public interface BookDao {
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl {

// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}

 

4.2、JdbcTemplate操作数据库(增删改)

  • JdbcTemplate添加数据步骤:

    • 对应数据库创建实体类,生成gettersetter方法

    • 编写service和dao

      • 在dao中进行数据库添加操作,调用JdbcTemplate.update()方法,方法的第一个参数是sql语句,第二个参数是可变参数,填充占位符的值

    • 测试类

//======================Book.java==========================
public class Book {

private String userId;
private String username;
private String ustatus;

public String getUserId() {return userId;}
public void setUserId(String userId) {this.userId = userId;}
public String getUsername() {return username;}
public void setUsername(String username) {this.username = username;}
public String getUstatus() {return ustatus;}
public void setUstatus(String ustatus) {this.ustatus = ustatus;}
}
//======================BookService.java==========================
@Service
public class BookService {

// 注入dao类
@Autowired
private BookDao bookDao;

// 数据库添加的方法(使用注入的dao)
public void addBook(Book book){
bookDao.add(book);
}

// 数据库修改的方法
public void updateBook(Book book){
bookDao.updateBook(book);
}

// 数据库删除的方法
public void deleteBook(String id){
bookDao.deleteBook(id);
}
}
//======================BookDao接口==========================
public interface BookDao {
void add(Book book);
void updateBook(Book book);
void deleteBook(String id);
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{

// 注入JdbcTemplate
@Autowired
private JdbcTemplate 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()};
jdbcTemplate.update(sql, args);
}

// 数据库的修改方法
@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);
}

// 数据库的删除方法
@Override
public void deleteBook(String id) {
String sql = "delete from t_book where user_id=?";
int update = jdbcTemplate.update(sql, id);
System.out.println(update);
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);

// 添加
// Book book = new Book();
// book.setUserId("1");
// book.setUsername("name1");
// book.setUstatus("status1");
// bookService.addBook(book);

// 修改
// Book book = new Book();
// book.setUserId("1");
// book.setUsername("name11");
// book.setUstatus("status11");
// bookService.updateBook(book);

// 删除
// bookService.deleteBook("1");
}
<!--xml配置和上一节相同-->

 

4.3、JdbcTemplate操作数据库(查询)

  • 查询返回某个值:jdbcTemplate.queryForObject

截屏2022-05-06 11.59.51

//======================BookService.java==========================
@Service
public class BookService {

// 注入dao类
@Autowired
private BookDao bookDao;

// 查询表的总记录数
public int findCount(){
return bookDao.selectCount();
}
}
//======================BookDao接口==========================
public interface BookDao {
int selectCount();
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{

// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;

// 查询返回某个值的方法
@Override
public int selectCount() {
String sql = "select count(*) from t_book";
// queryForObject有两个参数,第一个参数为sql语句,第二个参数为返回类型Class
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);

// 调用方法,查询返回某个值
System.out.println(bookService.findCount());
}

 

  • 查询返回对象:jdbcTemplate.queryForObject

截屏2022-05-06 12.01.59

queryForObject的三个参数: 第一个为sql语句 第二个为RowMapper接口,使用接口中的实现类可以封装查询返回的数据 第三个为sql语句中的值

//======================BookService.java==========================
@Service
public class BookService {

// 注入dao类
@Autowired
private BookDao bookDao;

// 查询返回一个对象
public Book findOne(String id){
return bookDao.findBookInfo(id);
}
}
//======================BookDao接口==========================
public interface BookDao {
Book findBookInfo(String id);
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{

// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;

// 查询返回对象
@Override
public Book findBookInfo(String id) {
String sql = "select user_id, username, ustatus from t_book where user_id=?";
// queryForObject的三个参数:
// 第一个为sql语句
// 第二个为RowMapper接口,用于封装不同类型数据,使用接口中的实现类可以实现对查询返回的数据封装
// 第三个为sql语句中的值
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
return book;
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);

// 查询返回对象
Book book = bookService.findOne("1");
System.out.println(book.toString());
}

 

  • 查询返回集合:jdbcTemplate.query

截屏2022-05-06 13.18.17

//======================BookService.java==========================
@Service
public class BookService {

// 注入dao类
@Autowired
private BookDao bookDao;

// 查询返回一个集合
public List<Book> findAll(){
return bookDao.findAllBook();
}
}
//======================BookDao接口==========================
public interface BookDao {
List<Book> findAllBook();
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{

// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;

// 查询返回集合
@Override
public List<Book> findAllBook() {
String sql = "select * from t_book";
List<Book> books = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return books;
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);

// 查询返回集合
List<Book> list = bookService.findAll();
list.forEach(System.out::println);
}

 

4.4、JdbcTemplate操作数据库(批量操作)

批量操作方法:jdbcTemplate.batchUpdate

  • 批量添加:jdbcTemplate.batchUpdate

    截屏2022-05-06 13.22.40

    batchUpdate的两个参数:第一个为sql语句,第二个为list集合,包含要添加的多条记录

//======================BookService.java==========================
@Service
public class BookService {

// 注入dao类
@Autowired
private BookDao bookDao;

// 批量添加
public void batchAdd(List<Object[]> batchArgs){
bookDao.batchAddBook(batchArgs);
}
}
//======================BookDao接口==========================
public interface BookDao {
void batchAddBook(List<Object[]> batchArgs);
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{

// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;

// 批量添加
@Override
public void batchAddBook(List<Object[]> batchArgs) {
String sql = "insert into t_book values(?,?,?)";
// 使用batchUpdate进行批量操作
// 直接传入集合即可,语句会遍历集合并逐个添加
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);

// 批量添加
List<Object[]> books = new ArrayList<>();
Object[] o1 = {"3" , "33", "333"};
Object[] o2 = {"4" , "44", "444"};
Object[] o3 = {"5" , "55", "555"};
books.add(o1);
books.add(o2);
books.add(o3);

bookService.batchAdd(books);
}

 

  • 批量修改:jdbcTemplate.batchUpdate

//======================BookService.java==========================
@Service
public class BookService {

// 注入dao类
@Autowired
private BookDao bookDao;

// 批量修改
public void batchUpdate(List<Object[]> batchArgs){
bookDao.batchUpdateBook(batchArgs);
}
}
//======================BookDao接口==========================
public interface BookDao {
void batchUpdateBook(List<Object[]> batchArgs);
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{

// 注入JdbcTemplate
@Autowired
private JdbcTemplate 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));
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);

// 批量修改
List<Object[]> books = new ArrayList<>();
Object[] o1 = {"333", "33","3"};
Object[] o2 = {"444", "44", "4"};
Object[] o3 = {"555", "55", "5"};
books.add(o1);
books.add(o2);
books.add(o3);

bookService.batchUpdate(books);
}

 

  • 批量删除:jdbcTemplate.batchUpdate

//======================BookService.java==========================
@Service
public class BookService {

// 注入dao类
@Autowired
private BookDao bookDao;

// 批量删除
public void batchDelete(List<Object[]> batchArgs){
bookDao.batchDeleteBook(batchArgs);
}
}
//======================BookDao接口==========================
public interface BookDao {
void batchDeleteBook(List<Object[]> batchArgs);
}
//======================BookDaoImpl.java==========================
@Repository
public class BookDaoImpl implements BookDao{

// 注入JdbcTemplate
@Autowired
private JdbcTemplate 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));
}
}
//======================测试==========================
@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);

// 批量删除
List<Object[]> books = new ArrayList<>();
Object[] o1 = {"3"};
Object[] o2 = {"4"};
Object[] o3 = {"5"};
books.add(o1);
books.add(o2);
books.add(o3);

bookService.batchDelete(books);
}

 

5、事务管理

5.1、事务概念

事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败

5.2、搭建事务操作环境

截屏2022-05-06 14.35.25

  1. 数据库中创建表,添加记录

    以转账为例

    create table t_account
    (
    id varchar(20) not null
    primary key,
    username varchar(50) null,
    money int null
    );

    截屏2022-05-06 14.40.33

  2. 创建service,搭建dao,完成对象创建和建立注入关系

    1. 在service中注入dao,在dao注入JdbcTemplate,在JdbcTemplate中注入DataSource

  3. 在dao中创建增加money和减少money的两个方法(也可以写成同一个修改money的方法),在service中创建转账的方法

//======================UserService.java==========================
@Service
public class UserService {

// 在service中注入dao
@Autowired
private UserDao userDao;

public void accountMoney(){
try {
// 第一步:开启事务

// 第二步:进行业务操作
userDao.reduceMoney(100, "mary");
// 模拟异常
// int i = 10/0;
userDao.addMoney(100, "lucy");

// 第三步:没有异常则提交事务
} catch(Exception e){
// 第四步:有异常时回滚事务
}

}

}
//======================UserDao接口==========================
public interface UserDao {

public void addMoney(int m, String username);

public void reduceMoney(int m, String username);

}
//======================UserDaoImpl.java==========================
@Repository
public class UserDaoImpl implements UserDao{

// 在dao中注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;

@Override
public void addMoney(int m, String username) {
String sql = "update t_account set money=money-? where username=?";
int update = jdbcTemplate.update(sql, m, username);
}

@Override
public void reduceMoney(int m, String username) {
String sql = "update t_account set money=money+? where username=?";
int update = jdbcTemplate.update(sql, m, username);
}
}
//======================测试==========================
@Test
public void testAccount(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}
<!--xml配置和上一节相同-->

 

事务操作过程:开启事务,进行业务操作,没有发生异常则提交事务,出现异常时事务回滚

 

5.3、Spring事务管理介绍

建议将事务添加到JavaEE三层结构(Web Service Dao)的Service层(业务逻辑层)

在Spring中进行事务管理操作有两种方式:编程式事务管理、声明式事务管理(常用)

  • 声明式事务管理:声明式事务管理底层使用的是AOP

    1. 基于注解方式(最常用)

    2. 基于xml配置文件方式

Spring事务管理API:Spring提供了一个接口PlatformTransactionManager,代表事务管理器,针对不同的数据库操作框架提供了不同的实现类,对于JdbcTemplate使用DataSourceTransactionManager

 

5.4、基于注解的声明式事务管理

  • 使用基于注解的声明式事务管理的步骤:

    1. 在spring配置文件中配置事务管理器

    2. 在spring配置文件中引入tx名称空间,开启事务注解

    3. 在service类(或类中的方法)上添加事务注解@Transactional,如果在类上添加则说明对类中每一个方法都启用事务

    <!--1.创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!--2.引入tx名称空间-->

<!--2.开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
//===================UserService.java=======================
@Service
@Transactional // 事务注解,启用事务
public class UserService {
}

 

5.5、声明式事务管理的参数配置

在service类添加的注解@Transactional中可以配置事务相关参数

  • propagation():事务传播行为

    • 事务传播行为

    • 图7

    • 默认为required,通过propagation配置:@Transactional(propagation = Propagation.REQUIRED)

  • isolation():事务隔离级别

    • 事务隔离级别

    • 默认为可重复读,通过isolation配置:@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)

  • timeout:超时时间

    • 超时不提交自动回滚

    • 默认为-1,时间以秒为单位,通过timeout配置

  • readOnly:是否只读

    • 默认为false,表示可增删改查

    • 通过readOnly配置

  • rollbackFor:回滚

    • 设置出现哪些异常时进行事务回滚

  • noRollbackFor:不回滚

    • 设置出现哪些异常时不回滚

 

5.6、基于xml的声明式事务管理

  • 基于xml进行声明式事务管理的步骤:

    1. 在Spring配置文件中进行配置

      1. 配置事务管理器

      2. 配置通知

      3. 配置切入点和切面

<!--===================bean4.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"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--开启组件扫描-->
<context:component-scan base-package="com.spring5"></context:component-scan>

<!--引入外部属性文件-->
<!--1.引入context名称空间-->
<!--2.引入外部属性文件-->
<context:property-placeholder location="classpath:/com/spring5/resources/jdbc.properties"/>
<!--3.配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--使用表达式${名称}获取属性值-->
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>

<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!--1.创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!--引入tx名称空间-->

<!--2.配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定在符合哪种规则的方法上添加事务,并设置事务管理参数-->
<!--通过方法名称添加-->
<tx:method name="accountMoney" propagation="REQUIRED" isolation="REPEATABLE_READ"/>
<tx:method name="account*"/>
</tx:attributes>
</tx:advice>

<!--3.配置切入点和切面-->
<!--引入aop名称空间-->
<aop:config>
<!--配置切入点-->
<!--切入点为UserService中的所有方法-->
<aop:pointcut id="pt" expression="execution(* com.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>

</beans>

 

5.7、完全注解声明式事务管理

  • 完全注解声明式事务管理步骤

    1. 创建配置类,使用配置类代替xml配置文件

//====================TxConfig.java=======================
@Configuration // 表示这个类是配置类
@ComponentScan(basePackages = "com.spring5") // 开启组件扫描
@EnableTransactionManagement // 开启事务
public class TxConfig {

// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true");
dataSource.setUsername("root");
dataSource.setPassword("12345678");
return dataSource;
}

// 创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入dataSource
// 在IOC容器中已经存在dataSource对象,直接注入即可
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}

// 创建事务管理器对象
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}

}
//======================测试========================
@Test
public void testAccount2(){
ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}

 

6、Spring5新特性

6.1、整合日志框架

整个 Spring5 框架的代码基于Java8,运行时兼容JDK9,许多不建议使用的类和方法在代码库中删除

 

Spring 5.0 框架自带了通用的日志封装 Log4j2

  • Spring5.0中使用Log4j2的步骤:

    1. 导入jar包:log4j-api-2.11.2.jarlog4j-core-2.11.2.jarlog4j-slf4j-impl-2.11.2.jarslf4j-api-1.7.30.jar

    2. 创建log4j2.xml配置文件

<!--===================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>

 

6.2、@Nullable注解

Spring5 框架核心容器支持 @Nullable 注解

@Nullable 注解可以使用在方法上,属性上,方法的参数上,表示方法返回可以为空,属性值可以为空,参数值可以为空

截屏2022-05-09 15.20.02

6.3、函数式注册对象

Spring5 核心容器支持函数式风格 GenericApplicationContext

//函数式风格创建对象,交给 spring 进行管理
@Test
public void testGenericApplicationContext() {
// 1.创建 GenericApplicationContext 对象
GenericApplicationContext context = new GenericApplicationContext();

// 2.调用 context 的方法对象注册
context.refresh();
context.registerBean("user1",User.class,() -> new User());

// 3.获取在 spring 注册的对象
// User user = (User)context.getBean("com.atguigu.spring5.test.User");
User user = (User)context.getBean("user1");
System.out.println(user);
}

 

6.4、整合JUnit5单元测试框架

Spring5 支持整合 JUnit5

  • 整合JUnit4

    1. 引入相关jar包:spring-test-5.2.6.RELEASE.jar

    2. 使用注解方式创建测试类

//====================JTest4.java=======================
@RunWith(SpringJUnit4ClassRunner.class) // 引入单元测试框架
@ContextConfiguration("classpath:bean4.xml") // 加载配置文件
public class JTest4 {

@Autowired // 创建对象
private UserService userService;

@Test
public void test1(){
userService.accountMoney();
}

}
  • 整合JUnit5

    1. 引入相关依赖:JUnit5

    2. 使用注解创建测试类

//====================JTest5.java=======================
// @ExtendWith(SpringExtension.class) // 引入单元测试框架
// @ContextConfiguration("classpath:bean4.xml") // 加载配置文件
// 使用一个复合注解替代上面两个注解完成整合
@SpringJUnitConfig(locations = "classpath:bean4.xml")
public class JTest5 {

@Autowired
private UserService userService;

@Test
public void test2(){
userService.accountMoney();
}

}
//====================JTest5.java=======================

 

6.5、SpringWerflux使用

SpringWebflux介绍

前置知识:SpringMVC、Springboot、Maven、Java8新特性

SpringWebflux 是 Spring5 添加新的模块,用于 web 开发,功能和 SpringMVC 类似,Webflux 使用当前一种比较流程响应式编程出现的框架。

传统 web 框架,比如 SpringMVC,基于 Servlet 容器,Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的。

异步非阻塞: 异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步 阻塞和非阻塞针对被调用者,被调用者受到请求之后,做完请求任务之后才给出反馈就是阻塞,受到请求之后马上给出反馈然后再去做事情就是非阻塞

Webflux的特点: 第一 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程 第二 函数式编程:Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求

Webflux和SpringMVC: 第一 两个框架都可以使用注解方式,都运行在 Tomet 等容器中 第二 SpringMVC 采用命令式编程,Webflux 采用异步响应式编程

响应式编程

响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。

Webflux执行流程和核心API

 

SpringWebflux(基于注解编程模型)

 

SpringWebflux(基于函数式编程模型)

 

 

 

 

posted @ 2022-08-31 01:50  Colin13  阅读(25)  评论(0编辑  收藏  举报