Spring从0到1,彻底学会Spring思想

为什么需要Spring

Sping实现了控制反转IoC,Spring可以帮我们new对象,帮我们维护对象之间的关系,Spring是一个实现了IoC思想的容器

看一个案例就知道了

public interface UserMapper { void delete(); }
public interface UserService { void deleteUser();}
public class UserMapperImplForMysql implements UserMapper {
    public void delete() {
        System.out.println("持久层正在删除数据");
    }
}
public class UserServiceImplForMysql implements UserService {
    private UserMapper userMapper = new UserMapperImplForMysql();
    public void deleteUser() {
        userMapper.delete();
    }
}
public class UserAction {
    private UserService userService = new UserServiceImplForMysql();
    public void demo(){
        userService.deleteUser();
    }
}
public class Test {
    public static void main(String[] args) {
        new UserAction().demo();
    }
}
//持久层正在删除数据

以上程序,耦合度太强了:

  • private UserMapper userMapper = new UserMapperImplForMysql();
  • private UserService userService = new UserServiceImplForMysql();

在实现接口的时候,具体的写出来要new的对象,如果我不想用mysql数据库呢?

是不是要重新new一个UserServiceImplForOracleUserMapperImplForOracle

耦合度太强了,上层接口依赖下层实现

所以可以:

  • private UserMapper userMapper;
  • private UserService userService;

面向抽象编程,只负责创建接口,具体的实现类(子类指引)(创建对象)让别的来做


OCP

OCP开闭原则,对扩展开放,对修改关闭

可以增加代码,但是不能修改原来的代码

当进行系统功能扩展的时候,如果动了之前稳定的程序,修改了之前的程序,之前所有程序都需要进行重新测试,这是不愿看到的,因为非常的麻烦,要进行大量的单元测试


DIP

依赖倒置原则,面向抽象编程,面向接口编程

降低程序的耦合度,提高拓展力


什么叫做符合依赖倒置

上 不依赖 下


什么叫做违背依赖倒置

上 依赖 下,就是违背

只要“下”一改动,“上”就受到牵连


什么是控制反转

控制反转IoC(Inversion of Control)

反转是什么?

  • 反转的是两件事
    1. 不在程序中采用硬编码的方式new对象了
    2. 不在程序中采用硬编码的方式来维护对象的关系了
  • 控制反转是一种编程思想,由于出现的比较新,没有被纳入GoF23中设计模式范围内
  • 控制反转的实现方式有很多,其中比较重要的叫做:依赖注入(Dependency Injection) 简称DI
  • 控制反转是思想,依赖注入是这种思想的具体实现
  • 依赖注入DI,包括两种常见的方式:
    • set注入(执行set方法给属性赋值)
    • 构造方法注入(执行构造方法给属性赋值)
  • 依赖注入中的 “依赖” 是什么意思?
    • 对象A跟对象B之间的关系
  • 依赖注入中的 “注入” 是什么意思?
    • 一种手段,可以让对象A跟对象B产生关系

Spring

Spring5版本之后是8个模块,spring5新增了Webflux模块

在这里插入图片描述


Spring特点

  1. 轻量
    • 从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的
    • Spring是非侵入式的:Spring应用中的对象不依赖于Spring的特定类。
  2. 控制反转
    • Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
  3. 面向切面
    • Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
  4. 容器
    • Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
  5. 框架
    • Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你

小试牛刀

package com.wzw.spring6.bean;
public class User {}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spinrg6-02</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <repositories>
        <repository>
            <id>repository.spring.milestone</id>
            <name>Spring Milestone Repository</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.19.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.19.0</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

</project>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--
        配置bean
        id是这个bean的身份证号,不能重复,是唯一标识
        class:必须是类的全限定类名
-->
    <bean id="userBean" class="com.wzw.spring6.bean.User"/>
</beans>
package com.wzw.spring6.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class FistSpringTest {
    @Test
    public void TestFirstSpringCode(){
        //第一步获取Spring容器对象
        //ApplicationContext的翻译是:应用上下文
        //ApplicationContext是一个接口
        //ApplicationContext 接口下有很多实现类,其中有一个实现类叫做ClassPathXmlApplicationContext
        //ClassPathXmlApplicationContext 专门从类路径当中加载Spring配置文件的一个Spring上下文对象
        //下面的这行代码只要一执行,就相当于启动Spring容器,Spring就开始解析spring.xml文件,并且开始实例化所有的bean对象,放到Spring容器中
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        //第二步,根据bean的idcong spring容器中获取这个对象
        Object userBean = applicationContext.getBean("userBean");
        System.out.println(userBean);
    }
}

输出:com.wzw.spring6.bean.User@63355449

  • 第一步,获取Spring容器对象

    • ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");

    • ApplicationContext的翻译是:应用上下文

    • ApplicationContext是一个接口

    • ApplicationContext 接口下有很多实现类,其中有一个实现类叫做ClassPathXmlApplicationContext

    • ClassPathXmlApplicationContext 专门从类路径当中加载Spring配置文件的一个Spring上下文对象

    • ClassPathXmlApplicationContext()可以有多个参数,可以放多个xml文件

  • 第二步,根据bean的id从 spring容器中获取这个对象

    • Object userBean = applicationContext.getBean("userBean");
    • getBean()可以有多个参数,指定转换的类型,比如:
      • UserDaoImplForyMysql userDaoBean = applicationContext.getBean("userDaoBean",UserDaoImplForyMysql.class);
  • 底层是怎么创建对象的呢?

    • 实际上底层是根据反射机制通过Class.forName()创建出来的
    • Class clazz = Class.forName("com.wzw.spring6.bean.User")
    • clazz.newInstance()创建对象(调用无参构造方法)

groovyScript(“if(”KaTeX parse error: Expected group as argument to '\"' at position 7: {_1}\" ̲== 'void'){def …{_1}“; def result = ‘\r’ + returnType; return result;}”, methodReturnType());


LOG4J2

从Spring5之后,Spring框架支持集成的日志框架是Log4j2.如何启用日志框架:

第一步:引入Log4j2的依赖

<!--log4j2的依赖-->
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.19.0</version>
</dependency>
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-slf4j2-impl</artifactId>
  <version>2.19.0</version>
</dependency>

第二步:在类的根路径下提供log4j2.xml配置文件(文件名固定为:log4j2.xml,文件必须放到类根路径下。)

<?xml version="1.0" encoding="UTF-8"?>

<configuration>

    <loggers>
        <!--
            level指定日志级别,从低到高的优先级:
                ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
        -->
        <root level="DEBUG">
            <appender-ref ref="spring6log"/>
        </root>
    </loggers>

    <appenders>
        <!--输出日志信息到控制台-->
        <console name="spring6log" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
        </console>
    </appenders>

</configuration>

第三步:使用日志框架

Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
logger.info("我是一条日志消息");

Spring对IoC的实现

控制反转IoC(Inversion of Control)

反转是什么?

  • 反转的是两件事
    1. 不在程序中采用硬编码的方式new对象了
    2. 不在程序中采用硬编码的方式来维护对象的关系了
  • 控制反转是一种编程思想,由于出现的比较新,没有被纳入GoF23中设计模式范围内
  • 控制反转的实现方式有很多,其中比较重要的叫做:依赖注入(Dependency Injection) 简称DI
  • 控制反转是思想,依赖注入是这种思想的具体实现
  • 依赖注入DI,包括两种常见的方式:
    • set注入(执行set方法给属性赋值)
    • 构造方法注入(执行构造方法给属性赋值)
  • 依赖注入中的 “依赖” 是什么意思?
    • 对象A跟对象B之间的关系
  • 依赖注入中的 “注入” 是什么意思?
    • 一种手段,可以让对象A跟对象B产生关系

依赖注入

set注入

set注入,基于set方法实现的,底层会通过反射机制调用属性对应的set方法然后给属性赋值。这种方式要求属性必须对外提供set方法。

  • 第一步:创建bean:

    public class UserMapper {
        private static  final Logger logger = LoggerFactory.getLogger(UserMapper.class);
        public void insert(){ logger.info("数据库正在保存信息"); } }
    
    public class UserService {
        private UserMapper userMapper;
        public void saveUser(){
            userMapper.insert();
        }
        public void setUserMapper(UserMapper userMapper) {
            this.userMapper = userMapper;
        }
    }
    
  • 第二步:写配置文件

    • <property标签
      • name表示setXx中的xx,可以理解成属性名,但是是可以改变的,主要是“set”后面的名字,因为根据反射获取set后面的名字
      • ref代表要注入的bean对象,set方法需要传入一个对象,这个传入的对象就是userMapperBean
    <bean id="userMapperBean" class="com.wzw.spring6.mapper.UserMapper"/>
    <bean id="userServiceBean" class="com.wzw.spring6.service.UserService">
        <property name="userMapper" ref="userMapperBean"/>
    </bean>
    
  • 测试:

    @Test
    public void TestDemo(){
        UserDaoImpl userDaoBean1 = applicationContext.getBean("userDaoBean", UserDaoImpl.class);
        userDaoBean1.fun();
    }
    

    结果:2022-11-07 22:37:18 895 [main] INFO com.wzw.spring6.mapper.UserMapper - 数据库正在保存信息


构造注入

核心原理:通过调用构造方法来给属性赋值。

  • 第一步,创建bean对象
public class UserMapper {
    private static  final Logger logger = LoggerFactory.getLogger(UserMapper.class);
    public void insert(){ logger.info("数据库正在保存信息"); } }
public class VipMapper {
    private static Logger logger = LoggerFactory.getLogger(VipMapper.class);
    public void delete(){ logger.info("viperMapper执行操作"); } }
public class CustomerService {
    private UserMapper userMapper;
    private VipMapper vipMapper;
    public void cs(){
        userMapper.insert();
        vipMapper.delete();
    }
    public CustomerService(UserMapper userMapper, VipMapper vipMapper) {
        this.userMapper = userMapper;
        this.vipMapper = vipMapper;
    }
}
  • 第二步,写好配置文件
    • constructor-arg标签
      • index代表构造器中的参数,0代表第一个参数,1代表第二个参数
      • ref还是代表要注入的bean对象
      • name可以替代index,直接指定注入的参数类型
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userMapperBean" class="com.wzw.spring6.mapper.UserMapper"/>
    <bean id="vipMapperBean" class="com.wzw.spring6.mapper.VipMapper"/>
    <bean id="csService" class="com.wzw.spring6.service.CustomerService">
        <constructor-arg index="0" ref="userMapperBean"/>
        <constructor-arg index="1" ref="vipMapperBean"/>
    </bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userMapperBean" class="com.wzw.spring6.mapper.UserMapper"/>
    <bean id="vipMapperBean" class="com.wzw.spring6.mapper.VipMapper"/>
    <bean id="csService" class="com.wzw.spring6.service.CustomerService">
        <constructor-arg name="userMapper" ref="userMapperBean"/>
        <constructor-arg name="VipMapper" ref="vipMapperBean"/>
    </bean>
</beans>
  • 测试:
 @Test
    public void TestDemo(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
        CustomerService csService = applicationContext.getBean("csService", CustomerService.class);
        csService.cs();
    }

结果:

2022-11-07 23:02:37 839 [main] INFO com.wzw.spring6.mapper.UserMapper - 数据库正在保存信息
2022-11-07 23:02:37 840 [main] INFO com.wzw.spring6.mapper.VipMapper - viperMapper执行操作


总结

set注入根据set方法,通过赋值给属性注入对象,是在创建完bean对象之后才开始注入

构造注入根据构造器,在创建bean对象的时候,注入对象


set注入专题

外部注入

通过ref标签进行外部注入,用的最多

<bean id="orderMapperBean" class="com.wzw.spring6.mapper.OrderMapper"/>
<bean id="orderServiceBean" class="com.wzw.spring6.service.OrderService">
    <property name="orderMapper" ref="orderMapperBean"/>
</bean>

内部注入

直接写在property标签内

<bean id="orderServiceBean2" class="com.wzw.spring6.service.OrderService">
    <property name="orderMapper">
        <bean class="com.wzw.spring6.mapper.OrderMapper"/>
    </property>
</bean>

简单注入

简单数据类型注入

//通过set方法注入
<bean id="userBean" class="com.wzw.spring6.bean.User">
    <property name="username" value="zs"/>
    <property name="age" value="20"/>
    <property name="password" value="123456"/>
</bean>

//通过构造器方法注入
<bean id="userBean2" class="com.wzw.spring6.bean.User">
    <constructor-arg index="0" value="zs"/>
    <constructor-arg index="1" value="123"/>
    <constructor-arg index="2" value="20"/>
</bean>

哪些数据是简单类型?

public static boolean isSimpleValueType(Class<?> type) {
		return (Void.class != type && void.class != type &&
				(ClassUtils.isPrimitiveOrWrapper(type) ||
				Enum.class.isAssignableFrom(type) ||
				CharSequence.class.isAssignableFrom(type) ||
				Number.class.isAssignableFrom(type) ||
				Date.class.isAssignableFrom(type) ||
				Temporal.class.isAssignableFrom(type) ||
				URI.class == type ||
				URL.class == type ||
				Locale.class == type ||
				Class.class == type));
	}
  • 基本数据类型
  • 基本数据类型对应的包装类
  • String或其他的CharSequence子类
  • Number子类
  • Date子类,在实际开发中,不会把Date当作简单数据类型,一般会采用ref给Date类型的属性赋值
  • Enum子类
  • URI
  • URL
  • Temporal子类,java8提供的时间和时区类型
  • Locale,语言类
  • Class
  • 另外还包括以上简单值类型对应的数组类型。

级联赋值

应用场景:Mybatis中的多对一关系

<bean id="studentBean" class="com.wzw.spring6.bean.Student">
    <property name="name" value="张三"/>
    <property name="age" value="18"/>
    <property name="clazz" ref="clazzBean"/>
    <property name="clazz.name" value="张三"/>
    <property name="clazz.clazz" value="高三一班"/>
</bean>
<bean id="clazzBean" class="com.wzw.spring6.bean.Clazz"/>
<bean id="studentBean2" class="com.wzw.spring6.bean.Student">
    <property name="name" value="张三"/>
    <property name="age" value="18"/>
    <property name="clazz" ref="clazzBean2"/>
</bean>
<bean id="clazzBean2" class="com.wzw.spring6.bean.Clazz">
    <property name="name" value="张三"/>
    <property name="clazz" value="高三二班"/>
</bean>
public class Student {
    private String name;
    private Integer age;
    private Clazz clazz;
public class Clazz {
    private String name;
    private String clazz;
注意!

这里有几个点要注意

  1. 使用级联赋值的时候要注意先 <property name="clazz" ref="clazzBean"/>,然后才能给clazz里面的属性赋值
  2. 属性clazz要有get方法,因为clazz.name是先getClazz()获取对象然后才赋值set

注入数组

  • 如果是简单类型的数组,注入的时候采用value:

    • <property name="hobbies">
              <array>
                  <value>抽烟</value>
              </array>
      </property>
      
  • 如果是复杂类型的数组,注入的时候要用ref引入:

    • <bean id="w1" class="com.wzw.spring6.bean.woman">
          <property name="name" value="小明"/>
      </bean>
      
    • <property name="women">
              <array>
                  <ref bean="w1"/>
              </array>
          </property>
      

具体的案例

public class Man {
    private String[] hobbies;
    private woman[] women;
    @Override
    public String toString() {
        return "Man{" +
                "hobbies=" + Arrays.toString(hobbies) +
                ", women=" + Arrays.toString(women) +
                '}';
    }
    public void setHobbies(String[] hobbies) {
        this.hobbies = hobbies;
    }
    public void setWomen(woman[] women) {
        this.women = women;
    }
}
public class woman {
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "woman{" +
                "name='" + name + '\'' +
                '}';
    }
}
<bean id="w1" class="com.wzw.spring6.bean.woman">
    <property name="name" value="小明"/>
</bean>
<bean id="w2" class="com.wzw.spring6.bean.woman">
    <property name="name" value="小红"/>
</bean>
<bean id="w3" class="com.wzw.spring6.bean.woman">
    <property name="name" value="小花"/>
</bean>
<bean id="w4" class="com.wzw.spring6.bean.woman">
    <property name="name" value="小草"/>
</bean>

<bean id="manBean" class="com.wzw.spring6.bean.Man">
    <property name="hobbies">
        <array>
            <value>抽烟</value>
            <value>喝酒</value>
            <value>烫头</value>
        </array>
    </property>
    <property name="women">
        <array>
            <ref bean="w1"/>
            <ref bean="w2"/>
            <ref bean="w3"/>
            <ref bean="w4"/>
        </array>
    </property>
</bean>

结果:

Man{hobbies=[抽烟, 喝酒, 烫头], women=[woman{name=‘小明’}, woman{name=‘小红’}, woman{name=‘小花’}, woman{name=‘小草’}]}


注入Set&注入List&注入Map

这两个跟数组类似,只是把array变成了list(set)

  • 如果是简单类型的数组,注入的时候采用value:

    • <property name="hobbies">
              <list>
                  <value>抽烟</value>
              </list>
      </property>
      <property name="address">
              <set>
                  <value>上海</value>
                  <value>北京</value>
              </set>
      </property>
      <property name="phones">
              <map>
                  <entry key="1" value="123"/>
                  <entry key="1" value="456"/>
              </map>
      </property>
      
  • 如果是复杂类型的数组,注入的时候要用ref引入:

    • <bean id="w1" class="com.wzw.spring6.bean.woman">
          <property name="name" value="小明"/>
      </bean>
      
    • <property name="women">
              <list>
                  <ref bean="w1"/>
              </list>
      </property>
      <property name="">
              <map>
                   <entry key-ref="" value-ref=""/>
              </map>
      </property>
      
      
注意

List集合允许有重复的值,Set不允许有重复的值,Map中如果有相同的key,会覆盖上一个的key的value

如果在set注入的时候,注入了一样的值,值会覆盖掉,即只显示一个

具体的案例

public class Person {
    private List<String> hobbies;
    private List<woman> women;
    private Set<String> address;
    private Map<Integer,String> phones;
    private Properties properties;
 <bean id="w1" class="com.wzw.spring6.bean.woman">
        <property name="name" value="小明"/>
    </bean>
    <bean id="w2" class="com.wzw.spring6.bean.woman">
        <property name="name" value="小红"/>
    </bean>
    <bean id="personBean" class="com.wzw.spring6.bean.Person">
        <property name="hobbies">
            <list>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>烫头</value>
                 <value>烫头</value>
            </list>
        </property>
        <property name="women">
            <list>
                <ref bean="w1"/>
                <ref bean="w2"/>
            </list>
        </property>
        <property name="address">
            <set>
                <value>上海</value>
                <value>上海</value>
                <value>北京</value>
            </set>
        </property>
        <property name="phones">
            <map>
                <entry key="1" value="123"/>
                <entry key="1" value="456"/>
                <entry key="2" value="456"/>
                <entry key="3" value="789"/>
            </map>
        </property>
        <property name="properties">
            <props>
                <prop key="driver">com.mysql.wzw.jdbc.Driver</prop>
                <prop key="url">jdbc:mysql://localhost:3306/spring6</prop>
                <prop key="username">root</prop>
                <prop key="password">123456</prop>
            </props>
        </property>
    </bean>
@Test
public void TestPerson(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-person.xml");
    Person personBean = applicationContext.getBean("personBean", Person.class);
    System.out.println(personBean);
}

结果:

Person{hobbies=[抽烟, 喝酒, 烫头,烫头], women=[woman{name=‘小明’}, woman{name=‘小红’}], address=[上海,北京]},phones={1=456, 2=456, 3=789}}, properties={password=123456, driver=com.mysql.wzw.jdbc.Driver, url=jdbc:mysql://localhost:3306/spring6, username=root}}


注入Properties

属性类对象,Properties本质上也是一个Map集合,Properties的父类Hashtable,Hashtable实现了Map接口

虽然这个也是一个Map集合,但是和Map集合的注入方式有点像,但是不一样

Properties的key和value只能是String类型,不能是别的类型

<property name="properties">
    <props>
        <prop key="driver">com.mysql.wzw.jdbc.Driver</prop>
        <prop key="url">jdbc:mysql://localhost:3306/spring6</prop>
        <prop key="username">root</prop>
        <prop key="password">123456</prop>
    </props>
</property>

注入NUll跟空字符

如果不注入,属性的默认值就是null

  • 手动注入null:

    • <properties name="">
      	<null/>
      </properties>
      
  • 注入空字符串

    • <properties name="">
      	<value/>
      </properties>
      
    • <properties name="" value="">
      </properties>
      

注入特殊字符

什么是特殊字符?

  • XML中有5个特殊字符,分别是:<、>、'、"、&

解决方案包括两种:

  • 第一种:特殊符号使用转义字符代替。
  • 第二种:将含有特殊符号的字符串放到:<![CDATA[]]> 当中。因为放在CDATA区中的数据不会被XML文件解析器解析。

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="mathBean" class="com.powernode.spring6.beans.Math">
        <property name="result">
            <!--只能使用value标签-->
            <value><![CDATA[2 < 3]]></value>
        </property>
    </bean>

</beans>

注意:使用CDATA时,不能使用value属性,只能使用value标签。


总结

public class Student {
    private String name;
    private int age;
    private List<Friend> friends;
    private List<String> hobbies;
    private Map<String,Long> phones;
    private String[] banks;
    private Properties properties;
public class Friend {
    private String name;
    private int age;
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="f1" class="com.wzw.spring6.demo.Friend">
        <property name="name" value="张三"/>
        <property name="age" value="20"/>
    </bean>
    <bean id="f2" class="com.wzw.spring6.demo.Friend">
        <property name="name" value="李四"/>
        <property name="age" value="20"/>
    </bean>
    <bean id="f3" class="com.wzw.spring6.demo.Friend">
        <property name="name" value="王五"/>
        <property name="age" value="20"/>
    </bean>

    <bean id="xxxix" class="com.wzw.spring6.demo.Student">
        <property name="name" value="zs"/>
        <property name="age" value="12"/>
        <property name="hobbies">
            <list>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>烫头</value>
            </list>
        </property>
        <property name="banks">
            <array>
                <value>农商</value>
                <value>工商</value>
            </array>
        </property>
        <property name="friends">
            <list>
                <ref bean="f1"/>
                <ref bean="f2"/>
                <ref bean="f3"/>
            </list>
        </property>
        <property name="phones">
            <map>
                <entry key="张三" value="123"/>
                <entry key="李四" value="456"/>
            </map>
        </property>
        <property name="properties">
            <props>
                <prop key="key1">value1</prop>
                <prop key="key2">value2</prop>
            </props>
        </property>
    </bean>
</beans>

Student{

name=‘zs’,

age=12,

friends=[Friend{name=‘张三’, age=20}, Friend{name=‘李四’, age=20}, Friend{name=‘王五’, age=20}], hobbies=[抽烟, 喝酒, 烫头],

phones={张三=123, 李四=456}, banks=[农商, 工商],

properties={key1=value1, key2=value2}

}


P命名空间注入

底层还是set注入,只是使用P命名空间注入可以简化set注入

在xml中配置: xmlns:p="http://www.springframework.org/schema/p"

public class Dog {
    private String name;
    private int age;
    private Date brith;
    //set方法
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="dogBean" class="com.wzw.spring6.bean.Dog" p:name="小花" p:age="28" p:brith-ref="brithBean"/>
    <bean id="brithBean" class="java.util.Date"/>

</beans>

结果:Dog{name=‘小花’, age=28, brith=Tue Nov 08 17:53:57 CST 2022}


C命名空间注入

底层是构造方法注入,使用C命名空间注入简化构造注入

在xml中配置: xmlns:c="http://www.springframework.org/schema/c"

public class Dog2 {
    private String name;
    private int age;
    private Date brith;
    //构造方法
<?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:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="dog2Bean" class="com.wzw.spring6.bean.Dog2" c:_0="zs" c:_1="20" c:_2-ref="birthBean" />
    <bean id="birthBean" class="java.util.Date"/>
</beans>

Dog2{name=‘zs’, age=20, brith=Tue Nov 08 17:54:09 CST 2022}


util命名空间

使用util命名空间可以让配置复用

没有用util之前,代码复用率低

在这里插入图片描述

如何配置util命名空间?

在这里插入图片描述

配置两条语句,类似p空间跟c空间

xmlns:util="http://www.springframework.org/schema/util"
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
如何使用?
<util:properties id="prop">
    <prop key="driver">com.mysql.wzw.jdbc.Driver</prop>
    <prop key="url">jdbc:mysql://localhost:3306/spring6</prop>
    <prop key="username">root</prop>
    <prop key="password">123456</prop>
</util:properties>


<bean id="dataSource1" class="com.wzw.spring6.bean.Datasource1" >
    <property name="properties" ref="prop"/>
</bean>
<bean id="dataSource2" class="com.wzw.spring6.bean.Datasource2" >
    <property name="properties" ref="prop"/>
</bean>

util主要的应用范围是集合

在这里插入图片描述


基于XML的自动装配

Spring还可以完成自动化的注入,自动化注入又被称为自动装配。它可以根据名字进行自动装配,也可以根据类型进行自动装配。

自动装配也是基于set注入


根据name自动装配

要求:被注入对象的bean的id必须是类的简名

<bean id="orderMapper" class="com.wzw.spring6.mapper.OrderMapper"/>
<bean id="orderServiceBean3" class="com.wzw.spring6.service.OrderService" autowire="byName"/>

根据type自动装配

要求:根据类型进行自动装配的时候,在有效的配置文件当中,某种类型的实例只能有一个

<bean class="com.wzw.spring6.mapper.UserMapper"/>
<bean class="com.wzw.spring6.mapper.VipMapper"/>
<bean id="csService2" class="com.wzw.spring6.service.CustomerService" autowire="byType"/>

引入外部文件

  1. 引入context命名空间
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  1. 创建properties文件
driverClass=com.mysql.wzw.jdbc.Driver
url=jdbc:mysql://localhost:3306/spring6
username=root
password=123456
  1. 使用引用文件

    • 先导入外部文件,通过location引入资源

      <context:property-placeholder location="jdbc.properties"/>	
      
<bean id="dataSource" class="com.wzw.spring6.bean.DataSource">
    <property name="driver" value="${driverClass}"></property>
    <property name="url" value="${url}"></property>
    <property name="username" value="${username}"></property>
    <property name="password" value="${password}"></property>
</bean>

location默认从类的根路径下开始加载资源

注意!

有可能输出的时候username不是root,因为${username}默认先从windows系统开始找,就有可能找到的是本地计算机的名字,所以最好还是加点别的东西,例如:jdbc.username


Scope作用域

Spring默认情况下是如何管理这个bean的呢?

  • 默认情况下Bean是单例的

  • 在Spring上下文(applicationContext)初始化的时候实例化

    • 这里的applicationContext不只指的是ClassPathXmlApplicationContext
  • 每一次调用getBean() 方法的时候回,都返回这个单例的对象

    @Test
    public void testBeanScope(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
        SpringBean springBean = applicationContext.getBean("springBean", SpringBean.class);
        System.out.println(springBean);
        SpringBean springBean2 = applicationContext.getBean("springBean", SpringBean.class);
        System.out.println(springBean2);
        SpringBean springBean3 = applicationContext.getBean("springBean", SpringBean.class);
        System.out.println(springBean3);
    }
    //com.wzw.spring6.bean.SpringBean@1a451d4d
    //com.wzw.spring6.bean.SpringBean@1a451d4d
    //com.wzw.spring6.bean.SpringBean@1a451d4d
    

Scope属性

  • scope有多个属性:

    • prototype:多例
    • singleton:单例(默认情况下就是单例)
    • request:一次请求当中一个Bean(仅限在WEB应用中使用)
    • session:一次会话中只有一个Bean(仅限在WEB应用中使用)
    • global session:portlet应用中专用的
    • application:一个应用对应一个Bean(仅限在WEB应用中使用)
    • websocket:一个websocket声明周期对应Bean(仅限在WEB应用中使用)
    • 自定义scope:很少使用
  • 当scope="prototype"的时候,bean是多例的

    • Spring上下文初始化的时候,并不会初始化这些prototype的bean
    • 只有调用getBean()方法的时候,才会实例化该bean对象
<bean id="springBean" class="com.wzw.spring6.bean.SpringBean" scope="prototype"/>
@Test
public void testBeanScope(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
    SpringBean springBean = applicationContext.getBean("springBean", SpringBean.class);
    System.out.println(springBean);
    SpringBean springBean2 = applicationContext.getBean("springBean", SpringBean.class);
    System.out.println(springBean2);
    SpringBean springBean3 = applicationContext.getBean("springBean", SpringBean.class);
    System.out.println(springBean3);
}
//com.wzw.spring6.bean.SpringBean@7334aada
//com.wzw.spring6.bean.SpringBean@1d9b7cce
//com.wzw.spring6.bean.SpringBean@4d9e68d0

自定义Scope

  • 第一步:自定义Scope。(实现Scope接口)

    • spring内置了线程范围的类:org.springframework.context.support.SimpleThreadScope,可以直接用。
  • 第二步:将自定义的Scope注册到Spring容器中。

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
      <property name="scopes">
        <map>
          <entry key="myThread">
            <bean class="org.springframework.context.support.SimpleThreadScope"/>
          </entry>
        </map>
      </property>
    </bean>
    
  • 第三步:使用Scope

    <bean id="sb" class="com.powernode.spring6.beans.SpringBean" scope="myThread" />
    
  • 测试

    @Test
    public void testCustomScope(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
        SpringBean sb1 = applicationContext.getBean("sb", SpringBean.class);
        SpringBean sb2 = applicationContext.getBean("sb", SpringBean.class);
        System.out.println(sb1);
        System.out.println(sb2);
        // 启动线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                SpringBean a = applicationContext.getBean("sb", SpringBean.class);
                SpringBean b = applicationContext.getBean("sb", SpringBean.class);
                System.out.println(a);
                System.out.println(b);
            }
        }).start();
    }
    

GoF之工厂模式

设计模式:一种可以被重复利用的解决方案

  • 不过除了GoF23种设计模式之外,还有其它的设计模式,比如:JavaEE的设计模式(DAO模式、MVC模式等)

  • GoF23种设计模式可分为三大类:

    • 创建型(5个):解决对象创建问题。
      • 单例模式
      • 工厂方法模式
      • 抽象工厂模式
      • 建造者模式
      • 原型模式
    • 结构型(7个):一些类或对象组合在一起的经典结构。
      • 代理模式
      • 装饰模式
      • 适配器模式
      • 组合模式
      • 享元模式
      • 外观模式
      • 桥接模式
    • 行为型(11个):解决类或对象之间的交互问题。
      • 策略模式
      • 模板方法模式
      • 责任链模式
      • 观察者模式
      • 迭代子模式
      • 命令模式
      • 备忘录模式
      • 状态模式
      • 访问者模式
      • 中介者模式
      • 解释器模式

Spring底层用了大量的工厂模式


工厂模式的三种形态

工厂模式通常有三种形态:

  • 第一种:简单工厂模式(Simple Factory):不属于23种设计模式之一。简单工厂模式又叫做:静态 工厂方法模式。简单工厂模式是工厂方法模式的一种特殊实现。
  • 第二种:工厂方法模式(Factory Method):是23种设计模式之一。
  • 第三种:抽象工厂模式(Abstract Factory):是23种设计模式之一。

简单工厂模式

简单工厂模式的角色包括三个:

  • 抽象产品 角色
  • 具体产品 角色
  • 工厂类 角色

案例:

抽象武器

public abstract class weapon {
    public abstract void attack();
}

具体武器的实现

public class tanke extends weapon{
    @Override
    public void attack() { System.out.println("坦克在攻击"); } }
public class feiji extends weapon{
    @Override
    public void attack() { System.out.println("飞机在攻击"); } }
public class paoche extends weapon{
    @Override
    public void attack() { System.out.println("跑车在攻击"); } }

工厂

public class factory {
    public static weapon get(String type){
        if ("tanke".equals(type)) {
            return new tanke();
        }else if ("feiji".equals(type)){
            return new feiji();
        }else if ("paoche".equals(type)){
            return new paoche();
        }else throw new RuntimeException("没有指定的类型");
    }
}

测试

public class demo {
    public static void main(String[] args) {
        //没用简单工厂模式,知道具体是怎么实现的
        weapon tanke = new tanke();
        tanke.attack();
        //用了简单工厂模式,不需要知道具体是怎么new出来的,直接用就完事了,做到了职能分工
        weapon feiji = factory.get("feiji");
        feiji.attack();
    }
}

简单工厂模式的优点:

  • 客户端程序不需要关心对象的创建细节,需要哪个对象时,只需要向工厂索要即可,初步实现了责任的分离。客户端只负责“消费”,工厂负责“生产”。生产和消费分离。

简单工厂模式的缺点:

  • 缺点1:工厂类集中了所有产品的创造逻辑,形成一个无所不知的全能类,有人把它叫做上帝类。显然工厂类非常关键,不能出问题,一旦出问题,整个系统瘫痪。
  • 缺点2:不符合OCP开闭原则,在进行系统扩展时,需要修改工厂类。

Spring中的BeanFactory就使用了简单工厂模式。


工厂模式

工厂方法模式既保留了简单工厂模式的优点,同时又解决了简单工厂模式的缺点。

工厂方法模式的角色包括:

  • 抽象工厂角色
  • 具体工厂角色
  • 抽象产品角色
  • 具体产品角色

案例

抽象产品

public abstract  class weapon {
    public abstract void attack();
}

具体产品

public class feiji extends weapon{
    @Override
    public void attack() {
        System.out.println("飞机在攻击");
    }
}
public class tanke extends weapon{
    @Override
    public void attack() {
        System.out.println("坦克在攻击");
    }
}

抽象工厂

public abstract class weaponFactory {
    public abstract weapon get();
}

具体工厂

public class feijiFactory extends weaponFactory{
    @Override
    public weapon get() {
        return new feiji();
    }
}
public class tankFactory extends weaponFactory{
    @Override
    public weapon get() {
        return new tanke();
    }
}

测试

public class demo {
    public static void main(String[] args) {
        weaponFactory feijiFactory = new feijiFactory();
        weapon feiji = feijiFactory.get();
        feiji.attack();

        weaponFactory tankeFactory = new tankFactory();
        weapon tanke = tankeFactory.get();
        tanke.attack();
    }
}

工厂方法模式的优点:

  • 一个调用者想创建一个对象,只要知道其名称就可以了。
  • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  • 屏蔽产品的具体实现,调用者只关心产品的接口。

工厂方法模式的缺点:

  • 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

抽象工厂模式

抽象工厂模式相对于工厂方法模式来说,就是工厂方法模式是针对一个产品系列的,而抽象工厂模式是针对多个产品系列的,即工厂方法模式是一个产品系列一个工厂类,而抽象工厂模式是多个产品系列一个工厂类。

抽象工厂模式特点:抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。它有多个抽象产品类,每个抽象产品类可以派生出多个具体产品类,一个抽象工厂类,可以派生出多个具体工厂类,每个具体工厂类可以创建多个具体产品类的实例。每一个模式都是针对一定问题的解决方案,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式针对的是多个产品等级结果。

抽象工厂中包含4个角色:

  • 抽象工厂角色
  • 具体工厂角色
  • 抽象产品角色
  • 具体产品角色

在这里插入图片描述

案例

抽象产品

public abstract class Weapon {
    public abstract void attack();
}
public abstract class Fruit {
    public abstract void ripeCycle();
}

具体产品

public class Gun extends Weapon{
    @Override
    public void attack() {
        System.out.println("开枪射击!");
    }
}
public class Dagger extends Weapon{
    @Override
    public void attack() {
        System.out.println("砍丫的!");
    }
}
public class Orange extends Fruit{
    @Override
    public void ripeCycle() {
        System.out.println("橘子的成熟周期是10个月");
    }
}
public class Apple extends Fruit{
    @Override
    public void ripeCycle() {
        System.out.println("苹果的成熟周期是8个月");
    }
}

抽象工厂

public abstract class AbstractFactory {
    public abstract Weapon getWeapon(String type);
    public abstract Fruit getFruit(String type);
}

具体工厂

public class WeaponFactory extends AbstractFactory{

    public Weapon getWeapon(String type){
        if (type == null || type.trim().length() == 0) {
            return null;
        }
        if ("Gun".equals(type)) {
            return new Gun();
        } else if ("Dagger".equals(type)) {
            return new Dagger();
        } else {
            throw new RuntimeException("无法生产该武器");
        }
    }
    @Override
    public Fruit getFruit(String type) {
        return null;
    }
}
public class FruitFactory extends AbstractFactory{
    @Override
    public Weapon getWeapon(String type) {
        return null;
    }

    public Fruit getFruit(String type){
        if (type == null || type.trim().length() == 0) {
            return null;
        }
        if ("Orange".equals(type)) {
            return new Orange();
        } else if ("Apple".equals(type)) {
            return new Apple();
        } else {
            throw new RuntimeException("我家果园不产这种水果");
        }
    }
}

测试

public class Client {
    public static void main(String[] args) {
        // 客户端调用方法时只面向AbstractFactory调用方法。
        AbstractFactory factory = new WeaponFactory(); // 注意:这里的new WeaponFactory()可以采用 简单工厂模式 进行隐藏。
        Weapon gun = factory.getWeapon("Gun");
        Weapon dagger = factory.getWeapon("Dagger");

        gun.attack();
        dagger.attack();

        AbstractFactory factory1 = new FruitFactory(); // 注意:这里的new FruitFactory()可以采用 简单工厂模式 进行隐藏。
        Fruit orange = factory1.getFruit("Orange");
        Fruit apple = factory1.getFruit("Apple");

        orange.ripeCycle();
        apple.ripeCycle();
    }
}

Bean的实例化方式

Spring为Bean提供了多种实例化方式,通常包括4种方式。(也就是说在Spring中为Bean对象的创建准备了多种方案,目的是:更加灵活)

  • 第一种:通过构造方法实例化
  • 第二种:通过简单工厂模式实例化
  • 第三种:通过factory-bean实例化(工厂模式)
  • 第四种:通过FactoryBean接口实例化

第一种:通过构造方法实例化

public class bean {
    public bean(){
        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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="bean" class="com.wzw.spring.bean"/>
</beans>

测试:

@Test
public void testBeanIns(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean.xml");
    bean bean = applicationContext.getBean("bean", bean.class);
    System.out.println(bean);
}
//无参构造方法执行了
//com.wzw.spring.bean@2d2e5f00

第二种:通过简单工厂模式实例化

public class StarSimpleFactory {
    public static Star get(){
        return new Star();
    }
}
public class Star {
    public Star(){
        System.out.println("star的无参构造方法执行了");
    }
}

factory-method:指定的是工厂类中的静态方法,也就是告诉spring用哪个方法创建对象

<bean id="bean2" class="com.wzw.spring.test2.StarSimpleFactory" factory-method="get"/>

测试

@Test
public void testBeanIns2(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean.xml");
    Star bean2 = applicationContext.getBean("bean2", Star.class);
    System.out.println(bean2);
}
//无参构造方法执行了
//star的无参构造方法执行了
//com.wzw.spring.test2.Star@2e005c4b

为什么 “无参构造方法执行了” 这句话会输出?

因为在执行spring.xml的时候就已经调用了所有对象的无参构造方法了,

即:Spring什么时候创建的对象?

  • 在执行spring.xml的时候就已经创建好了

第三种:通过factory-bean实例化

public class Tanke {
    public Tanke(){
        System.out.println("tanke的无参构造方法");
    }
}
public class TankeFactory {
    public Tanke get(){
        return new Tanke();
    }
}

factory-bean告诉spring根据哪个工厂类来创建对象

factory-method告诉spring根据工厂类里的哪个方法创建对象

<!--    factory-bean告诉spring根据哪个工厂类来创建对象,factory-method告诉spring根据工厂类里的哪个方法创建对象-->
    <bean id="tankeFactory" class="com.wzw.spring.test3.TankeFactory"/>
    <bean id="tanke" factory-bean="tankeFactory" factory-method="get"/>

测试

@Test
public void testBeanIns2(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean.xml");
    Star bean2 = applicationContext.getBean("bean2", Star.class);
    System.out.println(bean2);
}
//tanke的无参构造方法
//com.wzw.spring.test3.Tanke@4f6ee6e4

简单工厂跟工厂方法有啥区别?看着很像啊?

  1. 简单工厂方法里是static方法,不用new工厂直接调用方法去new bean对象
  2. 工厂方法里不是static方法,是实例方法,需要手动new工厂,通过实例去调用方法去new bean对象,所以工厂方法里是先实例化工厂,再通过工厂类实例化bean

第四种:通过FactoryBean接口实例化

public class Feiji {
    public Feiji(){
        System.out.println("飞机的方法执行了");
    }
}

工厂bean实现FactoryBean接口

public class FeijiFactory implements FactoryBean<Feiji> {
    @Override
    public Feiji getObject() throws Exception {
        return new Feiji();
    }
    @Override
    public Class<?> getObjectType() {
        return null;
    }
    /**
     * @Author wzw
     * @Description 这个方法在接口钟有默认实现,默认返回true,表示单例,如果想要多例,直接将这个方法修改为return false;
     * @Date 22:33 2022/11/10
     * @Param []
     * @Return boolean
     **/
    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }
}
<bean id="feiji" class="com.wzw.spring.test4.Feiji"/>

测试

@Test
public void testBeanIns4(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean.xml");
    Feiji bean4 = applicationContext.getBean("feiji", Feiji.class);
    System.out.println(bean4);
}
//飞机的方法执行了
//com.wzw.spring.test4.Feiji@4466af20

通过FactoryBean接口实例化 实际上是 通过factory-bean实例化的升级(优化)

  • 只要工厂类实现了FactoryBean接口,那就不用自己创建工厂类对象了,

  • 因为spring在实例化对象的时候发现:这个工厂类已经实现了FactoryBean接口,就知道这个是一个工厂类,就自己new了这个工厂类,

  • 通过factory-bean实例化少了一步创建工厂类对象:

    • <bean id="tankeFactory" class="com.wzw.spring.test3.TankeFactory"/>

BeanFactory和FactoryBean的区别

BeanFactory

BeanFactory是spring的超级父接口

在Spring的IoC容器中,“Bean工厂”负责创建Bean对象。BeanFactory是工厂。

  • BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-bean.xml");
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean.xml");
    

ClassPathXmlApplicationContext都是BeanFactory的子接口

FactoryBean

FactoryBean:它是一个Bean,也是一个接口,是一个能够辅助Spring实例化其它Bean对象的一个Bean。

简化xml配置,让工厂bean实现FactoryBean接口,可以参照Bean的实例化4

在Spring中,Bean可以分为两类:

  • 第一类:普通Bean
  • 第二类:工厂Bean(记住:工厂Bean也是一种Bean,只不过这种Bean比较特殊,它可以辅助Spring实例化其它Bean对象。)

小小的留个空

public class Clazz {
    private String cname;
    private List<Stu> stus;
    public void setCname(String cname) { this.cname = cname; }
    public void setStus(List<Stu> stus) { this.stus = stus; }
public class Stu {
    private String name;
    private int age;
    public Stu(String name, int age) {
        this.name = name;
        this.age = age;
    }
public class StuFactoryBean implements FactoryBean<Stu> {
    private String name;
    private int age;
    public StuFactoryBean(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public Stu getObject() throws Exception {
        return new Stu(name,age);
    }
    @Override
    public Class<?> getObjectType() {
        return null;
    }
}
<!--    自己试了一下,先创建工厂,在工厂里new Stu,再把Stu作为属性传进-->
    <bean id="stuFactoryBean" class="com.wzw.spring.demo.StuFactoryBean">
        <constructor-arg index="0" value="zs"/>
        <constructor-arg index="1" value="12"/>
    </bean>
    <bean id="clazz" class="com.wzw.spring.demo.Clazz">
        <property name="cname" value="高三一班"/>
        <property name="stus" ref="stuFactoryBean"/>
    </bean>

<!--    之前学的-->
    <bean id="stu" class="com.wzw.spring.demo.Stu">
        <constructor-arg index="0" value="ls"/>
        <constructor-arg index="1" value="20"/>
    </bean>
    <bean id="clazz2" class="com.wzw.spring.demo.Clazz">
        <property name="cname" value="高三二班"/>
        <property name="stus" ref="stu"/>
    </bean>

Bean的生命周期

什么是Bean的生命周期?

Spring其实就是一个管理Bean对象的工厂。它负责对象的创建,对象的销毁等。

所谓的生命周期就是:对象从创建开始到最终销毁的整个过程。

什么时候创建Bean对象?

创建Bean对象的前后会调用什么方法?

Bean对象什么时候销毁?

Bean对象的销毁前后调用什么方法?


为什么需要知道生命周期?

其实生命周期的本质是:在哪个时间节点上调用了哪个类的哪个方法。

我们需要充分的了解在这个生命线上,都有哪些特殊的时间节点。

只有我们知道了特殊的时间节点都在哪,到时我们才可以确定代码写到哪。

我们可能需要在某个特殊的时间点上执行一段特定的代码,这段代码就可以放到这个节点上。当生命线走到这里的时候,自然会被调用。


生命周期之五步

Bean生命周期可以粗略的划分为五大步:

  • 1.实例化Bean(调用无参数构造方法)
  • 2.初给Bean属性赋值(调用set方法)
  • 3.初始化Bean(调用Bean的init方法,这个init方法需要自己写自己配)
  • 4.使用Bean
  • 5.销毁Bean(调用Bean的destroy方法,这个destroy方法需要自己写自己配)
public class User {
    private String name;
    public User() {
        System.out.println("第一步:实例化bean");
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
    public void setName(String name) {
        System.out.println("第二步:给bean属性赋值");
        this.name = name;
    }
    public void initBean(){
        System.out.println("第三步:初始化bean");
    }
    public void destroyBean(){
        System.out.println("第五步:销毁bean");
    }
}

init-method指定初始化方法是哪个,在这个类中初始化方法是initBean() ,这个方法名是自己定的

destroy-method指定销毁方法是哪个,在这个类中初始化方法是destroyBean() ,这个方法名是自己定的

<bean id="user" class="com.wzw.spring6.bean.User" init-method="initBean" destroy-method="destroyBean">
    <property name="name" value="zs"/>
</bean>

测试

@Test
public void testUser(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    User user = applicationContext.getBean("user",User.class);
    System.out.println(user);
    //必须手动关闭Spring容器,这样spring容器蔡锷能销毁Bean
    ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
    context.close();
}
//第一步:实例化bean
//第二步:给bean属性赋值
//第三步:初始化bean
//第四步:使用bean:User{name='zs'}
//第五步:销毁bean

生命周期之七步

在以上的5步中,第3步是初始化Bean,如果还想在初始化前和初始化后添加代码,可以加入“Bean后处理器”。

  • 1.实例化Bean
  • 2.给Bean属性赋值
  • ​ 3.执行“Bean后处理器”的before方法
  • 4.初始化Bean
  • ​ 5.执行“Bean后处理器”的after方法
  • 6.使用Bean
  • 7.销毁Bean

编写一个类实现BeanPostProcessor类,并且重写before和after方法:

public class User7PostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean后处理器的before方法");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean后处理器的after方法");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

配置xml:这个Bean后处理器将作用于整个配置文件(当前的配置文件xml)的所有的Bean

<!--    配置Bean后处理器-->
<!--    这个Bean后处理器将作用于整个配置文件的所有的Bean-->
    <bean class="com.wzw.spring6.bean.User7PostProcessor"/>

其他不变,继续执行上面的代码:

@Test
public void testUser(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    User5 user = applicationContext.getBean("user", User5.class);
    System.out.println("第四步:使用bean:"+user);
    //必须手动关闭Spring容器,这样spring容器蔡锷能销毁Bean
    ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
    context.close();
}
//第一步:实例化bean
//第二步:给bean属性赋值
//Bean后处理器的before方法
//第三步:初始化bean
//Bean后处理器的after方法
//第四步:使用bean:User{name='zs'}
//第五步:销毁bean

说明加了Bean后处理器之后,之前的User类也执行了Bean后处理器,也说明了确实Bean后处理器是作用整个xml文件的


生命周期之十步

将七步分成更加细粒度的十步,那多出来的这三步在什么地方呢?

在这里插入图片描述

上图中检查Bean是否实现了Aware的相关接口是什么意思?

Aware相关的接口包括:BeanNameAwareBeanClassLoaderAwareBeanFactoryAware

  • 当Bean实现了BeanNameAware,Spring会将Bean的名字传递给Bean。
  • 当Bean实现了BeanClassLoaderAware,Spring会将加载该Bean的类加载器传递给Bean。
  • 当Bean实现了BeanFactoryAware,Spring会将Bean工厂对象传递给Bean。

一共5个接口:

  • Aware接口:

    • BeanNameAware

    • BeanClassLoaderAware

    • BeanFactoryAware

  • InitializingBean接口
  • DisposableBean接口

具体的十步:

  • 1.实例化Bean
  • 2.给Bean属性赋值
  • ​ 3.检查是否实现“Aware”相关接口,如果有就执行实现方法
  • ​ 4.执行“Bean后处理器”的before方法
  • ​ 5.检查是否实现“InitializingBean”相关接口,如果有就执行实现方法
  • 6.初始化Bean
  • ​ 7.执行“Bean后处理器”的after方法
  • 8.使用Bean
  • ​ 9.检查是否实现“DisposableBean”相关接口,如果有就执行实现方法
  • 10.销毁Bean
public class User10 implements BeanNameAware, BeanFactoryAware, BeanClassLoaderAware, InitializingBean, DisposableBean {
    private String name;

    public User10() {
        System.out.println("第一步:实例化bean");
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }

    public void setName(String name) {
        System.out.println("第二步:给bean属性赋值");
        this.name = name;
    }

    public void initBean(){
        System.out.println("第六步:初始化bean");
    }
    public void destroyBean(){
        System.out.println("第十步:销毁bean");
    }
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("第三步:类加载器:"+classLoader);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("第三步:bean工厂:"+beanFactory);
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("第三步:bean的名字:"+s);
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("第九步:DisposableBean的destroy方法");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("第五步:InitializingBean的afterPropertiesSet方法");
    }
}
public class User7PostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第四步:Bean后处理器的before方法");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第七步:Bean后处理器的after方法");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

测试

@Test
public void testUser10(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    User10 user = applicationContext.getBean("user10", User10.class);
    System.out.println("第八步:使用bean:"+user);
    //必须手动关闭Spring容器,这样spring容器蔡锷能销毁Bean
    ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
    context.close();
}

第一步:实例化bean
第二步:给bean属性赋值
第三步:bean的名字:user10
第三步:类加载器:jdk.internal.loader.ClassLoaders$AppClassLoader@63947c6b
第三步:bean工厂:org.springframework.beans.factory.support.DefaultListableBeanFactory@1e730495: defining beans [com.wzw.spring6.bean.User7PostProcessor#0,user10]; root of factory hierarchy
第四步:Bean后处理器的before方法
第五步:InitializingBean的afterPropertiesSet方法
第六步:初始化bean
第七步:Bean后处理器的after方法
第八步:使用bean:User{name=‘ls’}
第九步:DisposableBean的destroy方法
第十步:销毁bean


注意!

Spring容器只对singleton的Bean进行完整的生命周期管理

如果是prototype作用域的Bean,Spring只负责将该Bean初始化完毕,等客户端程序一旦获取到该Bean后,Spring容器就不再管理该对象的生命周期了

例如:将user10的scope改成prototype

<bean id="user10" class="com.wzw.spring6.bean.User10" init-method="initBean" destroy-method="destroyBean" scope="prototype">
    <property name="name" value="ls"/>
</bean>

相同的测试:

第一步:实例化bean
第二步:给bean属性赋值
第三步:bean的名字:user10
第三步:类加载器:jdk.internal.loader.ClassLoaders$AppClassLoader@63947c6b
第三步:bean工厂:org.springframework.beans.factory.support.DefaultListableBeanFactory@1e730495: defining beans [com.wzw.spring6.bean.User7PostProcessor#0,user10]; root of factory hierarchy
第四步:Bean后处理器的before方法
第五步:InitializingBean的afterPropertiesSet方法
第六步:初始化bean
第七步:Bean后处理器的after方法
第八步:使用bean:User{name='ls'}

只执行到使用bean,后面就不管了


自己new的对象如何让Spring管理

有些时候可能会遇到这样的需求,某个java对象是我们自己new的,然后我们希望这个对象被Spring容器管理,怎么实现?

@Test
    public void testBeanRegister(){
        // 自己new的对象
        User user = new User();
        System.out.println(user);

        // 创建 默认可列表BeanFactory 对象
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        // 注册Bean
        factory.registerSingleton("userBean", user);
        // 从spring容器中获取bean
        User userBean = factory.getBean("userBean", User.class);
        System.out.println(userBean);
    }

Bean的循环依赖问题

什么是循环依赖?

A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。

比如:丈夫类Husband,妻子类Wife。Husband中有Wife的引用。Wife中有Husband的引用。

那创建的时候会不会有什么问题呢?比如死循环?

在这里插入图片描述

public class Husband {
    private String name;
    private Wife wife;
public class Wife {
    private String name;
    private Husband husband;

set+singleton

set注入+singleton单例模式:没有循环的问题

  • singleton表示在整个Spring容器当中是单例的,独一无二的,只有一个咋循环

为什么在set+singleton这种模式下循环依赖没有出现问题?

因为在这种模式下Spring对Bean的管理主要分为两个清晰的阶段

  • 第一个阶段:

    • 在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行"曝光"(不等属性赋值就曝光),意思就是

      Spring.xml文件加载的时候,这个bean就已经创建出来了,但是这个时候属性还没有赋上值,这个时候就采取了"曝光",

      提前把这个bean的地址曝光出来了,其他bean可以引用了(ref),为什么敢这么做?因为是单例模式,只有一个该bean,属性可以后面赋值

  • 第二个阶段:

    • Bean"曝光"之后,再进行属性的赋值

核心解决方案是:实例化对象和对象的赋值分为两个阶段来完成

只有在scope是singleton的情况下,Bean才会采取提前曝光的措施

一般来说面试会问:IoC三级缓存跟循环依赖的关系

案例:


<bean id="husband" class="com.wzw.spring6.bean.Husband">
    <property name="name" value="zs"/>
    <property name="wife" ref="wife"/>
</bean>

<bean id="wife" class="com.wzw.spring6.bean.Wife">
    <property name="name" value="ls"/>
    <property name="Husband" ref="husband"/>
</bean>

测试:

@Test
public void testCD(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    Husband husband = applicationContext.getBean("husband", Husband.class);
    Wife wife = applicationContext.getBean("wife", Wife.class);
    System.out.println(husband);
    System.out.println(wife);
}
//Husband{name='zs', wife=ls}
//Wife{name='ls', husband=zs}

结果:没有产生循环,只创建了两个对象,且正常赋值


set+prototype

当创建的bean的scope全是prototype就会发生错误BeanCreationException

案例:

<bean id="husband2" class="com.wzw.spring6.bean.Husband" scope="prototype">
    <property name="name" value="zs"/>
    <property name="wife" ref="wife2"/>
</bean>

<bean id="wife2" class="com.wzw.spring6.bean.Wife" scope="prototype">
    <property name="name" value="ls"/>
    <property name="Husband" ref="husband2"/>
</bean>

测试:

@Test
public void testCD2(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    Husband husband = applicationContext.getBean("husband2", Husband.class);
    Wife wife = applicationContext.getBean("wife2", Wife.class);
    System.out.println(husband);
    System.out.println(wife);
}

结果:报错:nested exception is org.springframework.beans.factory.BeanCreationException

创建对象的时候发生问题,产生了循环

结果办法:只要创建的bean,不全是prototype即可,要有一个是singleton,其实还是用了曝光的办法,解决了循环问题

<bean id="husband2" class="com.wzw.spring6.bean.Husband" scope="prototype">
    <property name="name" value="zs2"/>
    <property name="wife" ref="wife2"/>
</bean>

<bean id="wife2" class="com.wzw.spring6.bean.Wife" scope="singleton">
    <property name="name" value="ls2"/>
    <property name="Husband" ref="husband2"/>
</bean>
@Test
public void testCD2(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    Husband husband = applicationContext.getBean("husband2", Husband.class);
    Wife wife = applicationContext.getBean("wife2", Wife.class);
    System.out.println(husband);
    System.out.println(wife);
}

结果:

Husband{name=‘zs2’, wife=ls2}
Wife{name=‘ls2’, husband=zs2}

构造注入是不能解决循环依赖的

Spring三级缓存

  • earlySingletonObjects 一级缓存

    • 一级缓存存储的是:完整的单例Bean(完整说明这个缓存中的Bean对象中的属性已经赋值了,是一个完整的Bean对象)
  • singletonObjects 二级缓存

    • 二级缓存存储的是:早期的单例Bean对象,这个缓存中的单例Bean对象的属性没有赋值,只是有一个地址
  • singletonFactories 三级缓存

    • 三级缓存存储的是:单例工厂对象,这个里面存储了大量的工厂对象,每一个单例Bean对象都会对应一个单例工厂对象
    • 这个集合中存储的是:创建该单例对象时,对应的那个单例工厂对象
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	private final Map<String, Object> earlySingletonObjects 
    private final Map<String, Object> singletonObjects 
	private final Map<String, ObjectFactory<?>> singletonFactories 
	

Spring是怎么处理循环依赖的?

Spring为什么可以解决set + singleton模式下循环依赖?

根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。

实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。

给Bean属性赋值的时候:调用setter方法来完成。

两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。

也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。

具体是怎么曝光的?曝光是一个什么样的机制?

曝光底层分析

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
      implements AutowireCapableBeanFactory {
    protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
        Object bean = instanceWrapper.getWrappedInstance();//创建Bean对象
        //添加工厂,曝光bean工厂
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        populateBean(beanName, mbd, instanceWrapper);//给Bean赋值
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(singletonFactory, "Singleton factory must not be null");
   synchronized (this.singletonObjects) {
      if (!this.singletonObjects.containsKey(beanName)) {
          //把这个bean的单例工厂放到三级缓存里
          //曝光该bean的工厂对象
         this.singletonFactories.put(beanName, singletonFactory);
          
         this.earlySingletonObjects.remove(beanName);
         this.registeredSingletons.add(beanName);
      }
   }
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // Quick check for existing instance without full singleton lock
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
         synchronized (this.singletonObjects) {
            // Consistent creation of early reference within full singleton lock
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               singletonObject = this.earlySingletonObjects.get(beanName);
               if (singletonObject == null) {
                  ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                  if (singletonFactory != null) {
                      //从三级缓存中获取bean
                     singletonObject = singletonFactory.getObject();
                      //获取的bean放到一级缓存
                     this.earlySingletonObjects.put(beanName, singletonObject);
                      //清除三级缓存
                     this.singletonFactories.remove(beanName);
                  }
               }
            }
         }
      }
   }
   return singletonObject;
}

从源码中可以看到,

spring会先从一级缓存中获取Bean,如果获取不到,则从二级缓存中获取Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题。


SPring Ioc注解开发

通过反射机制获取注解,并从注解中获取bean

大概的实现原理通过一个案例来看:

案例需求

自定义一个注解,给定一个包,包下类上有注解就创建对象,没有就不创建

自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Compoent {
    String value();
}

@Compoent("userBean")
public class User {
}
@Compoent("vipBean")
public class vip {
}

实现

public class demo {
    static Map<String,Object>  beanMap = new HashMap<>();
    public static void main(String[] args) throws ClassNotFoundException {
        String packageName = "com.wzw.spring6.bean";
        String packagePath = packageName.replaceAll("\\.", "/");
//        System.out.println(packagePath);
        URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
//        System.out.println(url);
        String path = url.getPath();
        File file = new File(path);
        File[] files = file.listFiles();
        Arrays.asList(files).forEach(f ->{
//                System.out.println(f.getName())
            try {
                String className = packageName + "." +f.getName().split("\\.")[0];
                Class<?> aClass = Class.forName(className);
                if (aClass.isAnnotationPresent(Compoent.class)) {
                    Compoent compoent = aClass.getAnnotation(Compoent.class);
                    String id = compoent.value();
                    Object o = aClass.newInstance();
                    beanMap.put(id,o);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        System.out.println(beanMap);
    }
}

结果:

{userBean=com.wzw.spring6.bean.User@5010be6}

大概的模拟了spring底层注解开发的原理:

  • 通过注解获取bean的id,创建bean

声明Bean的注解

负责声明Bean的注解,常见的包括四个:

  • @Component
  • @Controller 建议用在表示层(web)上
  • @Service 建议用在业务层(sevice)上
  • @Repository 建议用在持久层(dao)上

Spring注解的使用

如何使用以上的注解呢?

  • 第一步:加入aop的依赖
  • 第二步:在配置文件中添加context命名空间
  • 第三步:在配置文件中指定扫描的包
  • 第四步:在Bean类上使用注解

当Component注解没有写value值的时候,会有默认值,默认值是类名简写且首字母小写:

  • public class Student 默认:student

如果存在多个包下的bean,如何解决?

有两种解决方案

  • 多个包,可以指定这多个包共同的父包
  • 在xml中指定的时候用“,”隔开
    • <context:component-scan base-package="com.wzw.spring.bean,com.wzw.spring.bean2"/>

案例

@Component
public class Student {
}
@Component("userBean")
public class User {
}
@Component("vipBean")
public class Vip {
}

xml中使用context命名空间,给spring框架指定要扫描那些包里的类

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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"
>
<!--    给spring框架指定要扫描那些包里的类-->
        <context:component-scan base-package="com.wzw.spring.bean"/>
</beans>

测试

@Test
public void testBeanComponent(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    User userBean = applicationContext.getBean("userBean", User.class);
    Vip vipBean = applicationContext.getBean("vipBean", Vip.class);
    Student studentBean = applicationContext.getBean("student", Student.class);
    System.out.println(userBean);
    System.out.println(vipBean);
    System.out.println(studentBean);
}
//com.wzw.spring.bean.User@59af0466
//com.wzw.spring.bean.Vip@3e6ef8ad
//com.wzw.spring.bean.Student@346d61be


选择性实例化Bean

假设在某个包下有很多Bean,有的Bean上标注了Component,有的标注了Controller,有的标注了Service,有的标注了Repository,现在由

于某种特殊业务的需要,只允许其中所有的Controller参与Bean管理,其他的都不实例化。这应该怎么办呢?

有两种解决方案:

  • use-default-filters = false 搭配context:include-filter使用,

    • use-default-filters = false表示这个包下所有带有声明bean的注解全部失效

    • context:include-filter 表示这个注解生效

    •     <context:component-scan base-package="com.wzw.spring.bean2" use-default-filters="false">
      <!--        只有service生效-->
              <context:include-filter type="annotation"expression="org.springframework.stereotype.Service"/>
          </context:component-scan>
      
  • use-default-filters =true 搭配context:exclude-filter使用

    • use-default-filters = true表示这个包下所有带有声明bean的注解全部生效,默认就是true

    • context:exclude-filter 表示这个注解失效

    •     <context:component-scan base-package="com.wzw.spring.bean2">
      <!--        service不生效,其他都生效-->
              <context:exclude-filter type="annotation"expression="org.springframework.stereotype.Service"/>
          </context:component-scan>
      

案例

@Component
public class A {
    public A(){
        System.out.println("component生效");
    }
}
@Service
class B{
    public B(){
        System.out.println("service生效");
    }
}
@Controller
class C{
    public C(){
        System.out.println("controller生效");
    }
}

测试1

<!--    use-default-filters = true表示这个包下所有带有声明bean的注解全部生效,默认就是true-->
    <context:component-scan base-package="com.wzw.spring.bean2">
<!--        service不生效,其他都生效-->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>
@Test
public void testBeanComponent2(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
}
//component生效
//controller生效

测试2

    <context:component-scan base-package="com.wzw.spring.bean2" use-default-filters="false">
<!--        只有service生效-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>
@Test
public void testBeanComponent2(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
}
/service生效

负责注入的注解

@Component @Controller @Service @Repository 这四个注解是用来声明Bean的,声明后这些Bean将被实例化。

实例化之后如何给Bean的属性赋值。给Bean属性赋值需要用到这些注解:

  • @Value 专门注入简单类型
  • @Autowired
  • @Qualifier
  • @Resource

@Autowired + @Qualifie

  • @Autowired注解可以标注在哪里?

    • 构造方法上
    • 方法上
    • 形参上
    • 属性上
    • 注解上
案例:
public interface OrderDao {
    void insert();
}
public interface Service {
    void generate();
}
@Repository
public class OrderDaoForMysqlImpl implements OrderDao{
    @Override
    public void insert() {
        System.out.println("mysql数据库正在保存数据");
    }
}
@Repository
public class OrderDaoForOracleImpl implements OrderDao{
    @Override
    public void insert() {
        System.out.println("oracle数据库正在保存数据");
    }
}
@org.springframework.stereotype.Service
public class ServiceImpl implements Service{
    @Autowired
    @Qualifier("orderDaoForMysqlImpl")
    private OrderDao orderDao;

//    public ServiceImpl(OrderDao orderDao) {
//        this.orderDao = orderDao;
//    }

    @Override
    public void generate() {
        orderDao.insert();
    }
}
  • 如果单独使用@Autowired 就默认自动按类型装配,适用于只有一个实现类

  • 如果使用@Autowired@Qualifier ,就可以自动按名字装配,适用于有多个实现类,可以认为更改需要实现的类

    • 	@Autowired
          @Qualifier("orderDaoForMysqlImpl")
          private OrderDao orderDao;
      
  • 有时候可以省略Autowired@Qualifier ,比如:只有一个构造方法,且有需要注入的那个属性,可以省略

    • private OrderDao orderDao;
      public ServiceImpl(OrderDao orderDao) {
              this.orderDao = orderDao;
      }
      

@Resouce

@Resource注解也可以完成非简单类型注入。那它和@Autowired注解有什么区别?

  • @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
  • @Autowired注解是Spring框架自己的。
  • @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
  • @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
  • @Resource注解用在属性上、setter方法上。
  • @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。

spring6使用:

<dependency>
  <groupId>jakarta.annotation</groupId>
  <artifactId>jakarta.annotation-api</artifactId>
  <version>2.1.1</version>
</dependency>

spring5-使用:

<dependency>
  <groupId>javax.annotation</groupId>
  <artifactId>javax.annotation-api</artifactId>
  <version>1.3.2</version>
</dependency>
案例
public interface Service {
    void generate();
}
public interface StudentDao {
    void insert();
}
@org.springframework.stereotype.Service
public class ServiceImpl implements Service{
    @Resource(name = "studentDaoForMysqlImpl")
    private StudentDao studentDao;
    @Override
    public void generate() {
        studentDao.insert();
    }
}
@Repository
public class StudentDaoForMysqlImpl implements StudentDao{
    @Override
    public void insert() {
        System.out.println("mysql正在新增student");
    }
}
@Repository
public class StudentDaoForOracleImpl implements StudentDao{
    @Override
    public void insert() {
        System.out.println("oracle正在新增student");
    }
}

全注解开发

所谓的全注解开发就是不再使用spring配置文件了。写一个配置类来代替配置文件。

@Configuration
@ComponentScan({"com.wzw.spring.bean5_resource"})
public class Spring6Config {
}

接口

public interface StudentDao {
    void insert();
}
public interface Service {
    void generate();
}

实现类

@Repository
public class StudentDaoForMysqlImpl implements StudentDao{
    @Override
    public void insert() {
        System.out.println("mysql正在新增student");
    }
}
@Repository
public class StudentDaoForOracleImpl implements StudentDao{
    @Override
    public void insert() {
        System.out.println("oracle正在新增student");
    }
}
@org.springframework.stereotype.Service
public class ServiceImpl implements Service{
    @Resource(name = "studentDaoForMysqlImpl")
    private StudentDao studentDao;
    @Override
    public void generate() {
        studentDao.insert();
    }
}

测试

@Test
public void testAuto2Not(){
    AnnotationConfigApplicationContext Context = new AnnotationConfigApplicationContext(Spring6Config.class);
    com.wzw.spring.bean5_resource.ServiceImpl  serviceImpl = Context.getBean("serviceImpl", com.wzw.spring.bean5_resource.ServiceImpl .class);
    serviceImpl.generate();
}
//mysql正在新增student

JdbcTemplate

JdbcTemplate是Spring提供的一个JDBC模板类,是对JDBC的封装,简化JDBC代码。

当然,你也可以不用,可以让Spring集成其它的ORM框架,例如:MyBatis、Hibernate等。

接下来我们简单来学习一下,使用JdbcTemplate完成增删改查。

本大章节笔记均采用动力节点老杜笔记

环境准备

数据库表:t_user

img

IDEA中新建模块:spring6-007-jdbc

img

引入相关依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.powernode</groupId>
    <artifactId>spring6-007-jdbc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <repositories>
        <repository>
            <id>repository.spring.milestone</id>
            <name>Spring Milestone Repository</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--新增的依赖:mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <!--新增的依赖:spring jdbc,这个依赖中有JdbcTemplate-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

</project>

准备实体类:表t_user对应的实体类User。

package com.powernode.spring6.bean;

/**
 * @author 动力节点
 * @version 1.0
 * @className User
 * @since 1.0
 **/
public class User {
    private Integer id;
    private String realName;
    private Integer age;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", realName='" + realName + '\'' +
                ", age=" + age +
                '}';
    }

    public User() {
    }

    public User(Integer id, String realName, Integer age) {
        this.id = id;
        this.realName = realName;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getRealName() {
        return realName;
    }

    public void setRealName(String realName) {
        this.realName = realName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

编写Spring配置文件:

JdbcTemplate是Spring提供好的类,这类的完整类名是:org.springframework.jdbc.core.JdbcTemplate

我们怎么使用这个类呢?new对象就可以了。怎么new对象,Spring最在行了。直接将这个类配置到Spring配置文件中,纳入Bean管理即可。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"></bean>
</beans>

我们来看一下这个JdbcTemplate源码:

img

img

可以看到JdbcTemplate中有一个DataSource属性,这个属性是数据源,我们都知道连接数据库需要Connection对象,而生成Connection对象是数据源负责的。所以我们需要给JdbcTemplate设置数据源属性。

所有的数据源都是要实现javax.sql.DataSource接口的。这个数据源可以自己写一个,也可以用写好的,比如:阿里巴巴的德鲁伊连接池,c3p0,dbcp等。我们这里自己先手写一个数据源。

package com.powernode.spring6.jdbc;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

/**
 * @author 动力节点
 * @version 1.0
 * @className MyDataSource
 * @since 1.0
 **/
public class MyDataSource implements DataSource {
    // 添加4个属性
    private String driver;
    private String url;
    private String username;
    private String password;

    // 提供4个setter方法
    public void setDriver(String driver) {
        this.driver = driver;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    // 重点写怎么获取Connection对象就行。其他方法不用管。
    @Override
    public Connection getConnection() throws SQLException {
        try {
            Class.forName(driver);
            Connection conn = DriverManager.getConnection(url, username, password);
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}

写完数据源,我们需要把这个数据源传递给JdbcTemplate。因为JdbcTemplate中有一个DataSource属性:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="com.powernode.spring6.jdbc.MyDataSource">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="myDataSource"/>
    </bean>
</beans>

到这里环境就准备好了。


新增

编写测试程序:

package com.powernode.spring6.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * @author 动力节点
 * @version 1.0
 * @className JdbcTest
 * @since 1.0
 **/
public class JdbcTest {
    @Test
    public void testInsert(){
        // 获取JdbcTemplate对象
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
        // 执行插入操作
        // 注意:insert delete update的sql语句,都是执行update方法。
        String sql = "insert into t_user(id,real_name,age) values(?,?,?)";
        int count = jdbcTemplate.update(sql, null, "张三", 30);
        System.out.println("插入的记录条数:" + count);
    }
}

update方法有两个参数:

  • 第一个参数:要执行的SQL语句。(SQL语句中可能会有占位符 ? )
  • 第二个参数:可变长参数,参数的个数可以是0个,也可以是多个。一般是SQL语句中有几个问号,则对应几个参数。

修改

@Test
public void testUpdate(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行更新操作
    String sql = "update t_user set real_name = ?, age = ? where id = ?";
    int count = jdbcTemplate.update(sql, "张三丰", 55, 1);
    System.out.println("更新的记录条数:" + count);
}

执行结果:

img


删除

@Test
public void testDelete(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行delete
    String sql = "delete from t_user where id = ?";
    int count = jdbcTemplate.update(sql, 1);
    System.out.println("删除了几条记录:" + count);
}

执行结果:

img


查询一个对象

@Test
public void testSelectOne(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行select
    String sql = "select id, real_name, age from t_user where id = ?";
    User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 2);
    System.out.println(user);
}

执行结果:

img

queryForObject方法三个参数:

  • 第一个参数:sql语句
  • 第二个参数:Bean属性值和数据库记录行的映射对象。在构造方法中指定映射的对象类型。
  • 第三个参数:可变长参数,给sql语句的占位符问号传值。

查询多个对象

@Test
public void testSelectAll(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行select
    String sql = "select id, real_name, age from t_user";
    List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
    System.out.println(users);
}

执行结果:

img


查询一个值

@Test
public void testSelectOneValue(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 执行select
    String sql = "select count(1) from t_user";
    Integer count = jdbcTemplate.queryForObject(sql, int.class); // 这里用Integer.class也可以
    System.out.println("总记录条数:" + count);
}

执行结果:

img


批量添加

@Test
public void testAddBatch(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 批量添加
    String sql = "insert into t_user(id,real_name,age) values(?,?,?)";

    Object[] objs1 = {null, "小花", 20};
    Object[] objs2 = {null, "小明", 21};
    Object[] objs3 = {null, "小刚", 22};
    List<Object[]> list = new ArrayList<>();
    list.add(objs1);
    list.add(objs2);
    list.add(objs3);

    int[] count = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(Arrays.toString(count));
}

执行结果:

img


批量修改

@Test
public void testUpdateBatch(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 批量修改
    String sql = "update t_user set real_name = ?, age = ? where id = ?";
    Object[] objs1 = {"小花11", 10, 2};
    Object[] objs2 = {"小明22", 12, 3};
    Object[] objs3 = {"小刚33", 9, 4};
    List<Object[]> list = new ArrayList<>();
    list.add(objs1);
    list.add(objs2);
    list.add(objs3);

    int[] count = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(Arrays.toString(count));
}

执行结果:

img


批量删除

@Test
public void testDeleteBatch(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    // 批量删除
    String sql = "delete from t_user where id = ?";
    Object[] objs1 = {2};
    Object[] objs2 = {3};
    Object[] objs3 = {4};
    List<Object[]> list = new ArrayList<>();
    list.add(objs1);
    list.add(objs2);
    list.add(objs3);
    int[] count = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(Arrays.toString(count));
}

执行结果:

img


使用回调函数

使用回调函数,可以参与的更加细节:

@Test
public void testCallback(){
    // 获取JdbcTemplate对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
    String sql = "select id, real_name, age from t_user where id = ?";

    User user = jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
        @Override
        public User doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
            User user = null;
            ps.setInt(1, 5);
            ResultSet rs = ps.executeQuery();
            if (rs.next()) {
                user = new User();
                user.setId(rs.getInt("id"));
                user.setRealName(rs.getString("real_name"));
                user.setAge(rs.getInt("age"));
            }
            return user;
        }
    });
    System.out.println(user);
}

执行结果:

img


使用德鲁伊连接池

之前数据源是用我们自己写的。也可以使用别人写好的。例如比较牛的德鲁伊连接池。

第一步:引入德鲁伊连接池的依赖。(毕竟是别人写的)

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.8</version>
</dependency>

第二步:将德鲁伊中的数据源配置到spring配置文件中。和配置我们自己写的一样。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>
</beans>

测试结果:

image.png


GoF之代理模式

对代理模式的理解

生活场景1:牛村的牛二看上了隔壁村小花,牛二不好意思直接找小花,于是牛二找来了媒婆王妈妈。这里面就有一个非常典型的代理模式。牛二不能和小花直接对接,只能找一个中间人。其中王妈妈是代理类,牛二是目标类。王妈妈代替牛二和小花先见个面。(现实生活中的婚介所)【在程序中,对象A和对象B无法直接交互时。】

生活场景2:你刚到北京,要租房子,可以自己找,也可以找链家帮你找。其中链家是代理类,你是目标类。你们两个都有共同的行为:找房子。不过链家除了满足你找房子,另外会收取一些费用的。(现实生活中的房产中介)【在程序中,功能需要增强时。】

西游记场景:八戒和高小姐的故事。八戒要强抢民女高翠兰。悟空得知此事之后怎么做的?悟空幻化成高小姐的模样。代替高小姐与八戒会面。其中八戒是客户端程序。悟空是代理类。高小姐是目标类。那天夜里,在八戒眼里,眼前的就是高小姐,对于八戒来说,他是不知道眼前的高小姐是悟空幻化的,在他内心里这就是高小姐。所以悟空代替高小姐和八戒亲了嘴儿。这是非常典型的代理模式实现的保护机制。代理模式中有一个非常重要的特点:对于客户端程序来说,使用代理对象时就像在使用目标对象一样。****【在程序中,目标需要被保护时】

业务场景:系统中有A、B、C三个模块,使用这些模块的前提是需要用户登录,也就是说在A模块中要编写判断登录的代码,B模块中也要编写,C模块中还要编写,这些判断登录的代码反复出现,显然代码没有得到复用,可以为A、B、C三个模块提供一个代理,在代理当中写一次登录判断即可。代理的逻辑是:请求来了之后,判断用户是否登录了,如果已经登录了,则执行对应的目标,如果没有登录则跳转到登录页面。【在程序中,目标不但受到保护,并且代码也得到了复用。】

代理模式是GoF23种设计模式之一。属于结构型设计模式。

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。

代理模式中的角色:

  • 代理类(代理主题)
  • 目标类(真实主题)
  • 代理类和目标类的公共接口(抽象主题):客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口。

代理模式的类图:

img

代理模式在代码实现上,包括两种形式:

  • 静态代理
  • 动态代理

静态代理

公共接口

public interface OrderService {
    void generate();
    void modify();
    void detail();
}

目标对象

public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("订单已生成");
    }

    @Override
    public void modify() {
        try {
            Thread.sleep(234);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("订单已修改");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(124);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("订单显示详情");
    }
}

其中Thread.sleep()方法的调用是为了模拟操作耗时。

项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化。于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。

代理对象

public class OrderServiceProxy implements OrderService{
    private OrderService target;

    public OrderServiceProxy(OrderService target) {
        this.target = target;
    }

    @Override
    public void generate() {
        Long begin = System.currentTimeMillis();
        target.generate();
        Long end = System.currentTimeMillis();
        System.out.println(end-begin);
    }

    @Override
    public void modify() {
        Long begin = System.currentTimeMillis();
        target.modify();
        Long end = System.currentTimeMillis();
        System.out.println(end-begin);
    }

    @Override
    public void detail() {
        Long begin = System.currentTimeMillis();
        target.detail();
        Long end = System.currentTimeMillis();
        System.out.println(end-begin);
    }
}

测试:

public static void main(String[] args) {
    OrderService orderService =new OrderServiceImpl();
    OrderService proxy = new OrderServiceProxy(orderService);
    proxy.generate();
    proxy.detail();
    proxy.modify();
}

优点:

  • 符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。所以这种方案是被推荐的。

缺点:

  • 如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。

动态代理

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。

在内存当中动态生成类的技术常见的包括:

  • JDK动态代理技术:只能代理接口。
  • CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
  • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

JDK动态代理

案例

公共接口

public interface OrderService {
    String getName();
    void generate();
    void modify();
    void detail();
}

目标类

public class OrderServiceImpl implements OrderService {
    @Override
    public String getName() {
        return "张三";
    }

    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("订单已生成");
    }

    @Override
    public void modify() {
        try {
            Thread.sleep(234);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("订单已修改");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(124);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("订单显示详情");
    }
}

调用处理器 InvocationHandler

public class TimeInvocationHandle implements InvocationHandler {
    private Object target;

    public TimeInvocationHandle(Object target) {
        this.target = target;
    }

    /*
     * @Author wzw
     * @Description
     * 为什么强行要求必须实现InvocationHandler接口?
     *      因为一个类实现接口就必须实现接口中的方法
     *      一下这个方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了
     *      ps:invoke方法不是我们调用的,是写给JDK调用的
     * invoke方法什么时候被调用呢?
     *      当代理对象调用代理方法的时候,注册在InvocationHandle调用处理器当中的invoke()方法被调用
     * @Date 20:30 2022/11/14
     * @Param [java.lang.Object, java.lang.reflect.Method, java.lang.Object[]]
     * proxy代理对象的引用,method目标对象上的目标方法(要执行的目标方法),args目标方法上的实参
     * @Return java.lang.Object
     **/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Long begin = System.currentTimeMillis();
        Object o = method.invoke(target, args);
        Long end = System.currentTimeMillis();
        System.out.println(end-begin);
        return o;
    }
}

测试

public class Test {
    public static void main(String[] args) {

        //创建目标对象
        OrderService orderService = new OrderServiceImpl();
        //创建代理对象
//        Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器)
//        参数1:ClassLoader loder 类加载器
//                在内存当中生成的字节码也是class文件,要执行也得先加载到内存中,加载类就需要类加载器,所以这里要指定类加载器
//                    并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个
//        参数2:Class<?> interfaces
//                    代理类和目标类要实现同一个接口或同一些接口
//        参数3:InvocationHandle h 调用处理器,也是一个接口
//                    在调用处理器接口中编写的是:增强代码
        OrderService proxyInstance = (OrderService)Proxy.newProxyInstance(orderService.getClass().getClassLoader(), orderService.getClass().getInterfaces(), new TimeInvocationHandle(orderService));
        //调用代理对象的方法
        proxyInstance.generate();
        proxyInstance.detail();
        System.out.println(proxyInstance.getName());
    }
}
订单已生成
1236
订单显示详情
125
0
张三

Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器)

  • 参数1:ClassLoader loder 类加载器
  • 在内存当中生成的字节码也是class文件,要执行也得先加载到内存中,加载类就需要类加载器,所以这里要指定类加载器
  • 参数2:Class<?> interfaces 公共接口
    • 代理类和目标类要实现同一个接口或同一些接口
  • 参数3:InvocationHandle h 调用处理器,也是一个接口
  • 在调用处理器接口中编写的是:增强代码

InvocationHandle中的invoke方法

  • Object proxy 代理对象的引用

  • Method method 目标对象上的目标方法(要执行的目标方法)

  • Object[] args 目标方法上的实参

  • 有返回值,一般来说将目标方法的返回值作为返回值

  • 一般来说通过反射机制调用方法需要知道:目标对象,目标方法,方法参数,返回值

    • invoke 方法提供了目标方法method,目标参数args,返回值会有的

    • 如何获取目标对象?

    • 可以通过调用InvocationHandle的实现类的时候将目标对象传进来,且实现类接收这个目标对象

      //创建目标对象
              OrderService orderService = new OrderServiceImpl();
      //创建代理对象
    OrderService proxyInstance = (OrderService)Proxy.newProxyInstance(
      orderService.getClass().getClassLoader(), 
      orderService.getClass().getInterfaces(), 
      new TimeInvocationHandle(orderService));
    
    public class TimeInvocationHandle implements InvocationHandler {
        private Object target;
        public TimeInvocationHandle(Object target) {
            this.target = target;
        }
         @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object o = method.invoke(target, args);
            return o;
        }
    

为什么强行要求必须实现InvocationHandler接口?

  • 因为一个类实现接口就必须实现接口中的方法

    这个方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了

注意:invoke方法不是我们调用的,是写给JDK调用的

invoke方法什么时候被调用呢?

  • 当代理对象调用代理方法的时候,注册在InvocationHandle调用处理器当中的invoke()方法被调用

关于InvocationHandle中的invoke方法返回值的问题

如果目标对象的目标方法有返回值,且返回值需要用到,就需要把返回值继续返回,即invoke返回值就是目标方法的返回值

public String getName() {
        return "张三";
    }

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Long begin = System.currentTimeMillis();
        Object o = method.invoke(target, args);
        Long end = System.currentTimeMillis();
        System.out.println(end-begin);
        return o;
    }

JDK的动态代理跟CGLIB的动态代理有什么区别?

  1. JDK动态代理采用的是接口的方式,CGLIB采用的是继承的方式
  2. JDK只能代理接口,CGLIB可以代理接口跟类
  3. CGLIB功能更强大,效率更高

CGLIB动态代理

CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。

使用CGLIB,需要引入它的依赖:

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

我们准备一个没有实现接口的类,如下:

package com.powernode.mall.service;

public class UserService {

    public void login(){
        System.out.println("用户正在登录系统....");
    }

    public void logout(){
        System.out.println("用户正在退出系统....");
    }
}

使用CGLIB在内存中为UserService类生成代理类,并创建对象:

package com.powernode.mall;

import com.powernode.mall.service.UserService;
import net.sf.cglib.proxy.Enhancer;

public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        enhancer.setCallback(方法拦截器对象);
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userServiceProxy = (UserService)enhancer.create();

        userServiceProxy.login();
        userServiceProxy.logout();

    }
}

和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor

编写MethodInterceptor接口实现类:

package com.powernode.mall.service;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        return null;
    }
}

MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:

第一个参数:目标对象

第二个参数:目标方法

第三个参数:目标方法调用时的实参

第四个参数:代理方法

在MethodInterceptor的intercept()方法中调用目标以及添加增强:

package com.powernode.mall.service;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前增强
        long begin = System.currentTimeMillis();
        // 调用目标
        Object retValue = methodProxy.invokeSuper(target, objects);
        // 后增强
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "毫秒");
        // 一定要返回
        return retValue;
    }
}

回调已经写完了,可以修改客户端程序了:

package com.powernode.mall;

import com.powernode.mall.service.TimerMethodInterceptor;
import com.powernode.mall.service.UserService;
import net.sf.cglib.proxy.Enhancer;

public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器
        Enhancer enhancer = new Enhancer();
        // 告诉cglib要继承哪个类
        enhancer.setSuperclass(UserService.class);
        // 设置回调接口
        enhancer.setCallback(new TimerMethodInterceptor());
        // 生成源码,编译class,加载到JVM,并创建代理对象
        UserService userServiceProxy = (UserService)enhancer.create();

        userServiceProxy.login();
        userServiceProxy.logout();

    }
}

对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:

img

  • –add-opens java.base/java.lang=ALL-UNNAMED
  • –add-opens java.base/sun.net.util=ALL-UNNAMED

面向切面编程AOP

IoC使软件组件松耦合。AOP让你能够捕捉系统中经常使用的功能,把它转化成组件。

AOP(Aspect Oriented Programming):面向切面编程,面向方面编程。(AOP是一种编程技术)

AOP是对OOP的补充延伸。

AOP底层使用的就是动态代理来实现的。

Spring的AOP使用的动态代理是:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。


AOP介绍

一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务

这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。

如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:

  • 第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处。
  • 第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。

使用AOP可以很轻松的解决以上问题。

在这里插入图片描述

用一句话总结AOP:将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。

AOP的优点:

  • 代码复用性增强。
  • 代码易维护。
  • 使开发者更关注业务逻辑。

AOP的七大术语

public class UserService{
    public void do1(){
        System.out.println("do 1");
    }
    public void do2(){
        System.out.println("do 2");
    }
    public void do3(){
        System.out.println("do 3");
    }
    public void do4(){
        System.out.println("do 4");
    }
    public void do5(){
        System.out.println("do 5");
    }
    // 核心业务方法
    public void service(){
        //连接点
        do1();
        //连接点
        do2();
        //连接点
        do3();
        //连接点
        do5();
        //连接点
    }
}
  • 连接点 Joinpoint

    • 在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。
  • 切点 Pointcut

    • 在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)
  • 通知 Advice

    • 通知又叫增强,就是具体你要织入的代码。
    • 通知包括:
      • 前置通知 增强代码在切点前面
      • 后置通知 增强代码在切点后面
      • 环绕通知 增强代码在切点前后都有
      • 异常通知 增强代码在catch里
      • 最终通知 增强代码在finally里
  • 切面 Aspect

    • 切点 + 通知就是切面。
  • 织入 Weaving

    • 把通知应用到目标对象上的过程。
  • 代理对象 Proxy

    • 一个目标对象被织入通知后产生的新对象。
  • 目标对象 Target

    • 被织入通知的对象。

切点表达式

切点表达式用来定义通知(Advice)往哪些方法上切入。

切入点表达式语法格式:

execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])

访问控制权限修饰符:

  • 可选项。
  • 没写,就是4个权限都包括。
  • 写public就表示只包括公开的方法。

返回值类型:

  • 必填项。
  • * 表示返回值类型任意。

全限定类名:

  • 可选项。
  • 两个点“…”代表当前包以及子包下的所有类。
  • 省略时表示所有的类。

方法名:

  • 必填项。
  • *表示所有方法。
  • set*表示所有的set方法。

形式参数列表:

  • 必填项

  • () 表示没有参数的方法

  • (…) 参数类型和个数随意的方法

  • (*) 只有一个参数的方法

  • (*, String) 第一个参数类型随意,第二个参数是String的。

异常:

  • 可选项。
  • 省略时表示任意异常类型

理解以下的切点表达式:

  • service包下所有的类中以delete开始的public方法
execution(public * com.powernode.mall.service..delete(..))
  • mall包下所有的类的所有的方法
execution(* com.powernode.mall..*(..))
  • 所有类的所有方法
execution(* *(..))

使用Spring的AOP

Spring对AOP的实现包括以下3种方式:

  • 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
  • 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
  • 第三种方式:Spring框架自己实现的AOP,基于XML配置方式。

实际开发中,都是Spring+AspectJ来实现AOP。所以我们重点学习第一种和第二种方式。

什么是AspectJ?(Eclipse组织的一个支持AOP的框架。AspectJ框架是独立于Spring框架之外的一个框架,Spring框架用了AspectJ)

AspectJ项目起源于帕洛阿尔托(Palo Alto)研究中心(缩写为PARC)。该中心由Xerox集团资助,Gregor Kiczales领导,从1997年开始致力于AspectJ的开发,1998年第一次发布给外部用户,2001年发布1.0 release。为了推动AspectJ技术和社团的发展,PARC在2003年3月正式将AspectJ项目移交给了Eclipse组织,因为AspectJ的发展和受关注程度大大超出了PARC的预期,他们已经无力继续维持它的发展。


基于AspectJ的AOP开发

通知类型

通知类型包括:

  • 前置通知:@Before 目标方法执行之前的通知
  • 后置通知:@AfterReturning 目标方法执行之后的通知
  • 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
  • 异常通知:@AfterThrowing 发生异常之后执行的通知
  • 最终通知:@After 放在finally语句块中的通知

AspectJ的AOP的依赖

<!--spring aspects依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>6.0.0-M2</version>
</dependency>

Spring配置文件中添加context命名空间和aop命名空间

<?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">

</beans>
案例

切面类

@Component
//@Aspect注解标注代表的就是切面类
@Aspect
public class LogAspect {
//    @Before()注解标注的方法就是一个前置通知
    @Before("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }
    //后置通知
    @AfterReturning("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void AfterReturningAdvice(){
        System.out.println("后置通知");
    }
    //环绕通知
    @Around("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void AroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("前环绕");
        joinPoint.proceed();
        System.out.println("后环绕");
    }
    //异常通知
    @AfterThrowing("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void AfterThrowingAdvice(){
        System.out.println("异常通知");
    }
    //最终通知
    @After("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void AfterAdvice(){{
        System.out.println("最终通知");}
    }
}

目标对象

@Service
public class UserService {
    public void login(){
        System.out.println("用户正在登录");
    }
    public void exit(){
        System.out.println("用户正在退出");
    }
}

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">

        <context:component-scan base-package="com.wzw.spring6.service" />

<!--        开启自动代理-->
<!--        Spring容器在扫描类的时候,查看该类上是否有@Aspect注解,如果有,则自动给这个类生成代理对象-->
<!--        proxy-target-class="true" 表示强制使用CGLIB代理,默认是false,表示接口用JDK,类用CGLIB-->
        <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

测试

public class SpringAOPTest {
    @Test
    public void testBefore(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
        userService.exit();
    }
}
//前环绕
//前置通知
//用户正在退出
//后置通知
//最终通知
//后环绕

当发生异常的时候:情况1

public void exit(){
    System.out.println("用户正在退出");
    String s=null;
    try {
        s.toString();
    } catch (Exception e) {
        throw new RuntimeException("发生异常");
    }
}
//前环绕
//前置通知
//用户正在退出
//异常通知
//最终通知

java.lang.RuntimeException: 发生异常

由此可见如果发生异常的时候,后环绕跟后置通知是不会执行的

当发生异常的时候:情况2

public void exit(){

    String s=null;
    try {
        s.toString();
    } catch (Exception e) {
        throw new RuntimeException("发生异常");
    }
    System.out.println("用户正在退出");
}
//前环绕
//前置通知
//异常通知
//最终通知

java.lang.RuntimeException: 发生异常

切面的先后顺序

业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高

切面1:@Order(0) 先进LogAspect切面

@Component
//@Aspect注解标注代表的就是切面类
@Aspect
@Order(0)
public class LogAspect {
//    @Before()注解标注的方法就是一个前置通知
    @Before("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }
    //后置通知
    @AfterReturning("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void AfterReturningAdvice(){
        System.out.println("后置通知");
    }
    //环绕通知
    @Around("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void AroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("前环绕");
        joinPoint.proceed();
        System.out.println("后环绕");
    }
    //异常通知
    @AfterThrowing("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void AfterThrowingAdvice(){
        System.out.println("异常通知");
    }
    //最终通知
    @After("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void AfterAdvice(){{
        System.out.println("最终通知");}
    }

}

切面2:@Order(1) 后进OtherAspect切面

@Component
//@Aspect注解标注代表的就是切面类
@Aspect
@Order(1)
public class OtherAspect {
    //    @Before()注解标注的方法就是一个前置通知
    @Before("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void beforeAdvice(){
        System.out.println("前置通知:other");
    }
    //后置通知
    @AfterReturning("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void AfterReturningAdvice(){
        System.out.println("后置通知:other");
    }
    //环绕通知
    @Around("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void AroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("前环绕:other");
        joinPoint.proceed();
        System.out.println("后环绕:other");
    }
    //异常通知
    @AfterThrowing("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void AfterThrowingAdvice(){
        System.out.println("异常通知:other");
    }
    //最终通知
    @After("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void AfterAdvice(){{
        System.out.println("最终通知:other");}
    }

}

输出结果:

前环绕
前置通知
前环绕:other
前置通知:other
用户正在登录
后置通知:other
最终通知:other
后环绕:other
后置通知
最终通知
后环绕

切点表达式技巧,提高复用

构建一个方法,并在方法上使用@PointCut注解

@Pointcut("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void xx(){}

后面的切点表达式就用“xx()”

public class LogAspect {
    @Pointcut("execution(* com.wzw.spring6.service.UserService.*(..))")
    public void xx(){}
//    @Before()注解标注的方法就是一个前置通知
    @Before("xx()")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }
    //后置通知
    @AfterReturning("xx()")
    public void AfterReturningAdvice(){
        System.out.println("后置通知");
    }
}
全注解开发

使用注解类代替xml

@Component
@ComponentScan("com.wzw.spring6.service")
//开启CGLIB强制动态代理
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class SpringConfig {

}
 @Test
    public void testBeforeByAnnotation(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
//        userService.exit();
    }

基于XML配置方式的AOP(了解)

第一步:编写目标类

package com.powernode.spring6.service;

// 目标类
public class VipService {
    public void add(){
        System.out.println("保存vip信息。");
    }
}

第二步:编写切面类,并且编写通知

package com.powernode.spring6.service;

import org.aspectj.lang.ProceedingJoinPoint;

// 负责计时的切面类
public class TimerAspect {
    
    public void time(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long begin = System.currentTimeMillis();
        //执行目标
        proceedingJoinPoint.proceed();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }
}

第三步:编写spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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">

    <!--纳入spring bean管理-->
    <bean id="vipService" class="com.powernode.spring6.service.VipService"/>
    <bean id="timerAspect" class="com.powernode.spring6.service.TimerAspect"/>

    <!--aop配置-->
    <aop:config>
        <!--切点表达式-->
        <aop:pointcut id="p" expression="execution(* com.powernode.spring6.service.VipService.*(..))"/>
        <!--切面-->
        <aop:aspect ref="timerAspect">
            <!--切面=通知 + 切点-->
            <aop:around method="time" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>
</beans>

测试程序:

package com.powernode.spring6.test;

import com.powernode.spring6.service.VipService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest3 {

    @Test
    public void testAOPXml(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop-xml.xml");
        VipService vipService = applicationContext.getBean("vipService", VipService.class);
        vipService.add();
    }
}


事务概述

  • 什么是事务

    • 在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。
    • 多条DML要么同时成功,要么同时失败,这叫做事务。
    • 事务:Transaction(tx)
  • 事务的四个处理过程:

    • 第一步:开启事务 (start transaction)
    • 第二步:执行核心业务代码
    • 第三步:提交事务(如果核心业务处理过程中没有出现异常)(commit transaction)
    • 第四步:回滚事务(如果核心业务处理过程中出现异常)(rollback transaction)
  • 事务的四个特性:

    • A 原子性:事务是最小的工作单元,不可再分。
    • C 一致性:事务要求要么同时成功,要么同时失败。事务前和事务后的总量不变。
    • I 隔离性:事务和事务之间因为有隔离性,才可以保证互不干扰。
    • D 持久性:持久性是事务结束的标志。

Spring实现事务的两种方式

  • 编程式事务

    • 通过编写代码的方式来实现事务的管理。
  • 声明式事务

    • 基于注解方式
    • 基于XML配置方式

Spring事务管理API

Spring对事务的管理底层实现方式是基于AOP实现的。采用AOP的方式进行了封装。所以Spring专门针对事务开发了一套API,API的核心接口如下:

img

PlatformTransactionManager接口:spring事务管理器的核心接口。在Spring6中它有两个实现:

  • DataSourceTransactionManager:支持JdbcTemplate、MyBatis、Hibernate等事务管理。
  • JtaTransactionManager:支持分布式事务管理。

如果要在Spring6中使用JdbcTemplate,就要使用DataSourceTransactionManager来管理事务。(Spring内置写好了,可以直接用。)

声明式事务之注解实现方式

  • 第一步:在spring配置文件中配置事务管理器。

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"/>
    </bean>
    
  • 第二步:在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 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
  • 第三步:在spring配置文件中配置“事务注解驱动器”,开始注解的方式控制事务。
<tx:annotation-driven transaction-manager="transactionManager"/>
  • 第四步:在service类上或方法上添加@Transactional注解
 @Override
    @Transactional
    public void transfer(String fromActno, String toActno, Double money) {
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() < money) {
            throw new RuntimeException("余额不足");
        }
        Account toAct = accountDao.selectByActno(toActno);

        fromAct.setBalance(fromAct.getBalance() - money);
        toAct.setBalance(toAct.getBalance() + money);

        int count = accountDao.update(fromAct);
//        String s = null;
//        s.toString();
        count +=accountDao.update(toAct);

        if (count !=2){
            System.out.println("转账失败,需要回滚");
        }
    }

案例

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: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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    <context:component-scan base-package="com.wzw.bank"/>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

<!--    配置事务管理器-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    开启事务注解驱动器,开启事务注解-->
    <tx:annotation-driven transaction-manager="txManager"/>
</beans>

DAO:


@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    @Resource(name="jdbcTemplate")
    private JdbcTemplate jdbcTemplate;
    @Override
    public Account selectByActno(String actno) {
        String sql = "select * from t_act where actno = ?";
        Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);
        return account;
    }

    @Override
    public int update(Account account) {
        String sql = "update t_act set balance = ? where actno= ?";
        int count = jdbcTemplate.update(sql, account.getBalance(), account.getActno());
        return count;
    }
}

Service:

@Service("accountService")
//@Transactional
public class AccountServiceImpl implements AccountService {
    @Resource(name = "accountDao")
    private AccountDao accountDao;


    @Override
    @Transactional
    public void transfer(String fromActno, String toActno, Double money) {
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() < money) {
            throw new RuntimeException("余额不足");
        }
        Account toAct = accountDao.selectByActno(toActno);

        fromAct.setBalance(fromAct.getBalance() - money);
        toAct.setBalance(toAct.getBalance() + money);

        int count = accountDao.update(fromAct);
//        String s = null;
//        s.toString();
        count +=accountDao.update(toAct);

        if (count !=2){
            System.out.println("转账失败,需要回滚");
        }
    }
}

事务属性

@Transactional
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    String[] label() default {};

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    String timeoutString() default "";

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

其中几个比较重要的需要记一下:

  • Propagation propagation() default Propagation.REQUIRED 事务传播行为
  • Isolation isolation() default Isolation.DEFAULT 事务隔离级别
  • int timeout() default -1 事务超时
  • boolean readOnly() default false 只读事务
  • Class<? extends Throwable>[] rollbackFor() default {} 设置出现哪些异常回滚事务
  • Class<? extends Throwable>[] noRollbackFor() default {} 设置出现哪些异常不回滚事务

事务传播行为

什么是事务的传播行为?

在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。

事务传播行为在spring框架中被定义为枚举类型:

public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);

一共有七种传播行为:

  • REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行**【有就加入,没有就不管了】**
  • MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常**【有就加入,没有就抛异常】**
  • REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起**【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】**
  • NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务**【不支持事务,存在就挂起】**
  • NEVER:以非事务方式运行,如果有事务存在,抛出异常**【不支持事务,存在就抛异常】**
  • NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】

案例

解析 **REQUIRED **跟 REQUIRES_NEW 的区别

当两个service都是REQUIRED的时候

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Resource(name = "accountDao")
    private AccountDao accountDao;
    @Resource(name = "accountService2")
    private AccountService accountService2;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void save(Account account) {
        accountDao.insert(account);
        try {
            Account account1 = new Account("act004", 0.0);
            accountService2.save(account1);
        } catch (Exception e) {

        }
    }
}
@Service("accountService2")
//@Transactional
public class AccountServiceImpl2 implements AccountService {
    @Resource(name = "accountDao")
    private AccountDao accountDao;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void save(Account account) {
        accountDao.insert(account);
        String s = null;
        s.toString();
    }
}

测试

@Test
public void testPropagation(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
    Account account = new Account("act003", 0.0);
    accountService.save(account);
}
Creating new transaction with name [com.wzw.bank.service.Impl.AccountServiceImpl.save]:
Participating in existing transaction

出问题了,因为两个service都是同一个事务,当service2出现问题的时候,抛出错:service中的accountService2.save(account1);抛出错误被捕捉到,但是没有用,因为出现了报错,事务中出现报错是不是这个事务就自动回滚了?所以数据库不执行操作

当service2的事务是REQUIRED_NEW的时候

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Resource(name = "accountDao")
    private AccountDao accountDao;
    @Resource(name = "accountService2")
    private AccountService accountService2;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void save(Account account) {
        accountDao.insert(account);
        try {
            Account account1 = new Account("act004", 0.0);
            accountService2.save(account1);
        } catch (Exception e) {

        }
    }
}
@Service("accountService2")
//@Transactional
public class AccountServiceImpl2 implements AccountService {
    @Resource(name = "accountDao")
    private AccountDao accountDao;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void save(Account account) {
        accountDao.insert(account);
        String s = null;
        s.toString();
    }
}

测试:

@Test
public void testPropagation(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
    Account account = new Account("act003", 0.0);
    accountService.save(account);
}
 Creating new transaction with name [com.wzw.bank.service.Impl.AccountServiceImpl.save]:
 Suspending current transaction, creating new transaction with name [com.wzw.bank.service.Impl.AccountServiceImpl2.save]

没有问题,因为这是两个事务,当service2发生错误的时候,抛出错误:service中的accountService2.save(account1);抛出错误被捕捉到,执行catch语句,service2的事务回滚,但是对于service来说,没有发生错误,accountService2.save(account1);的报错已经被catch捕捉,当前事务可以继续执行,所以数据库最后只添加service中的那一条记录


事务隔离级别

  @Transactional(isolation = Isolation.READ_UNCOMMITTED)

事务隔离级别类似于教室A和教室B之间的那道墙,隔离级别越高表示墙体越厚。隔音效果越好。

数据库中读取数据存在的三大问题:(三大读问题)

  • 脏读:读取到没有提交到数据库的数据,叫做脏读。
  • 不可重复读:在同一个事务当中,第一次和第二次读取的数据不一样。
  • 幻读:读到的数据是假的。

事务隔离级别包括四个级别:

  • 读未提交:READ_UNCOMMITTED
  • 这种隔离级别,存在脏读问题,所谓的脏读(dirty read)表示能够读取到其它事务未提交的数据。
  • 读提交:READ_COMMITTED
    • 解决了脏读问题,其它事务提交之后才能读到,但存在不可重复读问题。
  • 可重复读:REPEATABLE_READ
    • 解决了不可重复读,可以达到可重复读效果,只要当前事务不结束,读取到的数据一直都是一样的。但存在幻读问题。
  • 序列化读:SERIALIZABLE
    • 解决了幻读问题,事务排队执行。不支持并发
隔离级别脏读不可重复读幻读
读未提交
读提交
可重复读
序列化

livegore。com

案例

隔离级别READ_UNCOMMITTEDREAD_COMMITTED 的区别

隔离级别 READ_UNCOMMITTED

IsolationServiceImpl类1

@Service("isolationService")
public class IsolationServiceImpl{
    @Resource(name = "accountDao")
    private AccountDao accountDao;

    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void getAccountByactno(String actno){
        System.out.println(accountDao.selectByActno(actno));
    }
}

IsolationServiceImpl2类2,用来模拟延时提交数据,数据已经进入缓存,但是还没有提交

@Service("isolationService2")
public class IsolationServiceImpl2 implements AccountService {
    @Resource(name = "accountDao")
    private AccountDao accountDao;
    @Override
    public void transfer(String fromActno, String toActno, Double money) {

    }

    @Override
    public void save(Account account) {
        accountDao.insert(account);
        try {
            Thread.sleep(1000*20);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

测试

@Test
public void testIsolation(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    AccountService accountService2 = applicationContext.getBean("isolationService2", AccountService.class);
    Account account = new Account("act003", 0.0);
    accountService2.save(account);
}
@Test
public void testIsolation2(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    IsolationServiceImpl isolationServiceImpl = applicationContext.getBean("isolationService", IsolationServiceImpl.class);
    isolationServiceImpl.getAccountByactno("act003");
}

读取到了:com.wzw.bank.bean.Account@6ad5923a

隔离级别是 READ_UNCOMMITTED 读未提交,说明在数据未提交的时候,就可以读取到了

隔离级别READ_COMMITTED

IsolationServiceImpl类1

@Service("isolationService")
public class IsolationServiceImpl{
    @Resource(name = "accountDao")
    private AccountDao accountDao;

    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void getAccountByactno(String actno){
        System.out.println(accountDao.selectByActno(actno));
    }
}

测试:

结果报错,在Spring自带的JdbcTemplate中,读不到数据是会报错的


事务超时

@Transactional(timeout = 10)

以上代码表示设置事务的超时时间为10秒。

表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话,最终结果会选择回滚。

默认值-1,表示没有时间限制。

这里有个坑,事务的超时时间指的是哪段时间?

在当前事务当中,最后一条DML语句执行之前的时间。如果最后一条DML语句后面很有很多业务逻辑,这些业务代码执行的时间不被计入超时时间。

案例

@Override
@Transactional(timeout = 2)
public void save(Account account) {
    accountDao.insert(account);
    Account account1 = new Account("act004", 0.0);
    accountDao.insert(account1);
    try {
        Thread.sleep(1000*2);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

没超时,最后一条DML语句是accountDao.insert(account1);执行完之后后面的时间就不算了

@Override
@Transactional(timeout = 2)
public void save(Account account) {
    accountDao.insert(account);
    try {
        Thread.sleep(1000*2);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    Account account1 = new Account("act004", 0.0);
    accountDao.insert(account1);
}

@Override
@Transactional(timeout = 2)
public void save(Account account) {
    try {
        Thread.sleep(1000*2);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    accountDao.insert(account);
    Account account1 = new Account("act004", 0.0);
    accountDao.insert(account1);
}

超时了,最后一条DML语句是accountDao.insert(account1);,从进来开始算时间直到最后一条DML语句结束


只读事务

代码如下:

@Transactional(readOnly = true)

将当前事务设置为只读事务,在该事务执行过程中只允许select语句执行,delete insert update均不可执行。

该特性的作用是:启动spring的优化策略。提高select语句执行效率。

如果该事务中确实没有增删改操作,建议设置为只读事务。


异常回滚事务

代码如下:

@Transactional(rollbackFor = RuntimeException.class)

表示只有发生RuntimeException异常或该异常的子类异常才回滚。


异常不回滚事务

代码如下:

@Transactional(noRollbackFor = NullPointerException.class)

表示发生NullPointerException或该异常的子类异常不回滚,其他异常则回滚。


事务的全注解式开发

@Configuration //代替Spring.xml配置文件,在这个类当中完成配置
@ComponentScan("com.wzw.bank") //组件扫描
@EnableTransactionManagement //开启事务注解
public class Spring6Config {
    @Bean(name = "dataSource")
    public DataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){ //Spring在调用这个方法的时候,会自动传递一个DataSource对象
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    @Bean(name = "txManager")
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}
@Test
public void testNoXML(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
    AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
    try {
        accountService.transfer("act001","act002",10000.0);
        System.out.println("转账成功");
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

声明式事务之XML实现方式

配置步骤:

  • 第一步:配置事务管理器
  • 第二步:配置通知
  • 第三步:配置切面

引入aspectj的依赖

<!--aspectj依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>6.0.0-M2</version>
</dependency>
<?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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    <context:component-scan base-package="com.wzw.bank"/>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

<!--    配置事务管理器-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    配置通知-->
    <tx:advice id="txAdvice" transaction-manager="txManager">
<!--        配置通知的相关属性-->
        <tx:attributes>
            <tx:method name="transfer" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="modify*" propagation="REQUIRED"/>
            <tx:method name="select*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
<!--    配置切面-->
    <aop:config>
<!--        切点-->
        <aop:pointcut id="txPointcut" expression="execution(* com.wzw.bank..*(..))"/>
<!--        切面=切点+通知-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
</beans>
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    @Resource(name="jdbcTemplate")
    private JdbcTemplate jdbcTemplate;
    @Override
    public Account selectByActno(String actno) {
        String sql = "select * from t_act where actno = ?";
        Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);
        return account;
    }
    @Override
    public int update(Account account) {
        String sql = "update t_act set balance = ? where actno= ?";
        int count = jdbcTemplate.update(sql, account.getBalance(), account.getActno());
        return count;
    }
}
@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Resource(name = "accountDao")
    private AccountDao accountDao;
    @Override
    public void transfer(String fromActno, String toActno, Double money) {
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() < money) {
            throw new RuntimeException("余额不足");
        }
        Account toAct = accountDao.selectByActno(toActno);
        fromAct.setBalance(fromAct.getBalance() - money);
        toAct.setBalance(toAct.getBalance() + money);
        int count = accountDao.update(fromAct);
        count +=accountDao.update(toAct);
        if (count !=2){
            System.out.println("转账失败,需要回滚");
        }
   

测试

public class BankTest {
    @Test
    public void testNoAnnotation(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        accountService.transfer("act001","act002",1000.0);
    }
}

Spring6整合JUnit

Spring对JUnit4的支持

引入依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring6-19-junit</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <repositories>
        <repository>
            <id>repository.spring.milestone</id>
            <name>Spring Milestone Repository</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
<!--        spring对junit支持的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
<!--            这个版本既支持junit4又支持junit5-->
            <version>6.0.0-M2</version>
        </dependency>
<!--        junit4的依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>2.1.1</version>
        </dependency>
    </dependencies>

</project>

Bean

@Component
public class User {
    @Value("zs")
    private String name;
    @Value("20")
    private int age;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.wzw.spring6.bean"/>

</beans>

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class UserTest {
    @Resource
    private User user;
    @Test
    public void testUSerJUnit4(){
//        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//        User user = applicationContext.getBean("user", User.class);
        System.out.println(user);
    }
}

其中@RunWith(SpringJUnit4ClassRunner.class)代表引入的是Junit4的依赖

@ContextConfiguration("classpath:spring.xml")代表从根路径下读取名为“spring.xml”的文件

自动注入User


Spring对JUnit5的支持

引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring6-19-junit</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <repositories>
        <repository>
            <id>repository.spring.milestone</id>
            <name>Spring Milestone Repository</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
<!--        spring对junit支持的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
<!--            这个版本既支持junit4又支持junit5-->
            <version>6.0.0-M2</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.9.0</version>
            <scope>test</scope>
        </dependency>
<!--        junit4的依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>2.1.1</version>
        </dependency>

    </dependencies>

</project>

测试:

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:spring.xml")
public class UserTest2 {
    @Resource
    private User user;
    @Test
    public void testJUnit5(){
        System.out.println(user);
    }
}

总结

junti4跟junit5的使用区别

使用junti4的时候使用的注解是:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")

使用junti5的时候使用的注解是:

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:spring.xml")

Spring6集成Mybatis

实现步骤

  • 第一步:准备数据库表

    • 使用t_act表(账户表)
  • 第二步:IDEA中创建一个模块,并引入依赖

    • spring-context
    • spring-jdbc
    • mysql驱动
    • mybatis
    • mybatis-spring:mybatis提供的与spring框架集成的依赖
    • 德鲁伊连接池
    • junit
  • 第三步:基于三层架构实现,所以提前创建好所有的包

    • com.powernode.bank.mapper
    • com.powernode.bank.service
    • com.powernode.bank.service.impl
    • com.powernode.bank.pojo
  • 第四步:编写pojo

    • Account,属性私有化,提供公开的setter getter和toString。
  • 第五步:编写mapper接口

    • AccountMapper接口,定义方法
  • 第六步:编写mapper配置文件

    • 在配置文件中配置命名空间,以及每一个方法对应的sql。
  • 第七步:编写service接口和service接口实现类

    • AccountService
    • AccountServiceImpl
  • 第八步:编写jdbc.properties配置文件

    • 数据库连接池相关信息
  • 第九步:编写mybatis-config.xml配置文件

    • 该文件可以没有,大部分的配置可以转移到spring配置文件中。
    • 如果遇到mybatis相关的系统级配置,还是需要这个文件。
  • 第十步:编写spring.xml配置文件

    • 组件扫描
    • 引入外部的属性文件
    • 数据源
    • SqlSessionFactoryBean配置
      • 注入mybatis核心配置文件路径
      • 指定别名包
      • 注入数据源
    • Mapper扫描配置器
      • 指定扫描的包
    • 事务管理器DataSourceTransactionManager
      • 注入数据源
    • 启用事务注解
      • 注入事务管理器
  • 第十一步:编写测试程序,并添加事务,进行测试

案例

引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring6-20-mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
<!--    spring里程碑版本的仓库-->
    <repositories>
        <repository>
            <id>repository.spring.milestone</id>
            <name>Spring Milestone Repository</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>
<!--    spring注解api-->
    <dependencies>
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>2.1.1</version>
        </dependency>
<!--        spring依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
<!--        mysql连接驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
<!--        mybatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
<!--        junit4依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
<!--        AOP切面依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
<!--        德鲁伊连接池依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.8</version>
        </dependency>
<!--        jdbc驱动-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
<!--        mybatis提供的与spring集成的依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.7</version>
        </dependency>
    </dependencies>

</project>

创建包和类

在这里插入图片描述

public class Account {
    private String actno;
    private Double balance;
//此处省略set,get,tostring方法
}
public interface AccountMapper {
    Account selectByActno(String actno);
    int updateByAct(Account account);
}
public interface AccountService {
    void transfer(String fromActno,String toActno,Double money);
}
@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountMapper accountMapper;
    @Transactional
    @Override
    public void transfer(String fromActno,String toActno,Double money) {
        Account fromAccount = accountMapper.selectByActno(fromActno);
        if (fromAccount.getBalance() < money) {
            throw new RuntimeException("余额不足");
        }
        fromAccount.setBalance(fromAccount.getBalance() - money);
        Account toAccount = accountMapper.selectByActno(toActno);
        toAccount.setBalance(toAccount.getBalance() + money);
        int count = accountMapper.updateByAct(fromAccount);
        count += accountMapper.updateByAct(toAccount);
        if (count != 2){
            throw new RuntimeException("转账发生错误");
        }
    }
}

编写mapper配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wzw.spring6.mapper.AccountMapper">
    <select id="selectByActno" resultType="Account">
        select * from t_act where actno = #{actno}
    </select>

    <update id="updateByAct">
<!--    <update id="updateByActno" parameterType="Account">-->
        update t_act set balance= #{balance} where actno = #{actno}
    </update>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.ort/DTD Config 3.0//EN"
        "http://mybatis.ort/dtd/mybatis-3-config.dtd">

<configuration>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
</configuration>

编写spring配置文件

可以将通用代码放在子配置文件中,再引入到主核心配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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 https://www.springframework.org/schema/context/spring-context.xsd">
    <!--    ○ 组件扫描-->
    <context:component-scan base-package="com.wzw.spring6"/>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    
<!--    ○ 组件扫描-->
<!--    <context:component-scan base-package="com.wzw.spring6"/>-->
<!--    在spring的核心配置文件中引入在其他的子spring配置文件-->
    <import resource="common.xml"/>
<!--    ○ 引入外部的属性文件-->
    <context:property-placeholder location="jdbc.properties"/>
<!--    ○ 数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
<!--    ○ SqlSessionFactoryBean配置-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--    ■ 注入数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--    ■ 注入mybatis核心配置文件路径-->
        <property name="configLocation" value="mybatis-config.xml"/>
        <!--    ■ 指定别名包-->
        <property name="typeAliasesPackage" value="com.wzw.spring6.bean"/>
    </bean>


<!--    ○ Mapper扫描配置器 主要扫码Mapper接口,生成代理类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.wzw.spring6.mapper"/>
    </bean>
<!--    ○ 事务管理器DataSourceTransactionManager-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--    ■ 注入数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    ○ 启用事务注解-->
    <tx:annotation-driven transaction-manager="txManager"/>
</beans>

这里是将mybatis-config.xml文件融合到spring.xml文件了

在这里对比一下如何融合

  • 注册Mapper mappers

    •     <mappers>
              <package name="com.wzw.spring6.mapper"/>
          </mappers>
      
    • <!--    ○ Mapper扫描配置器 主要扫码Mapper接口,生成代理类-->
          <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
              <property name="basePackage" value="com.wzw.spring6.mapper"/>
          </bean>
      
  • 环境environments

    •     <properties resource="jdbc.properties"/>
      <environments default="development">
              <environment id="development">
                  <transactionManager type="JDBC"></transactionManager>
                  <dataSource type="POOLED">
                      <property name="driver" value="${jdbc.driver}"/>
                      <property name="url" value="${jdbc.url}"/>
                      <property name="username" value="${jdbc.username}"/>
                      <property name="password" value="${jdbc.password}"/>
                  </dataSource>
              </environment>
          </environments>
      
    • <!--    ○ 引入外部的属性文件-->
          <context:property-placeholder location="jdbc.properties"/>
      <!--    ○ 数据源-->
          <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
              <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
              <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
              <property name="username" value="root"/>
              <property name="password" value="123456"/>
          </bean>
      
  • 别名typeAliases

    •     <typeAliases>
              <package name="com.wzw.spring6.bean"/>
          </typeAliases>
      
    • <!--    ○ SqlSessionFactoryBean配置-->
          <bean class="org.mybatis.spring.SqlSessionFactoryBean">
              <!--    ■ 注入数据源-->
              <property name="dataSource" ref="dataSource"/>
              <!--    ■ 注入mybatis核心配置文件路径-->
              <property name="configLocation" value="mybatis-config.xml"/>
              <!--    ■ 指定别名包-->
              <property name="typeAliasesPackage" value="com.wzw.spring6.bean"/>
          </bean>
      
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.ort/DTD Config 3.0//EN"
        "http://mybatis.ort/dtd/mybatis-3-config.dtd">

<configuration>
    <properties resource="jdbc.properties"/>
    <settings>
        <setting name="logImpl" value="slf4j"/>
    </settings>

    <typeAliases>
        <package name="com.wzw.spring6.bean"/>
    </typeAliases>


    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <package name="com.wzw.spring6.mapper"/>
    </mappers>

</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    
<!--    ○ 组件扫描-->
<!--    <context:component-scan base-package="com.wzw.spring6"/>-->
<!--    在spring的核心配置文件中引入在其他的子spring配置文件-->
    <import resource="common.xml"/>
<!--    ○ 引入外部的属性文件-->
    <context:property-placeholder location="jdbc.properties"/>
<!--    ○ 数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
<!--    ○ SqlSessionFactoryBean配置-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--    ■ 注入数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--    ■ 注入mybatis核心配置文件路径-->
        <property name="configLocation" value="mybatis-config.xml"/>
        <!--    ■ 指定别名包-->
        <property name="typeAliasesPackage" value="com.wzw.spring6.bean"/>
    </bean>


<!--    ○ Mapper扫描配置器 主要扫码Mapper接口,生成代理类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.wzw.spring6.mapper"/>
    </bean>
<!--    ○ 事务管理器DataSourceTransactionManager-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--    ■ 注入数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    ○ 启用事务注解-->
    <tx:annotation-driven transaction-manager="txManager"/>
</beans>

Spring中的八大模式

简单工厂模式

BeanFactory的getBean()方法,通过唯一标识来获取Bean对象。是典型的简单工厂模式(静态工厂模式);


工厂方法模式

FactoryBean是典型的工厂方法模式。在配置文件中通过factory-method属性来指定工厂方法,该方法是一个实例方法。


单例模式

Spring用的是双重判断加锁的单例模式。请看下面代码,我们之前讲解Bean的循环依赖的时候见过:

img


代理模式

Spring的AOP就是使用了动态代理实现的。


装饰器模式

JavaSE中的IO流是非常典型的装饰器模式。

Spring 中配置 DataSource 的时候,这些dataSource可能是各种不同类型的,比如不同的数据库:Oracle、SQL Server、MySQL等,也可能是不同的数据源:比如apache 提供的org.apache.commons.dbcp.BasicDataSource、spring提供的org.springframework.jndi.JndiObjectFactoryBean等。

这时,能否在尽可能少修改原有类代码下的情况下,做到动态切换不同的数据源?此时就可以用到装饰者模式。

Spring根据每次请求的不同,将dataSource属性设置成不同的数据源,以到达切换数据源的目的。

Spring中类名中带有:Decorator和Wrapper单词的类,都是装饰器模式。


观察者模式

定义对象间的一对多的关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。Spring中观察者模式一般用在listener的实现。

Spring中的事件编程模型就是观察者模式的实现。在Spring中定义了一个ApplicationListener接口,用来监听Application的事件,Application其实就是ApplicationContext,ApplicationContext内置了几个事件,其中比较容易理解的是:ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent


策略模式

策略模式是行为性模式,调用不同的方法,适应行为的变化 ,强调父类的调用子类的特性 。

getHandler是HandlerMapping接口中的唯一方法,用于根据请求找到匹配的处理器。

比如我们自己写了AccountDao接口,然后这个接口下有不同的实现类:AccountDaoForMySQL,AccountDaoForOracle。对于service来说不需要关心底层具体的实现,只需要面向AccountDao接口调用,底层可以灵活切换实现,这就是策略模式。


模板方法模式

Spring中的JdbcTemplate类就是一个模板类。它就是一个模板方法设计模式的体现。在模板类的模板方法execute中编写核心算法,具体的实现步骤在子类中完成。
ource" ref=“dataSource”/>

<tx:annotation-driven transaction-manager="txManager"/>
```

这里是将mybatis-config.xml文件融合到spring.xml文件了

在这里对比一下如何融合

  • 注册Mapper mappers

    •     <mappers>
              <package name="com.wzw.spring6.mapper"/>
          </mappers>
      
    • <!--    ○ Mapper扫描配置器 主要扫码Mapper接口,生成代理类-->
          <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
              <property name="basePackage" value="com.wzw.spring6.mapper"/>
          </bean>
      
  • 环境environments

    •     <properties resource="jdbc.properties"/>
      <environments default="development">
              <environment id="development">
                  <transactionManager type="JDBC"></transactionManager>
                  <dataSource type="POOLED">
                      <property name="driver" value="${jdbc.driver}"/>
                      <property name="url" value="${jdbc.url}"/>
                      <property name="username" value="${jdbc.username}"/>
                      <property name="password" value="${jdbc.password}"/>
                  </dataSource>
              </environment>
          </environments>
      
    • <!--    ○ 引入外部的属性文件-->
          <context:property-placeholder location="jdbc.properties"/>
      <!--    ○ 数据源-->
          <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
              <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
              <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
              <property name="username" value="root"/>
              <property name="password" value="123456"/>
          </bean>
      
  • 别名typeAliases

    •     <typeAliases>
              <package name="com.wzw.spring6.bean"/>
          </typeAliases>
      
    • <!--    ○ SqlSessionFactoryBean配置-->
          <bean class="org.mybatis.spring.SqlSessionFactoryBean">
              <!--    ■ 注入数据源-->
              <property name="dataSource" ref="dataSource"/>
              <!--    ■ 注入mybatis核心配置文件路径-->
              <property name="configLocation" value="mybatis-config.xml"/>
              <!--    ■ 指定别名包-->
              <property name="typeAliasesPackage" value="com.wzw.spring6.bean"/>
          </bean>
      
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.ort/DTD Config 3.0//EN"
        "http://mybatis.ort/dtd/mybatis-3-config.dtd">

<configuration>
    <properties resource="jdbc.properties"/>
    <settings>
        <setting name="logImpl" value="slf4j"/>
    </settings>

    <typeAliases>
        <package name="com.wzw.spring6.bean"/>
    </typeAliases>


    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <package name="com.wzw.spring6.mapper"/>
    </mappers>

</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    
<!--    ○ 组件扫描-->
<!--    <context:component-scan base-package="com.wzw.spring6"/>-->
<!--    在spring的核心配置文件中引入在其他的子spring配置文件-->
    <import resource="common.xml"/>
<!--    ○ 引入外部的属性文件-->
    <context:property-placeholder location="jdbc.properties"/>
<!--    ○ 数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
<!--    ○ SqlSessionFactoryBean配置-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--    ■ 注入数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--    ■ 注入mybatis核心配置文件路径-->
        <property name="configLocation" value="mybatis-config.xml"/>
        <!--    ■ 指定别名包-->
        <property name="typeAliasesPackage" value="com.wzw.spring6.bean"/>
    </bean>


<!--    ○ Mapper扫描配置器 主要扫码Mapper接口,生成代理类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.wzw.spring6.mapper"/>
    </bean>
<!--    ○ 事务管理器DataSourceTransactionManager-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--    ■ 注入数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    ○ 启用事务注解-->
    <tx:annotation-driven transaction-manager="txManager"/>
</beans>

Spring中的八大模式

简单工厂模式

BeanFactory的getBean()方法,通过唯一标识来获取Bean对象。是典型的简单工厂模式(静态工厂模式);


工厂方法模式

FactoryBean是典型的工厂方法模式。在配置文件中通过factory-method属性来指定工厂方法,该方法是一个实例方法。


单例模式

Spring用的是双重判断加锁的单例模式。请看下面代码,我们之前讲解Bean的循环依赖的时候见过:

img


代理模式

Spring的AOP就是使用了动态代理实现的。


装饰器模式

JavaSE中的IO流是非常典型的装饰器模式。

Spring 中配置 DataSource 的时候,这些dataSource可能是各种不同类型的,比如不同的数据库:Oracle、SQL Server、MySQL等,也可能是不同的数据源:比如apache 提供的org.apache.commons.dbcp.BasicDataSource、spring提供的org.springframework.jndi.JndiObjectFactoryBean等。

这时,能否在尽可能少修改原有类代码下的情况下,做到动态切换不同的数据源?此时就可以用到装饰者模式。

Spring根据每次请求的不同,将dataSource属性设置成不同的数据源,以到达切换数据源的目的。

Spring中类名中带有:Decorator和Wrapper单词的类,都是装饰器模式。


观察者模式

定义对象间的一对多的关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。Spring中观察者模式一般用在listener的实现。

Spring中的事件编程模型就是观察者模式的实现。在Spring中定义了一个ApplicationListener接口,用来监听Application的事件,Application其实就是ApplicationContext,ApplicationContext内置了几个事件,其中比较容易理解的是:ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent


策略模式

策略模式是行为性模式,调用不同的方法,适应行为的变化 ,强调父类的调用子类的特性 。

getHandler是HandlerMapping接口中的唯一方法,用于根据请求找到匹配的处理器。

比如我们自己写了AccountDao接口,然后这个接口下有不同的实现类:AccountDaoForMySQL,AccountDaoForOracle。对于service来说不需要关心底层具体的实现,只需要面向AccountDao接口调用,底层可以灵活切换实现,这就是策略模式。


模板方法模式

Spring中的JdbcTemplate类就是一个模板类。它就是一个模板方法设计模式的体现。在模板类的模板方法execute中编写核心算法,具体的实现步骤在子类中完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值