Spring

星期四, 12月 26, 2024 | 75分钟阅读

MyraZ
Spring

关于Spring的学习!

Spring框架

Spring框架概念

​ Spring 是众多开源java项目中的一员,基于分层的javaEE应用一站式轻量级开源框架,主要核心是 IOC(控制反转/依赖注入)与AOP(面向切面)两大技术,实现项目在开发过程中的轻松解耦,提高项目的开发效率。

​ 在项目中引入 Spring 立即可以带来下面的好处:

  • 降低组件之间的耦合度,实现软件各层之间的解耦。
  • 可以使用容器提供的众多服务,如:事务管理服务、消息服务等等。当我们使用容器管理事务时,开发人员就不再需要手工控制事务.也不需处理复杂的事务传播。
  • 容器提供单例模式支持,开发人员不再需要自己编写实现代码。
  • 容器提供了AOP技术,利用它很容易实现如权限拦截、运行期监控等功能。

image-20241229145316772

Spring源码架构

​ Spring 总共大约有20个模块,由1300多个不同的文件构成。而这些组件被分别整合在核心容器(Core Container)、Aop(AspectOriented Programming)和设备支持(Instrmentation)、数据访问及集成(Data Access/Integeration)、Web、报文发送(Messaging)、测试6个模块集合中。

  • 核心容器:由Spring-beans、Spring-core、pring Context、Spring-Expression四个模块组成。

    • Spring-beans 和 Spring-core 模块是 Spring 框架的核心模块,包含控制反转(Inversion of Control, IoC)和依赖注入(Dependency Injection, DI),核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,工厂模式的实现。BeanFactory 使用控制反转(IOC) 思想将应用程序的配置和依赖性规范与实际的应用程序代码分开。
    • Spring 上下文Spring Context:Spring 上下文是一个配置文件,向 Spring 框架提供 上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
    • Spring-Expression 模块是统一表达式语言(unified EL)的扩展模块,可以查询、 管理运行中的对象,同时也方便的可以调用对象方法、操作数组、集合等。它的 语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串 的模板函数。
  • Spring-AOP:Spring-aop是Spring的另一个核心模块, 在Spring中,他是以JVM的动态代理技术为基础,然后设计出了一系列的Aop横切实现,比如前置通知、返回通知、异常通知等。通过其配置管理特性,Spring AOP 模块直接将面向切面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。

  • Spring Data Access(数据访问):由Spring-jdbc、Spring-tx、Spring-orm、Spring-jms和Spring-oxm 5个模块组成 。

    • Spring-jdbc 模块是 Spring 提供的JDBC抽象框架的主要实现模块,用于简化 Spring JDBC。
    • Spring-tx 模块是SpringJDBC事务控制实现模块。使用Spring框架,它对事务做了很好的封装,通过它的Aop配置,可以灵活的配置在任何一层。
    • Spring-Orm 模块是ORM框架支持模块,主要集成 hibernate,Java Persistence API (JPA) 和 Java Data Objects (JDO) 用于资源管理、数据访问对象(DAO)的实现和事务策略。
    • Spring-Jms 模块(Java Messaging Service)能够发送和接受信息。
    • Spring-Oxm 模块主要提供一个抽象层以支撑OXM(OXM 是Object-to-XML-Mapping 的缩写,它是一个O/M-mapper,将java对象映射成 XML 数据,或者将 XML 数据映射成 java 对象),例如:JAXB, Castor, XMLBeans, JiBX 和 XStream 等。
  • Web 模块:由Spring-web、Spring-webmvc、Springwebsocket和Spring-webmvc-portlet 4个模块组成,Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

  • 报文发送:即Spring-messaging模块。

    Spring-messaging是Spring4 新加入的一个模块,主要职责是为Spring 框架集成一些基础的报文传送应用。

  • 单元测试:即Spring-test模块。Spring-test模块主要为测试提供支持

Spring 框架环境搭建

环境要求

​ JDK 版本:

​ JDK 1.7 及以上版本

​ Spring版本:

​ Spring 5.x版本

新建Maven项目

  1. 创建 Maven 的普通 Java 项目

    image-20241229151858327

  2. 设置项目的坐标

    image-20241229151917935

  3. 设置项目的 Maven 环境

    image-20241229151939262

    1. 设置项目的名称和存放的工作空间 image-20241229152004788

调整项目环境

  1. 修改 JDK 版本

    <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
  2. 修改单元测试 JUnit 版本

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    
  3. build标签中的pluginManagement标签

    <!--删除build标签中的pluginManagement标签-->
    <build>
    </build>
    

添加 Spring框架的依赖坐标

​ Maven仓库:https://mvnrepository.com/

<!-- 添加Spring框架的核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>

编写 Bean 对象

package com.xxxx.service;
public class UserService {
    public void test(){
        System.out.println("Hello Spring!");
   }
}

添加Spring配置文件

  1. 在项目的src下创建文件夹 resources(Alt+insert)

  2. 将 resources 标记为资源目录

    image-20241229152927790

  3. 在 src\main\resources 目录下新建 spring.xml 文件,并拷贝官网文档提供的模板内容到 xml 中。

    spring.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/
    spring-beans.xsd">
        <!--
            xmlns 即 xml namespace xml使用的命名空间
            xmlns:xsi 即xml schema instance xml 遵守的具体规范
            xsi:schemaLocation 本文档xml遵守的规范 官方指定
       -->
       <bean id="..."class="..."></bean>
    </beans>
    
  4. 在 spring.xml 中配置 Bean 对象

<!--
 id:bean对象的id,唯一标识。一般是Bean对象的名称的首字母小写
 class:bean对象的类路径
-->
<bean id="userService"class="com.xxxx.service.UserService"></bean>

加载配置文件,获取实例化对象

package com.xxxx;
import com.xxxx.service.UserService;
import
org.springframework.context.ApplicationContext;
import
org.springframework.context.support.ClassPathXml
ApplicationContext;
public class App {
    public static void main(String[] args) {
         // 获取Spring上下文环境 (加载配置文件)
        ApplicationContext ac = new
ClassPathXmlApplicationContext("spring.xml");
        // 通过getBean方法得到Spring容器中实例化好的
Bean对象 实例化Bean对象
        // userService代表的是配置文件中bean标签的id
属性值
        UserService userService = (UserService)
ac.getBean("userService");
        // 调用方法 (使用实例化对象)
        userService.test();
   }
}

原理:

 //得到spring的上下文环境。会加载配置文件,解析配置文件,查询配置文件中有多少个bean,并将结果返回给上下文环境ac中
   ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
  //通过id属性值得到指定的bean对象。调用ApplicationContext对象的getBean方法,传入指定id(配置文件中bean标签的id值),通过id可以找到指定的bean,通过bean的claa属性可以找到指定的class类,获取到了class之后,就可以通过class.forName(类路径)反射实例化得到对象
   UserService userService = (UserService) ac.getBean("userService");
  //通过调用实例化好的JavaBean对象中的方法
   userService.test();

Spring IOC容器 Bean对象实例化模拟

思路:

  1. 定义Bean 工厂接口,提供获取bean方法

  2. 定义Bean工厂接口实现类,解析配置文件,实例化Bean对象

  3. 实现获取Bean方法

定义Bean属性对象

package com.xxxx.spring;
/**
* bean对象
*     用来接收配置文件中bean标签的id与class属性值
*/
public class MyBean {
     private String id; // bean对象的id属性值
    private String clazz; // bean对象的类路径
    public MyBean() {
   }
    public MyBean(String id, String clazz) {
        this.id = id;
        this.clazz = clazz;
   }
    public String getId() {
        return id;
   }
    public void setId(String id) {
        this.id = id;
   }
    public String getClazz() {
        return clazz;
   }
    public void setClazz(String clazz) {
        this.clazz = clazz;
   }
}

添加dom4j坐标依赖

<!-- dom4j -->
<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.6.1</version>
</dependency>
<!-- XPath -->
<dependency>
    <groupId>jaxen</groupId>
    <artifactId>jaxen</artifactId>
    <version>1.1.6</version>
</dependency>

准备自定义配置文件

spring.xml

<?xml version="1.0" encoding="utf-8" ?>
<beans>
    <bean id="userService"
class="com.xxxx.service.UserService"></bean>
    <bean id="accountService"
class="com.xxxx.service.AccountService"></bean>
</beans>

定义 Bean工厂接口

package com.xxxx.spring;
/**
* Bean 工厂接口定义
*/
public interface MyFactory {
    // 通过id值获取对象
    public Object getBean(String id);
}

定义Bean接口的实现类

package com.xxxx.spring;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 模拟Spring的实现
* 1、通过构造器得到相关配置文件
* 2、通过dom4j解析xml文件,得到List存放id和class
* 3、通过反射实例化得到对象   Class.forName(类的全
路径).newInstance(); 通过Map<id,Class>存储
* 4、得到指定的实例化对象
*/
public class MyClassPathXmlApplicationContext
implements BeanFactory {
    private Map beans = new HashMap(); // 实例化后的对象放入map
    private List<MyBean> myBeans; // 存放已读取bean 配置信息

    /* 1、通过构造器得到相关配置文件 */
    public MyClassPathXmlApplicationContext(String fileName) {

        /* 2、通过dom4j解析xml文件,得到List (存放id和class) */
        this.parseXml(fileName);

        /* 3、通过反射实例化得到对象Class.forName(类路径).newInstance(); 通过Map存储 */
        this.instanceBean();

   }

    /**
     * 通过dom4j解析xml文件,得到List   存放id和class
     * 1、获取解析器
     * 2、得到配置文件的URL
     * 3、通过解析器解析xml文件(spring.xml)
     * 4、通过xpath语法,获取beans标签下的所有bean标签
     * 5、通过指定语法解析文档对象,返回集合
     * 6、判断集合是否为空,遍历集合
     * 7、获取标签元素中的属性
     * 8、得到Bean对象,将Bean对象设置到集合中
     * @param fileName
     */
     private void parseXml(String fileName) {
        // 1、获取解析器
        SAXReader reader = new SAXReader();
        // 2、得到配置文件的URL
        URL url =this.getClass().getClassLoader().getResource(fileName);
        try {
            // 3、通过解析器解析xml文件(spring.xml)
            Document document =reader.read(url);
            // 4、通过xpath语法,获取beans标签下的所有bean标签
            XPath xPath =document.createXPath("beans/bean");
            // 通过指定语法解析文档对象,返回集合
            List<Element> list =xPath.selectNodes(document);
            // 判断集合是否为空,遍历集合
            if (list != null && list.size() >0) {
                myBeans = new ArrayList<>();
                for(Element el : list) {
                    // 获取标签元素中的属性
                    String id =el.attributeValue("id"); // id 属性值
                    String clazz =el.attributeValue("class"); // class属性值
                  System.out.println(el.attributeValue("id"));
                  System.out.println(el.attributeValue("class"));
                    // 得到Bean对象
                    MyBean bean = newMyBean(id, clazz);
                    // 将Bean对象设置到集合中
                    myBeans.add(bean);
               }
           }
       } catch (DocumentException e) {
            e.printStackTrace();
       }
   }

    /**
     * 通过反射实例化得到对象  
     * Class.forName(类的全路径).newInstance();
     * 通过Map<id,Class>存储
     */
    private void instanceBean() {
        // 判断bean集合是否为空,不为空遍历得到对应Bean对象
        if (myBeans != null && myBeans.size() >0) {
            for (MyBean bean : myBeans){      
                               
                try {
                    // 通过类的全路径实例化对象
                    Object object =Class.forName(bean.getClazz()).newInstance();
                    // 将id与实例化对象设置到map对象中
                    beans.put(bean.getId(),object);
               } catch (Exception e) {
                    e.printStackTrace();
               }
           }
       }
   }
     /**
     * 通过key获取map中的指定value
     * @param id
     * @return
     */
    @Override
    public Object getBean(String id) {
        Object object = beans.get(id);
        return object;
   }
}

测试自定义IOC容器

  1. 创建与配置文件中对应的Bean对象

    UserService.java

    package com.xxxx.service;
    public class UserService {
        public void test(){
            System.out.println("UserService
    Test...");
       }
    }
    

    AccountService.java

    package com.xxxx.service;
    
    public class AccountService {
    
        public void test(){
            System.out.println("AccountService Test...");
       }
     }
    
  2. 测试是否可以获取实例化的Bean对象

    package com.xxxx;
    
    import com.xxxx.spring.MyFactory;
    import com.xxxx.spring.MyClassPathXmlApplicationContext;
    import com.xxxx.service.AccountService;
    import com.xxxx.service.UserService;
    
    public class App {
        
        public static void main(String[] args) {
            MyFactory factory = new
    MyClassPathXmlApplicationContext("spring.xml");
            // 得到实例化对象
            UserService userService =(UserService) factory.getBean("userService");
            userService.test();
            UserService userService2 =(UserService) factory.getBean("userService");
            System.out.println(userService+"====="+ userService2);
             AccountService accountService =(AccountService)factory.getBean("accountService");
            accountService.test();
       }
    }
    

    Spring 容器在启动的时候读取xml配置信息,并对配置的 bean进行实例化(这里模拟的比较简单,仅用于帮助大家理解),同时通过上下文对象提供的getBean() 方法拿到我们配置的bean 对象,从而实现外部容器自动化维护并创建 bean 的效果。

Spring IOC配置文件加载

Spring配置文件加载(单个文件)

spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">   
  <bean id="userService"class="com.xxxx.service.UserService"></bean>
</beans>

根据相对路径加载资源

ApplicationContext ac  = new
ClassPathXmlApplicationContext("spring.xml");

根据绝对路径加载资源

ApplicationContext ac = new
FileSystemXmlApplicationContext("C:/IdeaWorkspace/spring01/src/main/resources/spring.xml");  

Spring多配置文件加载

​ Spring 框架启动时可以加载多个配置文件到环境中。对于比较复杂的项目,可能对应的配置文件有多个,项目在启动部署时会将多个配置文件同时加载进来。

service.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean id="userService"class="com.xxxx.service.UserService"></bean>
</beans>

dao.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean id="userDao"class="com.xxxx.dao.UserDao"></bean>
</beans>

可变参数,传入多个文件名

// 同时加载多个资源文件
ApplicationContext ac = new
ClassPathXmlApplicationContext("spring.xml","dao.xml");

通过总的配置文件import其他配置文件

​ spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      
https://www.springframework.org/schema/beans/sp
ring-beans.xsd">
    
    <!--导入需要包含的资源文件-->
    <import resource="service.xml"/>
    <import resource="dao.xml"/>
</beans>

​ 加载时只需加载总的配置文件即可

// 加载总的资源文件
ApplicationContext ac = new
ClassPathXmlApplicationContext("spring.xml");

案例:

​ ![屏幕截图 2024-12-28 160047](index.assets/屏幕截图 2024-12-28 160047.png)

Spring IOC容器 Bean对象实例化

注意:

​ Spring IOC 容器 Bean 对象实例化获取的都是单例。

构造器实例化(默认)

注:

​ 通过默认构造器创建 空构造方法必须存在 否则创建失败

  1. 设置配置文件 spring.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans
    	xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
          https://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="userService"class="com.xxxx.service.UserService"></bean>
    </beans>
    
  2. 获取实例化对象

    ApplicationContext ac = new
    ClassPathXmlApplicationContext("spring.xml");
    UserService userService = (UserService)
    ac.getBean("userService");  
    userService.test();
    

    案例:

    ![屏幕截图 2024-12-28 170536](index.assets/屏幕截图 2024-12-28 170536.png)

    ![屏幕截图 2024-12-28 161635](index.assets/屏幕截图 2024-12-28 161635.png)

    ![屏幕截图 2024-12-28 165459](index.assets/屏幕截图 2024-12-28 165459.png)

静态工厂实例化

注意:

​ 1. 要有该工厂类及工厂方法

​ 2. 工厂方法为静态的

  1. 定义静态工厂类

    package com.xxxx.factory;
    
    import com.msb.xxxx.UserService;
    
    /**
     * 定义静态工厂类
     */
    public class StaticFactory {
    
        /**
         * 定义对应的静态方法
         * @return
         */
        public static UserService createService() {
    
    
            return new UserService();
        }
    
    }
    
  2. 设置配置文件 spring.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/
    spring-beans.xsd">
        <!--静态工厂-->
        <bean id="userService"class="com.xxxx.factory.StaticFactory"
    factory-method="createUserService"></bean>
    </beans>
    
  3. 获取实例化对象

ApplicationContext ac = new
ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService)
ac.getBean("userService");  
userService.test();

当我们指定Spring使用静态工厂方法来创建Bean实例时,Spring将先解析配置文件,并根据配置文件指定的信息,通过反射调用静态工厂类的静态工厂方法,并将该静态工厂方法的返回值作为Bean实例,在这个过程中,Spring不再负责创建Bean实例,Bean实例是由用户提供的静态工厂方法提供的。

案例:

![屏幕截图 2024-12-28 170225](index.assets/屏幕截图 2024-12-28 170225.png)

![屏幕截图 2024-12-28 170342](index.assets/屏幕截图 2024-12-28 170342.png)

![屏幕截图 2024-12-28 170404](index.assets/屏幕截图 2024-12-28 170404.png)

![屏幕截图 2024-12-28 170509](index.assets/屏幕截图 2024-12-28 170509.png)

实例化工厂实例化

注意:

​ 1. 工厂方法为非静态方法

​ 2. 需要配置工厂bean,并在业务bean中配置factory-bean,factory-method属性

  1. 定义工厂类

    package com.xxxx.factory;
    import com.xxxx.service.UserService;
    /**
    * 定义工厂类
    */
    public class InstanceFactory {
        /**
         * 定义方法,返回实例化对象
         * @return
         */
        public UserService createUserService() {
            return new UserService();
       }
    }
    
  2. 设置配置文件 spring.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     
    xsi:schemaLocation="http://www.springframework.org/schema/beans      https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--
     实例化工厂
       1.定义实例化工厂bean
       2.引用工厂bean 指定工厂创建方法(方法为非静态)
     -->
        <bean id="instanceFactory"
    class="com.xxxx.factory.InstanceFactory">
    </bean>
        <bean id="userService" factory-bean="instanceFactory" factory-method="createUserService"></bean>
    </beans>  
    
  3. 获取实例化对象

    ApplicationContext ac = new
    ClassPathXmlApplicationContext("spring.xml");
    UserService userService = (UserService)
    ac.getBean("userService");  
    userService.test();
    

​ 案例:

![屏幕截图 2024-12-28 170148](index.assets/屏幕截图 2024-12-28 170148.png)

![屏幕截图 2024-12-28 171316](index.assets/屏幕截图 2024-12-28 171316.png)

![屏幕截图 2024-12-28 171356](index.assets/屏幕截图 2024-12-28 171356.png)

![屏幕截图 2024-12-28 171634](index.assets/屏幕截图 2024-12-28 171634.png)

Spring三种实例化Bean的方式比较

  • 方式一:通过bean的缺省构造函数创建,当各个bean的业务逻辑相互比较独立的时候或者和外界关联较少的时候可以使用。

  • 方式二:利用静态factory方法创建,可以统一管理各个bean的创建,如各个bean在创建之前需要相同的初始化处理,则可用这个factory方法险进行统一的处理等等。

  • 方式三:利用实例化factory方法创建,即将factory方法也作为了业务bean来控制,1. 可用于集成其他框架的bean创建管理方法,2. 能够使bean和factory的角色互换

开发中项目一般使用一种方式实例化bean,项目开发基本采用第一种方式,交给Spring托管,使用时直接拿来使用即可。另外两种了解

Spring IOC注入(DI)

手动实例化与外部引入

图一:

![屏幕截图 2024-12-28 172353](index.assets/屏幕截图 2024-12-28 172353.png)

图二:

![屏幕截图 2024-12-28 172420](index.assets/屏幕截图 2024-12-28 172420.png)

​ 对比发现:图二中对于 UserDao 对象的创建并没有像图一那样主动的去实例化,而是通过带参方法形式将UserDao 传入过来,从而实现 UserService 对UserDao类 的依赖。

而实际创建对象的幕后对象即是交给了外部来创建。

Spring IOC手动装配(注入)

​ Spring 支持的手动注入方式共有四种:set 注入、构造器注入、静态工厂注入、实例化工厂注入。

set方法注入

注意:

​ 1.属性字段需要提供set方法

​ 2.四种方式,推荐使用set方法注入

set方法注入	
	通过property属性注入
        name:bean对象中属性字段的名称
        ref:指定bean标签的id属性值 (注入JavaBean)
        value:具体的值 (基本类型 常用对象|日期  集合)

原理:
	在有两个beam的情况下,第一个bean想用第二个bean时,需要将第二个bean对象注入到第一个bean对象中。
	a. 此时在第一个bean中定义一个成员属性。
	b. 通过set方法给set一下,属性字段提供set方法
	c. 在配置文件中,通过property标签属性将第二个bean配置进去.在配置文件中通过property属性指定属性字段
	d. 这个时候,ioc容器在实例化标签时,首先第一步会将两个bean对象实例化一下,第二步会去找当前容器中名字为userDao的一个对象,然后将该对象进行注入到第一个bean对象中。		  		
业务对象JavaBean
  1. 属性字段提供set方法

    public class UserService {
        // 业务对象UserDao set注入(提供set方法)
        private UserDao userDao;
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
       }
    }
    
  2. 配置文件的bean标签设置property标签

    <?xml version="1.0" encoding="UTF-8"?>
    <beans
    xmlns="http://www.springframework.org/schema/b
    eans"
          
    xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance"
          
    xsi:schemaLocation="http://www.springframework
    .org/schema/beans
          
    https://www.springframework.org/schema/beans/
    spring-beans.xsd">
        
       <!--
            IOC通过property标签手动装配(注入):
                Set方法注入
                    name:bean对象中属性字段的名称
                    ref:指定bean标签的id属性值
        -->
     <bean id="userDao" class="com.xxxx.dao.UserDao"></bean>
     <bean id="userService" class="com.xxxx.service.UserService">
            <!--业务对象 注入-->
          <property name="userDao"ref="userDao"/>
      </bean>
    </beans>
    
常用对象和基本类型
  1. 属性字段提供set方法

    public class UserService {
        // 常用对象String set注入(提供set方法)
        private String host;
        public void setHost(String host) {
            this.host = host;
       }
        // 基本类型Integer   set注入(提供set方法)
        private Integer port;
        public void setPort(Integer port) {
            this.port = port;
       }
    }
    
  2. 配置文件的bean标签设置property标签

    <?xml version="1.0" encoding="UTF-8"?>
    <beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/
    spring-beans.xsd">
        
       <!--
            IOC通过property标签手动装配(注入):
                Set方法注入
                    name:bean对象中属性字段的名称
                    value:具体的值(基本类型 常用对象|日期 集合)
        -->
     <bean id="userService"class="com.xxxx.service.UserService">
            <!--常用对象String 注入-->
            <property name="host"value="127.0.0.1"/>
            <!--基本类型注入-->
            <property name="port" value="8080"/>
        </bean>
    </beans>                 
    
集合类型和属性对象
  1. 属性字段提供set方法

    public class UserService {
        // List集合 set注入(提供set方法)
        public List<String> list;
        public void setList(List<String> list) {
            this.list = list;
       }
      
        // Set集合 set注入(提供set方法)
         private Set<String> set;
        public void setSet(Set<String> set) {
            this.set = set;
       }
    
    
        // Map set注入(提供set方法)
        private Map<String,Object> map;
        public void setMap(Map<String, Object>map) {
            this.map = map;
       }
        
    
        // Properties set注入(提供set方法)
        private Properties properties;
        public void setProperties(Properties properties) {
            this.properties = properties;
       }
      
     }
    
  2. 配置文件的bean标签设置property标签

    <?xml version="1.0" encoding="UTF-8"?>
     <beans
    xmlns="http://www.springframework.org/schema/beans"      
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">
        
       <!--
            IOC通过property标签手动装配(注入):
                Set方法注入
                    name:bean对象中属性字段的名称
                    value:具体的值(基本类型 常用对象|日期 集合)
        -->
     <!--List集合 注入-->
        <property name="list">
            <list>
                <value>上海</value>
                <value>北京</value>
                <value>杭州</value>
            </list>
        </property>
        <!--Set集合注入-->
        <property name="set">
            <set>
                <value>上海SH</value>
                <value>北京BJ</value>
                <value>杭州HZ</value>
            </set>
        </property>
        <!--Map注入-->
        <property name="map">
            <map>
                <entry>
                    <key><value>周杰伦</value></key>
                    <value>我是如此相信</value>
                </entry>       
                 <entry>
                    <key><value>林俊杰</value>
    </key>
                    <value>可惜没如果</value>
                </entry>
                <entry>
                    <key><value>陈奕迅</value>
    </key>
                    <value>十年</value>
                </entry>
            </map>
        </property>
        <!--Properties注入-->
        <property name="properties">
            <props>
                <prop key="上海">东方明珠</prop>
                <prop key="北京">天安门</prop>
                <prop key="杭州">西湖</prop>
            </props>
        </property>
    </beans>
    
测试代码

UserService.java

public class UserService {
    // 业务对象UserDao set注入(提供set方法)
    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
   }
    // 常用对象String set注入(提供set方法)
     private String host;
    public void setHost(String host) {
        this.host = host;
   }

    // 基本类型Integer   set注入(提供set方法)
    private Integer port;
    public void setPort(Integer port) {
        this.port = port;
   }

    // List集合 set注入(提供set方法)
    public List<String> list;
    public void setList(List<String> list) {
        this.list = list;
   }
    // List集合输出
    public void printList() {
        list.forEach(s ->System.out.println(s));
   }

    // Set集合 set注入(提供set方法)
    private Set<String> set;
    public void setSet(Set<String> set) {
        this.set = set;
   }
    // Set集合输出
    public void printSet() {
        set.forEach(s -> System.out.println(s));
   }


    // Map set注入(提供set方法)
    private Map<String,Object> map;
    public void setMap(Map<String, Object> map){
    	this.map = map;
   }
    // Map输出
    public void printMap() {
        map.forEach((k,v) ->System.out.println(k + "," + v));
   }

    // Properties set注入(提供set方法)
    private Properties properties;
    public void setProperties(Properties properties) {
        this.properties = properties;
   }
    // Properties输出
    public  void printProperties(){
       properties.forEach((k,v) ->System.out.println(k + ","+ v ));
   }

    public  void  test(){
        System.out.println("UserService Test...");
        userDao.test();
        studentDao.test();
        System.out.println("Host:" + host  + ",port:" + port);

        // List集合
        printList();
        // Set集合
        printSet();
        // Map
        printMap();
        // Properties
        printProperties();
   }
}                                                

spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/bea
ns"
      
xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance"
      
xsi:schemaLocation="http://www.springframework.o
rg/schema/beans
      
https://www.springframework.org/schema/beans/sp
ring-beans.xsd">
    
    <!--
        IOC通过property标签手动装配(注入):
            Set方法注入
                name:bean对象中属性字段的名称
                ref:指定bean标签的id属性值
                value:具体的值(基本类型 常用对象|日期 集合)
    -->
 <bean id="userDao" class="com.xxxx.dao.UserDao"></bean>
 <bean id="userService" class="com.xxxx.service.UserService">
    <!--业务对象 注入-->
      <property name="userDao"ref="userDao"/>
 	 <property name="studentDao" ref="studentDao"/>
     
     <!--常用对象String 注入-->
        <property name="host"value="127.0.0.1"/>
        <!--基本类型注入-->
        <property name="port" value="8080"/>
    
    <!--List集合 注入-->
    <property name="list">
        <list>
            <value>上海</value>
            <value>北京</value>
            <value>杭州</value>
        </list>
    </property>
    <!--Set集合注入-->
    <property name="set">
        <set>
            <value>上海SH</value>
            <value>北京BJ</value>
            <value>杭州HZ</value>
        </set>
    </property>
    <!--Map注入-->
    <property name="map">
        <map>
            <entry>
                <key><value>周杰伦</value></key>
                <value>我是如此相信</value>
            </entry>       
             <entry>
                <key><value>林俊杰</value></key>
                <value>可惜没如果</value>
            </entry>
            <entry>
                <key><value>陈奕迅</value></key>
                <value>十年</value>
            </entry>
        </map>
    </property>
    <!--Properties注入-->
    <property name="properties">
        <props>
            <prop key="上海">东方明珠</prop>
            <prop key="北京">天安门</prop>
            <prop key="杭州">西湖</prop>
        </props>
    </property>

构造器注入

注意:

​ 提供带参构造器

构造器注入
    设置构造器所需要的的参数
    通过constructor-arg标签设置构造器的参数
        name:属性名称
        ref:要注入的bean对象对应的bean标签的id属性值
        value:数据具体的值
        index:参数的位置(从0开始)

	原理:
		当存在A,B两个bean对象时,A的对象想注入B的对象,容器想要实例化A对象,但是A中想要拿到B对象,实例化A时会调用构造器,构造器中会有一个参数(该参数是B的实例化对象),所以IOC容器的第一步不是实例化A,而是先实例化B,B实例化好了之后才会实例化A。
单个Bean对象作为参数

Java 代码

public class UserService {
    private UserDao userDao; // JavaBean 对象
    
    public UserService(UserDao userDao) {
        this.userDao = userDao;
   }
    public  void  test(){
        System.out.println("UserService Test...");
        userDao.test();
   }
}

xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"    
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     
xsi:schemaLocation="http://www.springframework.org/schema/beans     
https://www.springframework.org/schema/beans/spring-beans.xsd">
 <!--
	IOC通过构造器注入:
            通过constructor-arg标签进行注入
                name:属性名称
                ref:指定bean标签的id属性值
    -->
    <bean id="userDao"class="com.xxxx.dao.UserDao" ></bean> 
    <bean id="userService"class="com.xxxx.service.UserService">
       <constructor-arg name="userDao"ref="userDao"></constructor-arg>
    </bean>
</beans>
多个Bean对象作为参数

java代码

public class UserService {
    private UserDao userDao;  // JavaBean 对象
    private AccountDao accountDao  // JavaBean 对
        
    public UserService(UserDao userDao,
AccountDao accountDao) {
        this.userDao = userDao;
        this.accountDao = accountDao;
   }
    public  void  test(){
        System.out.println("UserService Test...");
        userDao.test();
        accountDao.test();
   }
}

XML配置

<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">
 <!--
        IOC通过构造器注入:
            通过constructor-arg标签进行注入
                name:属性名称
                ref:指定bean标签的id属性值
    -->
    <bean id="userDao"class="com.xxxx.dao.UserDao" ></bean>
    <bean id="accountDao"class="com.xxxx.dao.AccountDao" ></bean>    
    <bean id="userService"class="com.xxxx.service.UserService">
       <constructor-arg name="userDao"ref="userDao"></constructor-arg>
       <constructor-arg name="accountDao"ref="accountDao">					</constructor-arg>
    </bean>
</beans>
Bean对象和常用对象作为参数

Java 代码

public class UserService {
    private UserDao userDao;  // JavaBean 对象
    private AccountDao accountDao;  // JavaBean对象
    private String uname;  // 字符串类型
        
    public UserService(UserDao userDao,AccountDao accountDao, String uname) {
        this.userDao = userDao;
        this.accountDao = accountDao;
        this.uname = uname;
   }
    public  void  test(){
        System.out.println("UserService Test...");
        userDao.test();
        accountDao.test();
        System.out.println("uname:" + uname);
   }
 }

XML配置

<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
  https://www.springframework.org/schema/beans/spring-beans.xsd">
 <!--
        IOC通过构造器注入:
            通过constructor-arg标签进行注入
                name:属性名称
                ref:指定bean标签的id属性值
 				value:基本类型 常用对象的值
                index:构造器中参数的下标,从0开始
    -->
    <bean id="userDao"class="com.xxxx.dao.UserDao" ></bean>
    <bean id="accountDao"class="com.xxxx.dao.AccountDao" ></bean>
    <bean id="userService"class="com.xxxx.service.UserService">
       <constructor-arg name="userDao"ref="userDao"></constructor-arg>
       <constructor-arg name="accountDao"ref="accountDao">					</constructor-arg>
        <constructor-arg name="uname"value="admin"></constructor-arg>
    </bean>
</beans>
循环依赖问题

循环问题产生的原因:

​ Bean通过构造器注入,之间彼此相互依赖对方导致bean无法实例化。

问题展示:

  1. Java 代码

    public class AccountService {
        private RoleService roleService;
       public AccountService(RoleService roleService) {
            this.roleService = roleService;
       }
        public void  test() {
            System.out.println("AccountService Test...");
       }
    }
    public class RoleService {
        private AccountService accountService;
        public RoleService(AccountService accountService) {
            this.accountService = accountService;
       }
    
        public void  test() {
            System.out.println("RoleService Test...");
       }
     }
    
  2. XML配置

    <!--
         如果多个bean对象中互相注入,则会出现循环依赖的问题
         可以通过set方法注入解决
     -->
     <bean id="accountService"class="com.xxxx.service.AccountService">
        <constructor-arg name="roleService"ref="roleService"/>
     </bean>
    
     <bean id="roleService"class="com.xxxx.service.RoleService">
        <constructor-arg name="accountService"ref="accountService"/>
     </bean>
    

解决方案:将构造器注入改为set方法注入

  1. Java代码

    public class AccountService {
         private RoleService roleService;
    
       /* public AccountService(RoleService roleService) {
            this.roleService = roleService;
        }*/
    
        public void setRoleService(RoleService roleService) {
            this.roleService = roleService;
       }
    
        public void  test() {
            System.out.println("AccountService Test...");
       }
     }
    
     public class RoleService {
    
        private AccountService accountService;
    
       /* public RoleService(AccountService accountService) {
            this.accountService = accountService;
        }*/
    
        public void setAccountService(AccountService accountService) {
            this.accountService = accountService;
       }
    
        public void  test() {
            System.out.println("RoleService Test...");
       }
     }         
    
  2. XML配置

    <!--
     <bean id="accountService"
    class="com.xxxx.service.AccountService">
        <constructor-arg name="roleService"
    ref="roleService"/>
        </bean>
        <bean id="roleService"
    class="com.xxxx.service.RoleService">
            <constructor-arg name="accountService"
    ref="accountService"/>
        </bean>
    -->
    <!--修改为set方法注入-->
    <bean id="accountService"class="com.xxxx.service.AccountService">
        <property name="roleService"ref="roleService"/>
    </bean>
    <bean id="roleService"class="com.xxxx.service.RoleService">
        <property name="accountService"ref="accountService"/>
    </bean>
    

静态工厂注入

​ 静态工厂注入也是借助set方法注入,只是被注入的bean对象的实例化是通过静态工厂实例化的

  1. 定义静态工厂类

    public class StaticFactory {
    
        // 定义静态方法
        public static TypeDao createTypeDao() {
            return new TypeDao();
       }
     }
    
  2. Java代码

    public class TypeService {
    
        private TypeDao typeDao;
    
        public void setTypeDao(TypeDao typeDao) {
            this.typeDao = typeDao;
       }
    
        public void  test() {
            System.out.println("TypeService Test...");
       }
     }
    
  3. 在配置文件中设置bean标签,指定工厂对象并设置对应的方法

    <bean id="typeService"
    class="com.xxxx.service.TypeService">
     <property name="typeDao" ref="typeDao"/>
    </bean>
    <!--
     静态工厂注入:
     	静态工厂注入也是借助set方法注入,只是被注入的bean对象的实例化是通过静态工厂实例化的
    -->
    <bean id="typeDao"class="com.xxxx.factory.StaticFactory" factory-method="createTypeDao"></bean>
    

实例化工厂注入

  1. 定义工厂类

    public class InstanceFactory {
         public TypeDao createTypeDao() {
            return new TypeDao();
       }
    }
    
  2. Java代码

    public class TypeService {
    
        private TypeDao typeDao;
    
        public void setTypeDao(TypeDao typeDao) {
            this.typeDao = typeDao;
       }
    
        public void  test() {
            System.out.println("TypeService Test...");
       }
     }
    
  3. xml配置

    <bean id="typeService"class="com.xxxx.service.TypeService">
     <property name="typeDao" ref="typeDao"/>
     </bean>
     <!--
     	实例化工厂注入:
     		实例化工厂注入也是借助set方法注入,只是被注入的bean对象的实例化是通过实例化工厂实例化的
     -->
     <bean id="instanceFactory"
    class="com.xxxx.factory.InstanceFactory">
    </bean>
     <bean id="typeDao" factory-bean="instanceFactory" factory-method="createTypeDao"></bean>
    

    重点掌握set注入和构造器注入,工厂方式了解即可。实际开发中基本使用set方式注入bean。

注入方式的选择

开发项目中set方式注入首选

​ 使用构造注入可以在构建对象的同时一并完成依赖关系的建立,对象一建立则所有的一切也就准备好了,但如果要建立的对象关系很多,使用构造器注入会在构建函数上留下一长串的参数,且不易记忆,这时使用Set注入会是个不错的选择。

​ 使用Set注入可以有明确的名称,可以了解注入的对象会是什么,像setXXX()这样的名称会比记忆Constructor上某个参数的位置代表某个对象更好。

p名称空间的使用

​ spring2.5以后,为了简化setter方法属性注入,引用p名称空间的概念,可以将子元素,简化为元素属性配置。

  1. 属性字段提供 set 方法

    public class UserService {
        // 业务对象UserDao set注入(提供set方法)
        private UserDao userDao;
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
       }
        
        // 常用对象String set注入(提供set方法)
        private String host;
        public void setHost(String host) {
            this.host = host;
       }
    }
    
  2. 在配置文件 spring.xml 引入 p 名称空间

    xmlns:p="http://www.springframework.org/schema/p"
    
    <?xml version="1.0" encoding="UTF-8"?>
    <beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">    
     <bean id="userDao"class="com.xxxx.dao.UserDao"></bean>
        <!--
             p:属性名:="xxx" 引入常量值
             p:属性名-ref:="xxx" 引入其他Bean对象的id属性值
     -->
     <bean id="userService"class="com.xxxx.service.UserService"
            p:userDao-ref="userDao"
            p:host="127.0.0.1" />
    </beans>
    

Spring IOC自动装配(注入)

注解方式注入 Bean

​ 对于 bean 的注入,除了使用 xml 配置以外,可以使用注解配置。注解的配置,可以简化配置文件,提高开发的速度,使程序看上去更简洁。对于注解的解释,Spring对于注解有专门的解释器,对定义的注解进行解析,实现对应bean对象的注入。通过反射技术实现

准备环境

  1. 修改配置文件

    <beans
    xmlns="http://www.springframework.org/schema/beans"      
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     
    xmlns:context="http://www.springframework.org/schema/context"     
    xsi:schemaLocation="http://www.springframework.org/schema/beans      https://www.springframework.org/schema/beans/spring-beans.xsd     	http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    
  2. 开启自动化注入

    <!--开启自动化装配(注入)-->
    <context:annotation-config/>
    <bean id="userDao"
    class="com.xxxx.dao.UserDao"></bean>
    <bean id="userService"
    class="com.xxxx.service.UserService"></bean>
    
  3. 给注入的bean对象添加注解

@Resource注解

@Resource注解实现自动注入(反射)

  • 默认根据属性字段名称查找对应的bean对象 (属性字段的名称与bean标签的id属性值相等)

  • 如果属性字段名称未找到,则会通过类型(Class类型)查找属性

  • 可以提供set方法,也可以不提供set方法

  • 注解可以声明在属性级别 或 set方法级别

  • 可以设置name属性,name属性值必须与bean标签的id属性值一致;如果设置了name属性值,就只会按照name属性值查找bean对象

  • 当注入接口时,如果接口只有一个实现则正常实例化;如果接口存在多个实现,则需要使用name属性指定需要被实例化的bean对象

代码示例

  1. 默认根据属性字段名称查找对应的bean对象 (属性字段的名称与bean标签的id属性值相等)

    /**
    * @Resource注解实现自动注入(反射)
    * 默认根据属性字段名称查找对应的bean对象 (属性字段
    的名称与bean标签的id属性值相等)
    */
     public class UserService {
    
        @Resource
        private UserDao userDao; // 属性字段的名称与bean标签的id属性值相等
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
       }
    
        public void test() {
            // 调用UserDao的方法
            userDao.test();
       }
     }
    
  2. 如果属性字段名称未找到,则会通过类型(Class类型)查找

    /**
    2 * @Resource注解实现自动注入(反射)
    3 *   如果属性字段名称未找到,则会通过类型(Class类型)查找
    4 */
     public class UserService {
    
        @Resource
        private UserDao ud; // 当在配置文件中属性字段名(ud)未找到,则会查找对应的class(UserDao类型)
    
        public void setUd(UserDao ud) {
            this.ud = ud;
       }
    
        public void test() {
            // 调用UserDao的方法
            ud.test();
       }
    }     
    
  3. 属性可以提供set方法,也可以不提供set方法

     /**
     * @Resource注解实现自动注入(反射)
     *   属性可以提供set方法,也可以不提供set方法
     */
     public class UserService {
    
        @Resource
        private UserDao userDao; // 不提供set方法
    
        public void test() {
            // 调用UserDao的方法
            userDao.test();
       }
     }
    
  4. 注解可以声明在属性级别 或 set方法级别

    /**
     * @Resource注解实现自动注入(反射)
     *   注解可以声明在属性级别 或 set方法级别
     */
     public class UserService {
    
        private UserDao userDao;
    
        @Resource // 注解也可设置在set方法上
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
       }
    
        public void test() {
         // 调用UserDao的方法
            userDao.test();
       }
     }    
    
  5. 可以设置name属性,name属性值必须与bean标签的id属性值一致;如果设置了name属性值,就只会按照name属性值查找bean对象

    /**
     * @Resource注解实现自动注入(反射)
     *   可以设置name属性,name属性值必须与bean的id属性值一致;
     *   如果设置了name属性值,就只会按照name属性值查找bean对象
     */
     public class UserService {
    
        @Resource(name = "userDao") // name属性值与配置文件中bean标签的id属性值一致
        private UserDao ud;
    
    
        public void test() {
            // 调用UserDao的方法
            ud.test();
       }
     }
    
  6. 当注入接口时,如果接口只有一个实现则正常实例化;如果接口存在多个实现,则需要使用name属性指定需要被实例化的bean对象

    定义接口类 IUserDao.java

    package com.xxxx.dao;
    
     /**
     * 定义接口类
     */
     public interface IUserDao {
        public void test();
     }
    

    定义接口实现类 UserDao01.java

    package com.xxxx.dao;
    
     /**
     * 接口实现类
     */
     public class UserDao01 implements IUserDao {
    
        @Override
        public void test(){
            System.out.println("UserDao01...");
       }
     }
    

    定义接口实现类 UserDao02.java

    package com.xxxx.dao;
    
     /**
     * 接口实现类
     */
     public class UserDao02 implements IUserDao {
    
        @Override
        public void test(){
            System.out.println("UserDao02...");
       }
     }
    

    XML配置文件

    <!--开启自动化装配(注入)-->
     <context:annotation-config/>
    
     <bean id="userService"class="com.xxxx.service.UserService"></bean>
    
     <bean id="userDao01"class="com.xxxx.dao.UserDao01"></bean>
     <bean id="userDao02"class="com.xxxx.dao.UserDao01"></bean>
    

    使用注解 UserService.java

    /**
     * @Resource注解实现自动注入(反射)
     *   当注入接口时,如果接口只有一个实现则正常实例化;如果接口存在多个实现,则需要使用name属性指定需要被实例化的bean对象
     */
     public class UserService {
        @Resource(name = "userDao01") // name属性值与其中一个实现类的bean标签的id属性值一致
        private IUserDao iUserDao; // 注入接口(接口存在多个实现)
        public void test() {
            iUserDao.test();
       }
    } 
    

@Autowired注解

@Autowired注解实现自动化注入:

  • 默认通过类型(Class类型)查找bean对象与属性字段的名称无关

  • 属性可以提供set方法,也可以不提供set方法

  • 注解可以声明在属性级别 或 set方法级别

  • 可以添加@Qualifier结合使用,通过value属性值查找bean对象(value属性值必须要设置,且值要与bean标签的id属性值对应)

  1. 默认通过类型(Class类型)查找bean对象 与属性字段的名称无关

    /**
    * @Autowired注解实现自动化注入
    * 默认通过类型(Class类型)查找bean对象   与属性字
    段的名称无关
    */
    public class UserService {
        @Autowired
        private UserDao userDao; // 默认通过类型(Class类型)查找bean对象与属性字段的名称无关
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
       }
    
        public void test() {
            // 调用UserDao的方法
            userDao.test();
       }
     }
    
  2. 属性可以提供set方法,也可以不提供set方法

    /**
     * @Autowired注解实现自动化注入
     * 属性可以提供set方法,也可以不提供set方法
     */
     public class UserService {
    
        @Autowired
        private UserDao userDao; // 不提供set方法
    
        public void test() {
            // 调用UserDao的方法
            userDao.test();
       }
     }
    
  3. 注解可以声明在属性级别 或 set方法级别

    /**
     * @Autowired注解实现自动化注入
     * 注解可以声明在属性级别 或 set方法级别
     */
     public class UserService {
        private UserDao userDao;
    
        @Autowired// 注解可以声明在set方法级别
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
       }
    
        public void test() {
            // 调用UserDao的方法
            userDao.test();
       }
     }
    
  4. 可以添加@Qualifier结合使用,通过value属性值查找bean对象(value属性值必须要设置,且值要与bean标签的id属性值对应)

    /**
     * @Autowired注解实现自动化注入
     * 可以添加@Qualifier结合使用,通过value属性值查找bean对象
     value属性值必须要设置,且值要与bean标签的id属性值对应
     */
     public class UserService {
    
        @Autowired
        @Qualifier(value="userDao") // value属性值必须要设置,且值要与bean标签的id属性值对应
        private UserDao userDao;
    
        public void test() {
            userDao.test();
       }
     }
    

    推荐使用**@Resource** 注解是属于J2EE的,减少了与Spring的耦合。

Spring IOC扫描器

​ 实际的开发中,bean的数量非常多,采用手动配置bean的方式已无法满足生产需要,Spring这时候同样提供了扫描的方式,对扫描到的bean对象统一进行管理,简化开发配置,提高开发效率。

Spring IOC扫描器的配置

Spring IOC 扫描器
 作用:bean对象统一进行管理,简化开发配置,提高开发效率
   1、设置自动化扫描的范围
        如果bean对象未在指定包范围,即使声明了注解,也无法实例化
   2、在需要被实例化的JavaBean的类上添加指定的注解(注解声明在类级别)  (ben对象的id属性默认是 类的首字母小写)

         Dao层:
             @Repository
         Service层:
             @Service
         Controller层:
             @Controller
         任意类:
             @Component
     注:开发过程中建议按照指定规则声明注解
  1. 设置自动化扫描范围

    <?xml version="1.0" encoding="UTF-8"?>
     <beans
    xmlns="http://www.springframework.org/schema/beans"      
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      
    xmlns:context="http://www.springframework.org/schema/context"     
    xsi:schemaLocation="http://www.springframework.org/schema/beans      https://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context8      
    http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 设置自动化扫描的范围 -->
        <context:component-scan base-package="com.xxxx"/>
    
     </beans>
    
  2. 使用特定的注解

    @Repository (Dao层)

    @Repository
    public class ResourceDao {
        public void  test() {
            System.out.println("ResourceDao...");
       }
    }
    

    @Service(Service层 )

    @Service
    public class ResourceService {
        @Resource
        private ResourceDao resourceDao; //
    service层注入dao层的bean对象
        public  void  test() {
          
    System.out.println("ResourceService...");
            resourceDao.test();
       }
    }
    

    @Controller (Controller 层 )

    @Controller
    public class ResourceController {
        @Autowired
        private ResourceService resourceService;
    // Controller层注入service层的bean对象
        public  void  test() {
          
    System.out.println("ResourceController...");
            resourceService.test();
       }
    }
    

    @Component (任意层)

    @Component
    public class PropertyUtils {
        public void test(){
            System.out.println("PropertyUtils...");
       }
    }
    

Spring模拟用户登录流程

Dao层(查询用户记录)

  1. 定义JavaBean User.java

    package com.xxxx.entity;
    
    /**
     * User实体类
     */
    public class User {
    
        private String userName; // 用户名称
        private String userPwd; // 用户密码
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String getUserPwd() {
            return userPwd;
        }
    
        public void setUserPwd(String userPwd) {
            this.userPwd = userPwd;
        }
    }
    
  2. 编写Dao层 UserDao.java

    package com.xxxx.dao;
    
    import com.xxxx.entity.User;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class UserDao {
    
        // 定义登录的账号密码
        private final String USERNAME = "admin";
        private final String USERPWD = "admin";
    
    
        /**
         * 通过用户名查询用户对象
         *      如果存在,返回对应的用户对象;如果不存在,返回null
         * @param userName
         * @return
         */
        public User queryUserByUserName(String userName){
            User user = null;
            // 判断用户名是否存在
            if (!USERNAME.equals(userName)) {
                return null;
            }
    
            // 给user对象赋值
            user = new User();
            user.setUserName(userName);
            user.setUserPwd(USERPWD);
    
            return user;
        }
    
    }
    

Service层 (业务逻辑处理)

  1. 定义业务处理返回消息模型 MessageModel.java

    package com.xxxx.entity.vo;
    
    /**
     * 消息模型对象
     *  用来接收处理结果
     *      resultCode  状态码
     *          1=成功  0=失败
     *      resultMsg   提示信息
     */
    public class MessageModel {
    
        private Integer resultCode = 1; // 状态码 (1=成功  0=失败)
        private String resultMsg; // 提示信息
    
    
        public Integer getResultCode() {
            return resultCode;
        }
    
        public void setResultCode(Integer resultCode) {
            this.resultCode = resultCode;
        }
    
        public String getResultMsg() {
            return resultMsg;
        }
    
        public void setResultMsg(String resultMsg) {
            this.resultMsg = resultMsg;
        }
    }
    
  2. 编写Service层 UserService.java

    package com.xxxx.service;
    
    import com.xxxx.dao.UserDao;
    import com.xxxx.entity.User;
    import com.xxxx.entity.vo.MessageModel;
    import com.xxxx.util.StringUtil;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    @Service
    public class UserService {
    
        @Resource
        private UserDao userDao;
    
        /**
         * 验证登录是否成功
         *  1. 参数的非空校验
         *  2. 通过用户名查询用户对象(调用Dao层的查询方法)
         *  3. 判断密码是否正确
         * @param uname
         * @param upwd
         * @return
         */
        public MessageModel checkUserLogin(String uname, String upwd){
            // 返回消息模型
            MessageModel messageModel = new MessageModel();
    
            // 1. 参数的非空校验
            if (StringUtil.isEmpty(uname) || StringUtil.isEmpty(upwd)) {
                // 用户名或密码不能为空
                messageModel.setResultCode(0);
                messageModel.setResultMsg("用户名称和密码不能为空!");
                return messageModel;
            }
    
            // 2. 通过用户名查询用户对象(调用Dao层的查询方法)
            User user = userDao.queryUserByUserName(uname);
            // 判断用户对象是否为空
            if (user == null) {
                messageModel.setResultCode(0);
                messageModel.setResultMsg("用户名不存在!");
                return messageModel;
            }
    
            // 3. 判断密码是否正确
            if (!upwd.equals(user.getUserPwd())) {
                messageModel.setResultCode(0);
                messageModel.setResultMsg("用户密码不正确!");
                return messageModel;
            }
    
            // 登录成功
            messageModel.setResultMsg("登录成功!");
    
            return messageModel;
        }
    }
    

Controller层 (接收请求)

  1. 编写Controller层 UserController.java

    package com.xxxx.controller;
    
    import com.xxxx.entity.vo.MessageModel;
    import com.xxxx.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    
    @Controller
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        public MessageModel userLogin(String uname, String upwd ){
    
            MessageModel messageModel = userService.checkUserLogin(uname,upwd);
    
            return messageModel;
    
        }
    
    }
    

通过JUnit进行测试

package com.xxxx.test;


import com.xxxx.controller.UserController;
import com.xxxx.entity.vo.MessageModel;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserTest {

    public static void main(String[] args) {
        // 获取Spring的上下文环境
        BeanFactory factory = new ClassPathXmlApplicationContext("spring.xml");
        // 得到实例化的userController对象
        UserController userController = (UserController) factory.getBean("userController");
        MessageModel messageModel = userController.userLogin("admin","admin");
        System.out.println("状态码:" + messageModel.getResultCode()+",提示信息:" + messageModel.getResultMsg());
    }
}

Bean的作用域与生命周期

Bean的作用域

scope属性
      singleton:单例作用域(默认)
      propotype:原型作用域

​ 默认情况下,我们从Spring容器中拿到的对象均是单例的,对于

bean的作用域类型如下:

singleton 作用域

image-20241230210019422

注意:

​ lazy-init是懒加载,

​ 如果等于true时,作用是指Spring容器启动的时候不会去实例化这个bean, 而是在程序调用时才去实例化。

​ 默认是false,即Spring容器启动时实例化。

​ 默认情况下,被管理的bean只会IOC容器中存在一个实例,对于所有获取该Bean的操作Spring容器将只返回同一个Bean。

容器在启动的情况下就实例化所有singleton 的 bean对象,并缓存与容器中。

单例作用域:
	容器在启动的时候就会去实例化所有的单例的bean对象(即单例作用域中的bean对象),并且会缓存在我们的容器中(即单例缓存池)。直白点说,当容器启动的时候,容器会把设置在单例作用域中的所有bean对象实例化,并且将其设置到单例缓存池中,当要使用的时候,直接从缓存池中拿即可。
	
lazy-init属性(懒加载)
    如果设置为true,表示懒加载,springIOC容器在启动时,不会实例化bean对象,在程序调用时(使用bean对象时)才会实例化
    如果设置false(默认),表示不懒加载,容器启动则实例化bean对象
     
lazy-init属性为什么要设置为false(lazy-init设置为false有什么好处)?
    1. 可以提前发现潜在的配置问题
    2. Bean对象在启动时就会设置在单例缓存池中,使用时不需要再去实例化bean对象,提高程序运行效率
    
什么对象适合作为单例对象?(什么对象适合交给IOC容器实例化?)
    无状态或状态不可变的对象(不存在改变当前对象状态的成员变量)
    使用对象:controller层、service层、dao层
    
什么是无状态或状态不可改变的对象?
	实际上对象状态的变化往往均是由于属性值得变化而引起的,比如user类 姓名属性会有变化,属性姓名的变化一般会引起user对象状态的变化。
	对于我们的程序来说,无状态对象没有实例变量的存在,保证了线程的安全性,service 层业务对象即是无状态对象。线程安全的。    

prototype 作用域

image-20241230211856640

​ 通过scope=“prototype” 设置bean的类型 ,每次向Spring容器请求获取Bean都返回一个全新的Bean,相对于"singleton"来说就是不缓存Bean,每次都是一个根据Bean定义创建的全新Bean。(Spring IOC容器在启动时,不会将bean对象实例化设置到单例缓存池中,每次实例化对象都会创建一个新的实例).

Web应用中的作用域

  1. request作用域

​ 表示每个请求需要容器创建一个全新Bean。比如提交表单的数据必须是对每次请求新建一个Bean来保持这些表单数据,请求结束释放这些数据。

  1. session作用域

​ 表示每个会话需要容器创建一个全新Bean。比如对于每个用户一般会有一个会话,该用户的用户信息需要存储到会话中,此时可以将该Bean作用域配置为session级别。

  1. globalSession作用域

​ 类似于session作用域,其用于portlet(Portlet是基于Java的Web组件,由Portlet容器管理,并由容器处理请求,生产动态内容)环境的web应用。如果在非portlet环境将视为session作用域。

​ 配置方式和基本的作用域相同,只是必须要有web环境支持,并配

置相应的容器监听器或拦截器从而能应用这些作用域。

Bean的生命周期

在Spring中,Bean的生命周期包括Bean的定义、初始化、使用和销毁4个阶段

Bean的定义

​ 在Spring中,通常是通过配置文档的方式来定义Bean的( 通过bean标签定义对应bean对象)。在一个配置文档中,可以定义多个Bean。

Bean的初始化

​ 默认在IOC容器加载时,实例化对象。

​ Spring bean 初始化有两种方式:

​ **方式一:**在配置文档中通过指定 init-method 属性来完成。

public class RoleService {
    // 定义初始化时需要被调用的方法
    public void init() {
        System.out.println("RoleService init...");
   }
}
<!-- 通过init-method属性指定方法 -->
<bean id="roleService" class="com.xxxx.service.RoleService" init-method="init"></bean>

方式二: 实现org.springframework.beans.factory.InitializingBean 接口。

public class RoleService implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("RoleService init...");
   }
}
<bean id="roleService"class="com.xxxx.service.RoleService" ></bean>

​ Bean对象实例化过程是在Spring容器初始化时被实例化的,但也不是不可改变的,可以通过 lazy-init=“true” 属性延迟bean对象的初始化操作,此时再调用getBean 方法时才会进行bean的初始化操作

Bean 的使用

​ **方式一:**使用 BeanFactory对象

// 得到Spring的上下文环境
BeanFactory factory = new ClassPathXmlApplicationContext("spring.xml");
RoleService roleService = (RoleService)factory.getBean("roleService");

​ **方式二:**使用 ApplicationContext对象

// 得到Spring的上下文环境
ApplicationContext ac = new
ClassPathXmlApplicationContext("spring.xml");
RoleService roleService = (RoleService)ac.getBean("roleService");

Bean的销毁

​ 实现销毁方式(Spring容器会维护bean对象的管理,可以指定bean对象的销毁所要执行的方法)。

​ **步骤一:**实现销毁方式(Spring容器会维护bean对象的管理,在配置文件中可以指定bean对象的销毁所要执行的方法)

<bean id="roleService"class="com.xxxx.service.RoleService" destroy-method="destroy"></bean>

​ **步骤二:**通过 AbstractApplicationContext 对象,调用其close方法实现bean的销毁过程

AbstractApplicationContext ctx=new
ClassPathXmlApplicationContext("spring.xml");
ctx.close();

总结:

​ IOC控制反转和DI依赖注入 :

​ IOC控制反转:将对象实例化的创建过程转交给外部容器(IOC容器 充当工厂角色),将bean对象放到IOC工厂中,工厂通过配置文件,最终会生成实例化好的、可供我们使用的实例化对象。

​ 依赖注入:属性赋值的操作。

代理模式

​ 代理模式在 Java 开发中是一种比较常见的设计模式。

​ 设计目的:旨在为服务类与客户类之间插入其他功能,插入的功能对于调用者是透明的,起到伪装控制的作用。

​ 如租房的例子:房客、中介、房东。对应于代理模式中即:客户类、代理类 、委托类(被代理类)。

​ 为某一个对象(委托类)提供一个代理(代理类),用来控制对这个对象的访问。委托类和代理类有一个共同的父类或父接口。代理类会对请求做预处理、过滤,将请求分配给指定对象。

​ 生活中常见的代理情况:

​ 租房中介、婚庆公司等

​ 代理模式的两个设计原则:

  1. 代理类 与 委托类 具有相似的行为(共同)

  2. 代理类增强委托类的行为

image-20241231165004680

​ 常见的代理模式:

		1. 静态代理
		1. 动态代理

静态代理

​ 某个对象提供一个代理,代理角色固定,以控制对这个对象的访问。 代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。

代理三要素

  • 有共同的行为(结婚)-定义接口
  • 目标对象(新人)-实现行为
  • 代理对象(婚庆公司)-实现行为,增强目标对象行为

静态代理的特点

  1. 目标对象固定
  2. 在应用程序执行前就得到目标对象
  3. 代理对象会增强目标对象的行为
  4. 有可能存在多个代理,引起“类爆炸”(缺点)

代理对象的实现

定义接口(定义共同行为)

/**
* 定义行为
*/
public interface Marry {
public void toMarry();
}

目标对象(实现行为)

/**
* 静态代理 ——> 目标对象
*/
public class You implements Marry {
// 实现行为
@Override
	public void toMarry() {
		System.out.println("我要结婚了...");
	}
}

代理对象(实现行为,增强目标对象的行为)

/**
* 静态代理 ——> 代理对象
*/
public class MarryCompanyProxy implements Marry {
    // 目标对象
    private Marry marry;
    // 通过构造器将目标对象传入
    public MarryCompanyProxy(Marry marry) {
    	this.marry = marry;
    }
    // 实现行为
    @Override
    public void toMarry() {
        // 增强行为
        before();
        // 执行目标对象中的方法
        marry.toMarry();
        // 增强行为
        after();
    }
    /**
    * 增强行为
    */
    private void after() {
        System.out.println("新婚快乐,早生贵子!");
    }
    /**
    * 增强行为
    */
    private void before() {
    	System.out.println("场地正在布置中...");
    }
}

通过代理对象实现目标对象的功能

// 目标对象
You you = new You();
// 构造代理角色同时传入真实角色
MarryCompanyProxy marryCompanyProxy = new MarryCompanyProxy(you);
// 通过代理对象调用目标对象中的方法
marryCompanyProxy.toMarry();

​ 静态代理对于代理的角色是固定的,如dao层有20个dao类,如果要对方法的访问权限进行代理,此时需要创建20个静态代理角色,引起类爆炸,无法满足生产上的需要,于是就催生了动态代理的思想。

动态代理

​ 相比于静态代理,动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由Java反射机制动态产生。它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象,无需程序员手动编写它的源代码。动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为反射机制可以生成任意类型的动态代理类。代理的行为可以代理多个方法,既满足生产需要的同时又达到代码通用的目的

​ 动态代理的优点:

1. 简化了编程工作
2. 提高了软件系统的可扩展性(因为反射机制可以生成任意类型的动态代理类)
3. 代理的行为可以代理多个方法,既满足生产需要的同时又达到代码通用的目的

​ 动态代理的两种实现方式:

			1. JDK动态代理
			1. CGLIB动态代理

动态代理的特点

1. 目标对象并不固定
2. 在应用程序执行时创建目标对象
3. 代理对象会增强目标对象的行为

JDK动态代理

注意:

​ JDK动态代理的目标对象必须有接口实现,JDK的动态代理依靠接口实现,如果有些类并没有接口实现,则不能使用JDK代理。

原理:
	代理类会实现InvocationHandler接口,该代理类的实例会关联到一个InvocationHandler,当我们通过代理类获取代理类实例(对象)时(通过代理类中的getProxy()方法获取实例),在去调某个方法的时候,最终会去调用目标对象中的方法,该方法会转发到InvocationHandler中的invoke方法中执行。

	动态代理类指的并不是自己自定义的JdkHandler,而是指的是由Proxy.newProxyInstance()方法生成的源码文件$Proxy0.class的中$Proxy0类。
	
流程:
    1.首先需要一个代理对象,该对象通过调用Proxy类中的newProxyInstance()方法返回了一个代理对象。
    2.调用newProxyInstance()方法时需要传入三个参数,三个参数分别是类加载器,目标对象的接口数组,InvocationHandler接口。
    3.想要获取目标对象的接口数组这个参数,需要通过带参构造方法将目标对象传进代理类中。此时目标对象就有值了,此时就可以通过目标对象得到目标对象的接口数组了。
    4.获取InvocationHandler接口这个参数,代理类的本身就是一个类的			  InvocationHandler对象(每一个代理类都需要实现InvocationHandler接口),所以直接通过this可以获取到。
    5.当实现InvocationHandler接口时,需要重写该类中的invoke方法,该方法就可以调用目标对象中的方法,对用户行为进行增强。
    
问:Java动态代理类中的invoke是怎么调用的?
答:在生成的动态代理类$Proxy0.class中,构造方法调用了父类Proxy.class的构造方法,给成员变量invocationHandler赋值,$Proxy0.class的static模块中创建了被代理类的方法,调用相应方法时方法体中调用了父类中的成员变量InvocationHandler的invoke()方法。

newProxyInstance

Proxy类:

​ Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下操作方法:

/*
	返回一个指定接口的代理类的实例方法调用分派到指定的调用处理程序。 (返回代理对象)
	loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
	interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
	h:一个InvocationHandler接口,表示代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke方法(传入InvocationHandler接口的子类)
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

获取代理对象

public class JdkHandler implements InvocationHandler {
	// 目标对象
    private Object target; // 目标对象的类型不固定,创建时动态生成
    // 通过构造器将目标对象赋值
    public JdkHandler(Object target) {
    this.target = target;
    }
    /**
    * 1、调用目标对象的方法(返回Object)
    * 2、增强目标对象的行为
    * @param proxy 调用该方法的代理实例
    * @param method 目标对象的方法
    * @param args 目标对象的方法形参
    * @return
    * @throws Throwable
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 增强行为
        System.out.println("==============方法前执行");
        // 调用目标对象的方法(返回Object)
        Object result = method.invoke(target,args);
        // 增强行为
        System.out.println("方法后执行==============");
        return result;
}
    /**
    * 得到代理对象
    * public static Object newProxyInstance(ClassLoader loader,
    * Class<?>[] interfaces,
    * InvocationHandler h)
    * loader:类加载器
    * interfaces:接口数组
    * h:InvocationHandler接口 (传入InvocationHandler接口的实现类)
    *
    *
    * @return
    */
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
}

通过代理对象实现目标对象的功能

// 目标对象
You you = new You();
// 获取代理对象
JdkHandler jdkHandler = new JdkHandler(you);
Marry marry = (Marry) jdkHandler.getProxy();
// 通过代理对象调用目标对象中的方法
marry.toMarry();

CGLIB动态代理

​ JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能使用JDK的动态代理,cglib是针对类来实现代理的,它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

实现原理:继承思想

实现流程:
    1.获取一个Enhancer对象,通过Enhancer对象中的create()方法生成一个类,用于生成代理对象。
    2.设置父类 (将目标类作为代理类的父类)。该目标对象通过带参构造传进来。
    3.设置拦截器 回调对象为本身对象
    4.生成代理类对象,并返回给调用者
    5.调用getProxy()获取到代理类对象。
    6.获取到代理类对象时,调用目标对象中的方法时,它就会被MethodInterceptor拦截器所拦截,即会进入intercept()方法。

屏幕截图 2024-12-30 155300

添加依赖

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

定义类

实现MethodInterceptor接口

package com.xxxx.proxy;

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

import java.lang.reflect.Method;

public class CglibInterceptor implements MethodInterceptor {

    // 目标对象
    private Object target;
    // 通过构造器传入目标对象
    public CglibInterceptor(Object target) {
        this.target = target;
    }

    /**
     * 获取代理对象
     * @return
     */
    public Object getProxy(){
        // 通过Enhancer对象中的create()方法生成一个类,用于生成代理对象
        Enhancer enhancer = new Enhancer();
        // 设置父类 (将目标类作为代理类的父类)
        enhancer.setSuperclass(target.getClass());
        // 设置拦截器 回调对象为本身对象
        enhancer.setCallback(this);
        // 生成代理类对象,并返回给调用者
        return enhancer.create();
    }


    /**
     * 拦截器
     *    1. 目标对象的方法调用
     *    2. 行为增强
     * @param o cglib动态生成的代理类的实例
     * @param method    实体类所调用的被代理的方法的引用
     * @param objects   参数列表
     * @param methodProxy   生成的代理类对方法的代理引用
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        // 增强行为
        System.out.println("===================方法前执行");

        // 调用目标类中的方法
        Object object = methodProxy.invoke(target, objects);

        // 增强行为
        System.out.println("方法后执行===================");

        return object;
    }


}

调用方法

// 目标对象
You you = new You();
CglibInterceptor cglibInterceptor = new CglibInterceptor(you);
Marry marry = (Marry) cglibInterceptor.getProxy();
marry.toMarry();
User user = new User();
CglibInterceptor cglibInterceptor = new CglibInterceptor(user);
User u = (User) cglibInterceptor.getProxy();
u.test();

JDK代理与CGLIB代理的区别

  • JDK动态代理实现接口,Cglib动态代理继承思想

  • JDK动态代理(目标对象存在接口时)执行效率高于Ciglib

  • 如果目标对象有接口实现,选择JDK代理,如果没有接口实现选择Cglib代

Spring AOP

日志处理带来的问题

​ 我们有一个Pay(接口) 然后两个实现类DollarPay和RmbPay,都需要重写pay()方法, 这时我们需要对pay方法进行性能监控,日志的添加等等怎么做?

image-20250102091859123

最容易想到的方法

​ 对每个字符方法均做日志代码的编写处理,如下面方式

image-20250102091953548

​ 缺点: 代码重复太多,添加的日志代码耦合度太高(如果需要更改日志记录代码功能需求,类中方法需要全部改动,工程量浩大)

使用装饰器模式/代理模式改进解决方案

​ 装饰器模式:动态地给一个对象添加一些额外的职责。

​ 代理模式:以上刚讲过。于是得出以下结构:

image-20250102092152900

​ 仔细考虑过后发现虽然对原有内部代码没有进行改动,对于每个类做日志处理,并引用目标类,但是如果待添加日志的业务类的数量很多,此时手动为每个业务类实现一个装饰器或创建对应的代理类,同时代码的耦合度也加大,需求一旦改变,改动的工程量也是可想而知的。

有没有更好的解决方案,只要写一次代码,对想要添加日志记录的地方能够实现代码的复用,达到松耦合的同时,又能够完美完成功能?

答案是肯定的,存在这样的技术,aop已经对其提供了完美的实现!

什么是AOP

Aspect Oriented Programing 面向切面编程,相比较 oop 面向对象编程来说,Aop关注的不再是程序代码中某个类,某些方法,而aop考虑的更多的是一种面到面的切入,即层与层之间的一种切入,所以称之为切面。联想大家吃的汉堡(中间夹肉)。那么aop是怎么做到拦截整个面的功能呢?考虑前面学到的servlet filter /* 的配置 ,实际上也是aop 的实现。

AOP能做什么?

​ AOP主要应用于日志记录,性能统计,安全控制,事务处理等方面,实现公共功能性的重复使用。(主要用于非业务模块、公共功能)

AOP的特点

  1. 降低模块与模块之间的耦合度,提高业务代码的聚合度。(高内聚低耦合)

  2. 提高了代码的复用性。

  3. 提高系统的扩展性。(高版本兼容低版本)

  4. 可以在不影响原有的功能基础上添加新的功能

AOP的底层实现

​ 动态代理(JDK+CGLIB)

AOP基本概念

joinpoint(连接点)

​ 被拦截到的每个点,spring中指被拦截到的每一个方法,spring aop一个连接点即代表一个方法的执行。

Pointcut(切入点)

​ 对连接点进行拦截的定义(匹配规则定义 规定拦截哪些方法,对哪些方法进行处理),spring 有专门的表达式语言定义。

Advice(通知)

​ 拦截到每一个连接点即(每一个方法)后所要做的操作

1. 前置通知 (前置增强)— before() 执行方法前通知
2. 返回通知(返回增强)— afterReturn 方法正常结束返回后的通知
3. 异常抛出通知(异常抛出增强)— afetrThrow()
4. 最终通知 — after 无论方法是否发生异常,均会执行该通知。
5. 环绕通知 — around 包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

Aspect(切面)

​ 切入点与通知的结合,决定了切面的定义,切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要做什么,切面则是横切关注点的抽象,与类相似,类是对物体特征的抽象,切面则是横切关注点抽象。

Target(目标对象)

​ 被代理的目标对象

Weave(织入)

​ 将切面应用到目标对象并生成代理对象的这个过程即为织入

Introduction(引入)

​ 在不修改原有应用程序代码的情况下,在程序运行期为类动态添加方法或者字段的过程称为引入

Spring AOP的实现

Spring AOP环境搭建

坐标依赖引入

<!--Spring AOP-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>

添加spring.xml的配置

​ 添加命名空间

xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd

注解实现

1. 切面
      切入点和通知的抽象
      定义 切入点  和 通知
         切入点:定义要拦截哪些类的哪些方法
         通知:定义了拦截之后方法要做什么

2. 切入点
      定义要拦截哪些类的哪些方法
      匹配规则,拦截什么方法

      定义切入点
          @Pointcut("匹配规则")
			例如:@Pointcut("execution(* com.xxxx.service..*.*(..))")


      Aop切入点表达式
          1. 执行所有的公共方法
              execution(public *(..))
         2. 执行任意的set方法
             execution(* set*(..))
         3. 设置指定包下的任意类的任意方法 (指定包:com.xxxx.service)
              execution(* com.xxxx.service.*.*(..))
         4. 设置指定包及子包下的任意类的任意方法(指定包:com.xxxx.service)
             execution(* com.xxxx.service..*.*(..))

        表达式中的第一个*
              代表的是方法的修饰范围  (public、private、protected)
              如果取值是*,则表示所有范围
3.通知
	声明前置通知,并将通知应用到指定的切入点上
   		目标类的方法执行前,执行该通知
		@Before(value = "cut()")

	声明返回通知,并将通知应用到指定的切入点上
      	目标类的方法在无异常执行后,执行该通知
		@AfterReturning(value = "cut()")

	声明最终通知,并将通知应用到指定的切入点上
      	目标类的方法在执行后,执行该通知 (有异常和无异常最终都会执行)
		@After(value = "cut()")

	声明异常通知,并将通知应用到指定的切入点上
      	目标类的方法在执行异常时,执行该通知
		@AfterThrowing(value = "cut()", throwing = "e")

	声明环绕通知,并将通知应用到指定的切入点上
      	目标类的方法执行前后,都可通过环绕通知定义响应的处理
         	需要通过显式调用的方法,否则无法访问指定方法 									pjp.proceed();
		@Around(value = "cut()")

定义切面

package com.xxxx.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 切面
 *      切入点和通知的抽象
 *      定义 切入点和通知
 *         切入点:定义要拦截哪些类的哪些方法
 *         通知:定义了拦截之后方法要做什么
 */
@Component // 将对象交给IOC容器进行实例化
@Aspect // 声明当前类是一个切面
public class LogCut {

    /**
     * 切入点
     *      定义要拦截哪些类的哪些方法
     *      匹配规则,拦截什么方法
     *
     *      定义切入点
     *          @Pointcut("匹配规则")
     *
     *      Aop切入点表达式
     *          1. 执行所有的公共方法
     *              execution(public *(..))
     *         2. 执行任意的set方法
     *              execution(* set*(..))
     *         3. 设置指定包下的任意类的任意方法 (指定包:com.xxxx.service)
     *              execution(* com.xxxx.service.*.*(..))
     *         4. 设置指定包及子包下的任意类的任意方法 (指定包:com.xxxx.service)
     *             execution(* com.xxxx.service..*.*(..))
     *
     *        表达式中的第一个*
     *              代表的是方法的修饰范围  (public、private、protected)
     *              如果取值是*,则表示所有范围
     */
    @Pointcut("execution(* com.xxxx.service..*.*(..))")
    public void cut(){

    }

    /**
     * 声明前置通知,并将通知应用到指定的切入点上
     *      目标类的方法执行前,执行该通知
     */
    @Before(value = "cut()")
    public void before() {
        System.out.println("前置通知...");
    }

    /**
     * 声明返回通知,并将通知应用到指定的切入点上
     *      目标类的方法在无异常执行后,执行该通知
     */
    @AfterReturning(value = "cut()")
    public void afterReturn(){
        System.out.println("返回通知...");
    }

    /**
     * 声明最终通知,并将通知应用到指定的切入点上
     *      目标类的方法在执行后,执行该通知 (有异常和无异常最终都会执行)
     */
    @After(value = "cut()")
    public void after(){
        System.out.println("最终通知...");
    }

    /**
     * 声明异常通知,并将通知应用到指定的切入点上
     *      目标类的方法在执行异常时,执行该通知
     */
    @AfterThrowing(value = "cut()", throwing = "e")
    public void afterThrow(Exception e){
        System.out.println("异常通知...  ===== 异常原因:" + e.getMessage());
    }


    /**
     * 声明环绕通知,并将通知应用到指定的切入点上
     *      目标类的方法执行前后,都可通过环绕通知定义响应的处理
     *          需要通过显式调用的方法,否则无法访问指定方法 pjp.proceed();
     * @param pjp
     * @return
     */
    @Around(value = "cut()")
    public Object around(ProceedingJoinPoint pjp)  {
        System.out.println("环绕通知-前置通知...");

        Object object = null;
        //object = pjp.proceed();

        try{
            // 显式调用对应的方法
            object = pjp.proceed();
            System.out.println(pjp.getTarget());
            System.out.println("环绕通知-返回通知...");
        } catch (Throwable throwable){
            throwable.printStackTrace();
            System.out.println("环绕通知-异常通知...");
        }
        System.out.println("环绕通知-最终通知...");

        return object;
    }

}

配置文件

<!--配置AOP代理-->
<aop:aspectj-autoproxy/>

XML实现

定义切面

package com.xxxx.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 切面
 *      切入点和通知的抽象
 *      定义 切入点  和 通知
 *         切入点:定义要拦截哪些类的哪些方法
 *         通知:定义了拦截之后方法要做什么
 */
@Component // 将对象交给IOC容器进行实例化
public class LogCut02 {

    /**
     * 切入点
     *      定义要拦截哪些类的哪些方法
     *      匹配规则,拦截什么方法
     *
     */
    public void cut(){

    }

    /**
     * 声明前置通知,并将通知应用到指定的切入点上
     *      目标类的方法执行前,执行该通知
     */
    public void before() {
        System.out.println("前置通知...");
    }

    /**
     * 声明返回通知,并将通知应用到指定的切入点上
     *      目标类的方法在无异常执行后,执行该通知
     */
    public void afterReturn(){
        System.out.println("返回通知...");
    }

    /**
     * 声明最终通知,并将通知应用到指定的切入点上
     *      目标类的方法在执行后,执行该通知 (有异常和无异常最终都会执行)
     */
    public void after(){
        System.out.println("最终通知...");
    }

    /**
     * 声明异常通知,并将通知应用到指定的切入点上
     *      目标类的方法在执行异常时,执行该通知
     */
    public void afterThrow(Exception e){
        System.out.println("异常通知...  ===== 异常原因:" + e.getMessage());
    }


    /**
     * 声明环绕通知,并将通知应用到指定的切入点上
     *      目标类的方法执行前后,都可通过环绕通知定义响应的处理
     *          需要通过显式调用的方法,否则无法访问指定方法 pjp.proceed();
     * @param pjp
     * @return
     */
    public Object around(ProceedingJoinPoint pjp)  {
        System.out.println("环绕通知-前置通知...");

        Object object = null;
        //object = pjp.proceed();

        try{
            // 显式调用对应的方法
            object = pjp.proceed();
            System.out.println(pjp.getTarget());
            System.out.println("环绕通知-返回通知...");
        } catch (Throwable throwable){
            throwable.printStackTrace();
            System.out.println("环绕通知-异常通知...");
        }
        System.out.println("环绕通知-最终通知...");

        return object;
    }
}

配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       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.xxxx"/>

        <!-- aop 相关配置 -->
        <aop:config>
                <!-- aop 切面 -->
                <aop:aspect ref="logCut02">
                        <!-- 定义 aop 切入点 -->
                        <aop:pointcut id="cut" expression="execution(* com.xxxx.service..*.*(..))"/>
                        <!-- 配置前置通知,设置前置通知对应的方法名及切入点 -->
                        <aop:before method="before" pointcut-ref="cut"/>
                        <!-- 配置返回通知,设置返回通知对应的方法名及切入点 -->
                        <aop:after-returning method="afterReturn" pointcut-ref="cut"/>
                        <!-- 配置最终通知,设置最终通知对应的方法名及切入点 -->
                        <aop:after method="after" pointcut-ref="cut"/>
                        <!-- 配置异常通知,设置异常通知对应的方法名及切入点 -->
                        <aop:after-throwing method="afterThrow" pointcut-ref="cut" throwing="e" />
                        <!-- 配置环绕通知,设置环绕通知对应的方法名及切入点 -->
                        <aop:around method="around" pointcut-ref="cut"/>
                </aop:aspect>
        </aop:config>

</beans>

Spring AOP总结

代理模式实现三要素

  1. 接口定义

  2. 目标对象与代理对象必须实现统一接口

  3. 代理对象持有目标对象的引用 增强目标对象行为

代理模式实现分类以及对应区别

  1. 静态代理:手动为目标对象制作代理对象,即在程序编译阶段完成代理对象的创建

  2. 动态代理:在程序运行期动态创建目标对象对应代理对象。

  3. jdk动态代理:被代理目标对象必须实现某一或某一组接口 实现方式 通过回调创建代理对象。

  4. cglib 动态代理:被代理目标对象可以不必实现接口,继承的方式实现。

动态代理相比较静态代理,提高开发效率,可以批量化创建代理,提高代码复用率。

Aop理解

  1. 面向切面,相比oop 关注的是代码中的层 或面

  2. 解耦,提高系统扩展性

  3. 提高代码复用

Aop 关键词

  1. 连接点:每一个方法

  2. 切入点:匹配的方法集合

  3. 切面:连接点与切入点的集合决定了切面,横切关注点的抽象

  4. 通知:五种通知

  5. 目标对象:被代理对象

  6. 织入:程序运行期将切面应用到目标对象 并生成代理对象的过程

  7. 引入:在不修改原始代码情况下,在程序运行期为程序动态引入方法或字段的过程

Spring 整合JDBC环境

​ Spring 框架除了提供 IOC 与 AOP 核心功能外,同样提供了基于JDBC 的数据访问功能,使得访问持久层数据更加方便。使用Spring JDBC 环境,首先需要一套 Spring 整合 JDBC 的环境。

添加依赖坐标

<!-- 添加相关的依赖坐标 -->
<!-- spring 框架坐标依赖添加 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.4.RELEASE</version>
    </dependency>
 <!-- spring 测试环境 -->
 <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.4.RELEASE</version>
    <scope>test</scope>
 </dependency>
 <!-- aop -->
 <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
 </dependency>
 <!-- spring jdbc -->
 <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.4.RELEASE</version>
 </dependency>
 <!-- spring事物 -->
 <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.4.RELEASE</version>
 </dependency>
 <!-- mysql 驱动包 -->
 <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
 </dependency>
 <!-- c3p0 连接池 -->
 <dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.5</version>
</dependency>

添加jdbc 配置文件

​ 在src/main/resources目录下新建jdbc.properties配置文件,并设

置对应的配置信息

# 驱动名
jdbc.driver=com.mysql.cj.jdbc.Driver
# 数据库连接
jdbc.url=jdbc:mysql://localhost:3306/(数据库名称)?
useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
# 数据库用户名称
jdbc.user=(数据库账号)
# 数据库用户密码
jdbc.password=(数据库密码)

​ 以下为可选配置

# 指定连接池的初始化连接数。取值应在minPoolSize 与maxPoolSize 之间.Default:3
initialPoolSize=20
# 指定连接池中保留的最大连接数. Default:15
maxPoolSize=100
# 指定连接池中保留的最小连接数
minPoolSize=10
# 最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。 Default:0
maxIdleTime=600
# 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数.Default:3
acquireIncrement=5
# JDBC的标准,用以控制数据源内加载的PreparedStatements数量。
maxStatements=5
# 每60秒检查所有连接池中的空闲连接.Default:0
idleConnectionTestPeriod=60

修改spring 配置文件

<!-- 加载properties 配置文件到IOC容器中,用来读取jdbc.properties文件中的数据 -->
<context:property-placeholder location="jdbc.properties" />

spring.xml

<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
     https://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- Spring扫描注解的配置 -->
    <context:component-scan base-package="com.xxxx" />
     <!-- 加载properties 配置文件 -->
    <context:property-placeholder location="jdbc.properties" />
    
</beans>

配置数据源

​ 由于建立数据库连接是一个非常耗时耗资源的行为,所以通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时直接到连接池中申请一个就行,用完后再放回去。

C3P0 与 DBCP 二选一即可

DBCP(DataBase connection pool),数据库连接池。是 apache 上的一个 java 连接池项目,也是 tomcat 使用的连接池组件。单独使用dbcp需要2个包:commons-dbcp.jar,commons-pool.jar。dbcp,没有自动回收空闲连接的功能。

C3P0是一个开源的JDBC连接池,它实现了数据源,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。c3p0有自动回收空闲连接功能。

C3P0 数据源配置

<!-- 配置 c3p0 数据源 -->
 <bean id="dataSource"class="com.mchange.v2.c3p0.ComboPooledDataSource"
>
    <!-- property标签的value属性对应的是jdbc.properties中的值 -->
    <property name="driverClass"value="${jdbc.driver}"></property>
    <property name="jdbcUrl" value="${jdbc.url}">
    </property>
        <property name="user" value="${jdbc.user}">
    </property>
    <property name="password"value="${jdbc.password}"></property>
 </bean>

C3P0 其他额外配置(对应的值在jdbc.properties文件中指定)

<!-- 指定连接池中保留的最大连接数。 Default:15-->
 <property name="maxPoolSize"value="${maxPoolSize}"/>
 <!-- 指定连接池中保留的最小连接数。-->
 <property name="minPoolSize"value="${minPoolSize}"/>
 <!-- 指定连接池的初始化连接数。取值应在minPoolSize 与maxPoolSize 之间.Default:3-->
 <property name="initialPoolSize"value="${initialPoolSize}"/>
 <!-- 最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。 Default:0-->
 <property name="maxIdleTime"value="${maxIdleTime}"/>
 <!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。 Default:3-->
 <property name="acquireIncrement"value="${acquireIncrement}"/>
<!-- JDBC的标准,用以控制数据源内加载的PreparedStatements数量。  
但由于预缓存的statements属于单个connection,而不是整个连接池所以设置这个参数需要考虑到多方面的因数。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:0-->
<property name="maxStatements"value="${maxStatements}"/>
<!-- 每60秒检查所有连接池中的空闲连接。Default:0 -->
<property name="idleConnectionTestPeriod"
          value="${idleConnectionTestPeriod}"/>

DBCP数据源配置

<!-- 配置dbcp数据源-->
<bean id="myDataSource"class="org.apache.commons.dbcp2.BasicDataSource"
>
     <property name="driverClassName"value="${jdbc.driver}" />
     <property name="url" value="${jdbc.url}"/>
     <property name="username"value="${jdbc.user}"/>
     <property name="password"value="${jdbc.password}"/>
     <!-- 连接池启动时的初始值 -->  
     <property name="initialSize" value="1"/>  
     <!-- 最大空闲值.当经过一个高峰时间后,连接池可以将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->  
     <property name="maxIdle" value="2"/>  
     <!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请一些连接,以避免洪峰来时再申请而造成的性能开销 -->  
     <property name="minIdle" value="1"/>  
</bean>

模板类配置

Spring把 JDBC 中重复的操作建立成了一个模板类:org.springframework.jdbc.core.JdbcTemplate 。

<!-- 配置JdbcTemplate实例,并注入一个dataSource数据源-->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource">
</property>
</bean>

JDBC测试

创建指定数据库

​ 选择连接,右键选择"新建数据库",设置数据库的名称和编码格式

image-20241231101655247

创建数据表

image-20241231101759865

使用JUnit测试

​ 通过 junit 测试 jdbcTemplate bean 是否获取到

JUnit测试
 @Test
    public void testQueryCount() {
        // 获取spring上下文环境
        ApplicationContext ctx = new
ClassPathXmlApplicationContext("spring.xml");
        // 得到模板类 JdbcTemplate对象
        JdbcTemplate jdbcTemplate =
(JdbcTemplate) ctx.getBean("jdbcTemplate");
        // 定义sql语句
        String sql = "select count(1) from
tb_account";
        // 执行查询操作(无参数)
        Integer total=
jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println("总记录数:" + total);
   }
    @Test
    public void testQueryCountByUserId() {
		// 获取spring上下文环境
        ApplicationContext ctx = new
ClassPathXmlApplicationContext("spring.xml");
        // 得到模板类 JdbcTemplate对象
        JdbcTemplate jdbcTemplate =
(JdbcTemplate) ctx.getBean("jdbcTemplate");
        // 定义sql语句
        String sql = " select count(1) from
tb_account where user_id = ?";
        // 执行查询操作(有参数)
        Integer total =
jdbcTemplate.queryForObject(sql, Integer.class,1);
        System.out.println("总记录数:" + total);
   }
}
简单封装
public class SpringJdbcTest02 {
    private JdbcTemplate jdbcTemplate;
    @Before
    public void init() {
        // 得到Spring上下文环境
        ApplicationContext ac = new
ClassPathXmlApplicationContext("spring.xml");
        // 得到模板类 JdbcTemplate对象
        jdbcTemplate = (JdbcTemplate)ac.getBean("jdbcTemplate");
   }
    @Test
    public void testQueryCount() {
        // 定义sql语句
        String sql = "select count(1) from tb_account";
        // 执行查询操作(无参数)
        Integer total=
jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println("总记录数:" + total);
   }
    @Test
    public void testQueryCountByUserId() {
        // 定义sql语句
        String sql = " select count(1) from tb_account where user_id = ?";
        // 执行查询操作(有参数)
        Integer total =
jdbcTemplate.queryForObject(sql, Integer.class,1);
        System.out.println("总记录数:" + total);
   }
}
注解封装
@RunWith
     就是一个运行器
     @RunWith(JUnit4.class) 就是指用JUnit4来运行
     @RunWith(SpringJUnit4ClassRunner.class) 让测试运行于Spring测试环境
@ContextConfiguration
     Spring整合JUnit4测试时使用注解引入多个配置文件
 	@ContextConfiguration(Locations="classpath:applicationContext.xml")  
 	@ContextConfiguration(locations ={"classpath:spring.xml", "classpath:bean.xml"}) 
@RunWith(SpringJUnit4ClassRunner.class) // 将junit测试加到spring环境中
@ContextConfiguration(locations ={"classpath:spring.xml"}) // 设置要加载的资源文件
public class SpringJdbcTest03 {
    @Resource
    private JdbcTemplate jdbcTemplate;
    @Test
    public void testQueryCount() {
        // 定义sql语句
        String sql = "select count(1) from tb_account";
        // 执行查询操作(无参数)
        Integer total=
jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println("总记录数:" + total);
   }
}                     
通用封装
  1. 定义一个父类,设置通用的配置信息

    /**
     * 通用的测试环境,需要使用环境的直接继承类即可
     */
     @RunWith(SpringJUnit4ClassRunner.class) // 将junit测试加到spring环境中
     @ContextConfiguration(locations ={"classpath:spring.xml"}) // 设置要加载的资源文件
     public class BaseTest {
        
     }
    
  2. 继承通用的测试类

    public class SpringJdbcTest04 extends BaseTest
    {
        @Resource
        private JdbcTemplate jdbcTemplate;
        @Test
        public void testQueryCount() {
            // 定义sql语句
            String sql = "select count(1) from tb_account";
            // 执行查询操作(无参数)
            Integer total=
    jdbcTemplate.queryForObject(sql, Integer.class);
            System.out.println("总记录数:" + total);
       }
    }
    

持久层账户模块操作

​ 当完成 Spring Jdbc 环境集成后,这里使用spring jdbc 完成账户单

表crud 操作。

账户接口方法定义

定义实体类

​ Account.java

package com.xxxx.po;

import java.util.Date;

/**
 * 用户账户类
 */
public class Account {

    private Integer accountId; // 账户ID,主键
    private String accountName; // 账户名称
    private String accountType; // 账户类型
    private Double money; // 账户金额
    private String remark; // 账户备注
    private Date createTime; // 创建时间
    private Date updateTime; // 修改时间
    private Integer userId; // 用户ID,账户所属用户

    public Account() {
    }

    public Account(String accountName, String accountType, Double money, String remark, Integer userId) {
        this.accountName = accountName;
        this.accountType = accountType;
        this.money = money;
        this.remark = remark;
        this.userId = userId;
    }

    @Override
    public String toString() {
        return "Account{" +
                "accountId=" + accountId +
                ", accountName='" + accountName + '\'' +
                ", accountType='" + accountType + '\'' +
                ", money=" + money +
                ", remark='" + remark + '\'' +
                ", createTime=" + createTime +
                ", updateTime=" + updateTime +
                ", userId=" + userId +
                '}';
    }

    public Integer getAccountId() {
        return accountId;
    }

    public void setAccountId(Integer accountId) {
        this.accountId = accountId;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    public String getAccountType() {
        return accountType;
    }

    public void setAccountType(String accountType) {
        this.accountType = accountType;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }
}

定义接口类

IAccountDao.java

package com.xxxx.dao;

import com.xxxx.po.Account;

import java.util.List;

/**
 * 账户模块 接口定义
 *      1. 添加账户
 *          添加账户记录,返回受影响的行数
 *          添加账户记录,返回主键
 *          批量添加账户记录,返回受影响的行数
 *      2. 修改账户
 *          修改账户记录,返回受影响的行数
 *          批量修改账户记录,返回受影响的行数
 *      3. 删除账户
 *          删除账户记录,返回受影响的行数
 *  *       批量删除账户记录,返回受影响的行数
 *      4. 查询账户
 *          查询指定账户的账户的总记录数,返回总记录数
 *          查询指定账户的账户详情,返回账户对象
 *          多条件查询指定用户的账户列表,返回账户集合
 */
public interface IAccountDao {

    /**
     * 添加账户
     *      添加账户记录,返回受影响的行数
     * @param account
     * @return
     */
    public int addAccount(Account account);

    /**
     * 添加账户
     *      添加账户记录,返回主键
     * @param account
     */
    public int addAccountHasKey(Account account);

    /**
     * 添加账户
     *      量添加账户记录,返回受影响的行数
     * @param accounts
     * @return
     */
    public int addAccountBatch(List<Account> accounts);

    /**
     * 查询账户
     *      查询指定用户的账户的总记录数,返回总记录数
     * @param userId
     * @return
     */
    public int queryAccountCount(int userId);

    /**
     * 查询账户
     *      查询指定账户的账户详情,返回账户对象
     * @param accountId
     * @return
     */
    public Account queryAccountById(int accountId);

    /**
     * 查询账户
     *      多条件查询指定用户的账户列表,返回账户集合
     * @param userId    指定用户的ID
     * @param accountName   账户名称(模糊查询)
     * @param accountType   账户类型
     * @param createTime    创建时间(大于当前时间)
     * @return
     */
    public List<Account> queryAccountByParams(Integer userId, String accountName,String accountType, String createTime);

    /**
     * 修改账户
     *      修改账户记录,返回受影响的行数
     * @param account
     * @return
     */
    public int updateAccount(Account account);

    /**
     * 修改账户
     *      批量修改账户记录,返回受影响的行数
     * @param accounts
     * @return
     */
    public int updateAccountBatch(List<Account> accounts);

    /**
     * 删除账户
     *      删除账户记录,返回受影响的行数
     * @param accountId
     * @return
     */
    public int deleteAccount(int accountId);

    /**
     * 删除账户
     *      批量删除账户记录,返回受影响的行数
     * @param ids
     * @return
     */
    public int deleteAccountBatch(Integer[] ids);

    /**
     * 支出
     * @param accountId
     * @param money
     * @return
     */
    public int outAccount(Integer accountId, Double money);

    /**
     * 收入
     * @param accountId
     * @param money
     * @return
     */
    public int inAccount(Integer accountId, Double money);

}

定义接口实现类

package com.xxxx.dao.impl;

import com.xxxx.dao.IAccountDao;
import com.xxxx.po.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 账户模块接口的实现类
 */
@Repository
public class AccountDaoImpl implements IAccountDao {

    // 注入JdbcTemplate模板类
    @Resource
    private JdbcTemplate jdbcTemplate;

    /**
     * 添加账户记录,返回受影响的行数
     * @param account
     * @return
     */
    @Override
    public int addAccount(Account account) {
        // 定义sql语句
        String sql = "insert into tb_account (account_name,account_type,money,remark," +
                " create_time,update_time,user_id) values (?,?,?,?,now(),now(),?)";
        // 设置参数
        Object[] objs = {account.getAccountName(),account.getAccountType(),account.getMoney(),
                account.getRemark(),account.getUserId()};

        int row = jdbcTemplate.update(sql,objs);

        return row;
    }

    /**
     * 添加账户记录,返回主键
     * @param account
     * @return
     */
    @Override
    public int addAccountHasKey(Account account) {

        // 定义sql语句
        String sql = "insert into tb_account (account_name,account_type,money,remark," +
                " create_time,update_time,user_id) values (?,?,?,?,now(),now(),?)";

        // 定义KeyHolder对象  获取记录的主键值
        KeyHolder keyHolder = new GeneratedKeyHolder();

        jdbcTemplate.update(connection -> {
            // 预编译sql语句,并设置返回主键
            PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            // 设置参数
            ps.setString(1,account.getAccountName());
            ps.setString(2,account.getAccountType());
            ps.setDouble(3,account.getMoney());
            ps.setString(4,account.getRemark());
            ps.setInt(5,account.getUserId());
            // 返回预编译对象
            return ps;
        }, keyHolder);

        // 得到返回的主键
        int key = keyHolder.getKey().intValue();
        return key;
    }


    /**
     * 批量添加账户记录,返回受影响的行数
     * @param accounts
     * @return
     */
    @Override
    public int addAccountBatch(List<Account> accounts) {
        // 定义sql语句
        String sql = "insert into tb_account (account_name,account_type,money,remark," +
                " create_time,update_time,user_id) values (?,?,?,?,now(),now(),?)";

        int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                Account account = accounts.get(i);
                // 设置参数
                ps.setString(1,account.getAccountName());
                ps.setString(2,account.getAccountType());
                ps.setDouble(3,account.getMoney());
                ps.setString(4,account.getRemark());
                ps.setInt(5,account.getUserId());
            }

            @Override
            public int getBatchSize() {
                return accounts.size();
            }
        }).length;

        return rows;
    }


    /**
     * 查询指定用户的账户总记录数,返回总数量
     * @param userId
     * @return
     */
    @Override
    public int queryAccountCount(int userId) {
        // 定义sql语句
        String sql = "select count(1) from tb_account where user_id = ? ";
        // 查询方法
        int count = jdbcTemplate.queryForObject(sql, Integer.class, userId);
        return count;
    }


    /**
     * 查询指定账户记录详情,返回账户对象
     * @param accountId
     * @return
     */
    @Override
    public Account queryAccountById(int accountId) {
        // 定义sql语句
        String sql = "select * from tb_account where account_id = ? ";
        // 查询对象
        Account account = jdbcTemplate.queryForObject(sql, (ResultSet rs, int i) ->  {
            Account acc = new Account();
            acc.setAccountId(accountId);
            acc.setAccountName(rs.getString("account_name"));
            acc.setAccountType(rs.getString("account_type"));
            acc.setMoney(rs.getDouble("money"));
            acc.setRemark(rs.getString("remark"));
            acc.setUserId(rs.getInt("user_id"));
            acc.setCreateTime(rs.getDate("create_time"));
            acc.setUpdateTime(rs.getDate("update_time"));
            return acc;
        },accountId);
        return account;
    }

    /**
     * 多条件查询指定用户的账户记录列表,返回账户集合
     * @param userId    指定用户的ID
     * @param accountName   账户名称(模糊查询)
     * @param accountType   账户类型
     * @param createTime    创建时间(大于当前时间)
     * @return
     */
    @Override
    public List<Account> queryAccountByParams(Integer userId, String accountName, String accountType, String createTime) {
        // 定义sql语句
        String sql = "select * from tb_account where user_id = ? ";
        // 定义参数列表
        List<Object> params = new ArrayList<>();
        params.add(userId);

        // 判断参数是否为空,如果不为空,拼接sql语句及设置对应的参数
        // 账户名称
        if (StringUtils.isNotBlank(accountName)){
            // 拼接sql语句
            sql += " and account_name like concat('%',?,'%') ";
            // 设置参数
            params.add(accountName);
        }
        // 账户类型
        if (StringUtils.isNotBlank(accountType)){
            // 拼接sql语句
            sql += " and account_type = ? ";
            // 设置参数
            params.add(accountType);
        }
        // 创建时间
        if (StringUtils.isNotBlank(createTime)){
            // 拼接sql语句
            sql += " and create_time < ? ";
            // 设置参数
            params.add(createTime);
        }

        // 将集合转换为数组
        Object[] objs = params.toArray();
        // 查询集合
        List<Account> accountList = jdbcTemplate.query(sql, objs, (ResultSet rs, int i) ->  {
            Account acc = new Account();
            acc.setAccountId(rs.getInt("account_id"));
            acc.setAccountName(rs.getString("account_name"));
            acc.setAccountType(rs.getString("account_type"));
            acc.setMoney(rs.getDouble("money"));
            acc.setRemark(rs.getString("remark"));
            acc.setUserId(rs.getInt("user_id"));
            acc.setCreateTime(rs.getTimestamp("create_time"));
            acc.setUpdateTime(rs.getTimestamp("update_time"));
            return acc;
        });
        return accountList;
    }

    /**
     * 修改账户,返回受影响的行数
     * @param account
     * @return
     */
    @Override
    public int updateAccount(Account account) {
        // 定义sql
        String sql = "update tb_account set account_name = ? , account_type = ? , money = ? , remark = ? , " +
                " update_time = now(), user_id = ? where account_id = ?";
        // 设置参数
        Object[] objs = {account.getAccountName(),account.getAccountType(),account.getMoney(),
                        account.getRemark(),account.getUserId(),account.getAccountId()};
        int row = jdbcTemplate.update(sql, objs);
        return row;
    }

    /**
     * 批量修改账户记录,返回受影响的行数
     * @param accounts
     * @return
     */
    @Override
    public int updateAccountBatch(List<Account> accounts) {
        // 定义sql语句
        String sql = "update tb_account set account_name = ? , account_type = ? , money = ? , remark = ? , " +
                " update_time = now(), user_id = ? where account_id = ?";
        int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                Account account = accounts.get(i);
                // 设置参数
                ps.setString(1,account.getAccountName());
                ps.setString(2,account.getAccountType());
                ps.setDouble(3,account.getMoney());
                ps.setString(4,account.getRemark());
                ps.setInt(5,account.getUserId());
                ps.setInt(6,account.getAccountId());
            }

            @Override
            public int getBatchSize() {
                return accounts.size();
            }
        }).length;
        return rows;
    }

    /**
     * 删除账户记录,返回受影响的行数
     * @param accountId
     * @return
     */
    @Override
    public int deleteAccount(int accountId) {
        // 定义SQL语句
        String sql = "delete from tb_account where account_id = ?";
        int row = jdbcTemplate.update(sql,accountId);
        return row;
    }


    /**
     * 批量删除账户记录,返回受影响的行数
     * @param ids
     * @return
     */
    @Override
    public int deleteAccountBatch(Integer[] ids) {
        // 定义SQL语句
        String sql = "delete from tb_account where account_id = ?";
        int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setInt(1, ids[i]);
            }

            @Override
            public int getBatchSize() {
                return ids.length;
            }
        }).length;
        return rows;
    }


    /**
     * 支出
     * @param accountId
     * @param money
     * @return
     */
    @Override
    public int outAccount(Integer accountId, Double money) {
        String sql = "update tb_account set money = money - ? where account_id = ? ";
        Object[] objs = {money, accountId};
        return jdbcTemplate.update(sql,objs);
    }

    /**
     * 收入
     * @param accountId
     * @param money
     * @return
     */
    @Override
    public int inAccount(Integer accountId, Double money) {
        String sql = "update tb_account set money = money + ? where account_id = ? ";
        Object[] objs = {money, accountId};
        return jdbcTemplate.update(sql,objs);
    }

}

账户记录添加实现

​ 在企业项目开发时,对于记录的添加可能涉及到多种添加方式,比如添加单条记录,批量添加多条记录等情况。这里对于账户记录添加方式分为三种方式:添加单条记录返回受影响行数、添加单条记录返回主键、批量添加多条记录。

添加账户记录

/**
  * 添加单条记录,返回受影响的行数
  * @param account
  * @return
  */
@Override
public int addAccount(Account account) {
    String sql = "insert into
tb_account(account_name,account_type,money,remark,user_id,create_time,update_time) values (?,?,?,?,?,now(),now())";
    Object[] objs ={account.getAccountName(),account.getAccountType(),account.getMoney(),account.getRemark(),account.getUserId()};
    return jdbcTemplate.update(sql,objs);
}

测试方法

/**
  * 添加账户记录,得到受影响的行数
  */
@Test
public void testAddAccount() {
    // 准备要添加的数据
    Account account = new Account("张三","建设银行",100.0,"零花钱",1);
    // 调用对象的添加方法,返回受影响的行数
    int row = accountDao.addAccount(account);
    System.out.println("添加账户受影响的行数:" +row);
}

添加记录返回主键

    /**
     * 添加账户记录,返回主键
     * @param account
     * @return
     */
    @Override
    public int addAccountHasKey(Account account) {

        // 定义sql语句
        String sql = "insert into tb_account (account_name,account_type,money,remark," +
                " create_time,update_time,user_id) values (?,?,?,?,now(),now(),?)";

        // 定义KeyHolder对象  获取记录的主键值
        KeyHolder keyHolder = new GeneratedKeyHolder();

        jdbcTemplate.update(connection -> {
            // 预编译sql语句,并设置返回主键
            PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            // 设置参数
            ps.setString(1,account.getAccountName());
            ps.setString(2,account.getAccountType());
            ps.setDouble(3,account.getMoney());
            ps.setString(4,account.getRemark());
            ps.setInt(5,account.getUserId());
            // 返回预编译对象
            return ps;
        }, keyHolder);

        // 得到返回的主键
        int key = keyHolder.getKey().intValue();
        return key;
    }

测试方法

/**
  * 添加账户记录,返回主键
  */
@Test
public void testAddAccountHasKey() {
    // 准备要添加的数据
    Account account = new Account("李四","招商银
行",200.0,"兼职费",2);
    // 调用对象的添加方法,返回主键
    int key =
accountDao.addAccountHasKey(account);
    System.out.println("添加账户返回的主键:" +
key);
}

批量添加账户记录

	/**
     * 批量添加账户记录,返回受影响的行数
     * @param accounts
     * @return
     */
    @Override
    public int addAccountBatch(List<Account> accounts) {
        // 定义sql语句
        String sql = "insert into tb_account (account_name,account_type,money,remark," +
                " create_time,update_time,user_id) values (?,?,?,?,now(),now(),?)";

        int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                Account account = accounts.get(i);
                // 设置参数
                ps.setString(1,account.getAccountName());
                ps.setString(2,account.getAccountType());
                ps.setDouble(3,account.getMoney());
                ps.setString(4,account.getRemark());
                ps.setInt(5,account.getUserId());
            }

            @Override
            public int getBatchSize() {
                return accounts.size();
            }
        }).length;

        return rows;
    }

测试方法

/**
  * 批量添加数据,返回受影响的行数
  */
@Test
public void testAddAccountBatch() {
    // 准备要添加的数据
    Account account  = new Account("王五","农业银行",2000.0,"工资",3);
    Account account2 = new Account("赵六","中国银行",280.0,"奖金",3);
    Account account3 = new Account("田七","工商银行",800.0,"零花钱",3);
    List<Account> accountList = new ArrayList<>();
    accountList.add(account);
    accountList.add(account2);
    accountList.add(account3);
    // 调用对象的添加方法,返回主键
    int rows =accountDao.addAccountBatch(accountList);
    System.out.println("批量添加账户受影响的行数:" +rows);
}

账户记录查询实现

​ 账户记录查询这里提供了三种查询方式,查询指定用户所有账户记录数,查询单条账户记录详情,多条件查询指定用户账户记录。

查询用户的账户总记录数

/**
  * 查询指定用户的账户总记录数,返回记录数
  * @param userId
  * @return
  */
@Override
public int queryAccountCount(Integer userId) {
    String sql = "select count(1) from tb_account where user_id = ?";
    int count =jdbcTemplate.queryForObject(sql,Integer.class,userId);
    return count;
}

测试方法

/**
  * 查询用户的账户总记录数,返回总记录数
  */
@Test
public void testQueryAccountCount(){
    // 查询ID为1的用户的账户总记录数
    int total = accountDao.queryAccountCount(1);
    System.out.println("总记录数:" + total);
}

查询指定账户记录详情

/**
  * 查询某个账户记录详情,返回账户对象
  * @param accountId
  * @return
  */
@Override
public Account queryAccountById(Integer
accountId) {
        String sql = "select * from tb_account where account_id = ?";
        Account account =jdbcTemplate.queryForObject(sql, new Object[]
{accountId}, (resultSet, i) -> {
          Account acc = new Account();
          acc.setAccountId(resultSet.getInt("account_id"));
          acc.setMoney(resultSet.getDouble("money"));
          acc.setAccountName(resultSet.getString("account_name"));
          acc.setAccountType(resultSet.getString("account_type"));
          acc.setRemark(resultSet.getString("remark"));
          acc.setCreateTime(resultSet.getDate("create_time"));
          acc.setUpdateTime(resultSet.getDate("update_time"));
          acc.setUserId(resultSet.getInt("user_id"));
            return acc;
       });
        return account;
   }

测试方法

/**
  * 查询指定账户的记录详情,返回账户对象
  */
@Test
public void testQueryAccountById(){
    // 查询ID为1的账户记录的详情
    Account account =
accountDao.queryAccountById(1);
    System.out.println("账户详情:" +
account.toString());
}

多条件查询用户账户记录

/**
  * 多条件查询指定用户的账户列表,返回账户集合
  * @param userId 用户Id
  * @param accountName 账户名称 (模糊查询)
  * @param accountType 账户类型
  * @param createTime 账户创建时间
  * @return
  */
@Override
public List<Account> queryAccountsByParams(Integer userId, String
accountName, String accountType,String createTime) {
    String sql = "select * from tb_account where user_id = ? ";
    List<Object> params = new ArrayList<>();
    params.add(userId);
    // 判断是否有条件查询
    // 如果账户名称不为空,通过账户名称模糊匹配
    if (StringUtils.isNotBlank(accountName)) {
        sql += " and account_name like concat('%',?,'%') ";
        params.add(accountName);
   }
    // 如果账户类型不为空,通过指定类型名称查询
    if (StringUtils.isNotBlank(accountType)) {
        sql += " and account_type = ? ";
        params.add(accountType);
   }
    // 如果创建时间不为空,查询创建时间大于指定时间的账户记录
    if (StringUtils.isNotBlank(createTime)) {
        sql += " and create_time > ? ";
        params.add(createTime);
   }
    // 将集合转换成数组
    Object[] objs = params.toArray();
    List<Account> accountList =jdbcTemplate.query(sql, objs, (resultSet,rowNum) -> {
              Account acc = new Account();
              acc.setAccountId(resultSet.getInt("account_id"));
              acc.setMoney(resultSet.getDouble("money"));
              acc.setAccountName(resultSet.getString("account_name"));
              acc.setAccountType(resultSet.getString("account_type"));
              acc.setRemark(resultSet.getString("remark"));
        	  acc.setCreateTime(resultSet.getDate("create_time"));
              acc.setUpdateTime(resultSet.getDate("update_time"));
              acc.setUserId(resultSet.getInt("user_id"));
                return acc;
       });
    
    return accountList;
}

测试方法

/**
  * 多条件查询用户的账户记录,返回账户集合
  */
@Test
public void testQueryAccountByParams(){
    // 查询用户的账户列表
    List<Account> accountList =
accountDao.queryAccountsByParams(3,null,null,null);
    // 通过指定条件查询用户的账户列表
    List<Account> accountList02 =
accountDao.queryAccountsByParams(3,"张",null,null);
    System.out.println(accountList.toString());
    System.out.println(accountList02.toString());
}

账户记录更新实现

更新账户记录

/**
  * 更新指定账户记录,返回受影响的行数
  * @param account
  * @return
  */
@Override
public int updateAccountById(Account account) {
     String sql = "update tb_account set account_name = ?,account_type = ?, money = ? ,remark = ?,user_id= ? ,update_time = now() where account_id = ? ";
     Object[] objs =
{account.getAccountName(),account.getAccountType(),account.getMoney(),
account.getRemark(),account.getUserId(),account.getAccountId()};
     return jdbcTemplate.update(sql,objs);
}

测试方法

/**
  * 更新指定账户记录,返回受影响的行数
  */
@Test
public void testUpdateAccount(){
    // 准备要修改的数据
    Account account = new Account("张三1","建设银行1",500.0,"零花钱加倍",1);
    account.setAccountId(1);
    int row =accountDao.updateAccountById(account);
    System.out.println("修改账户返回受影响的行数:" +row);
}

批量更新账户记录

/**
  * 批量新账户记录,返回受影响的行数
  * @param accounts
  * @return
  */
@Override
public int updateAccountBatch(List<Account> accounts) {
    String sql = "update tb_account set account_name = ?, account_type = ?,  money = ? ,remark = ?,user_id= ? ,update_time = now()  where account_id = ? ";
    int rows = jdbcTemplate.batchUpdate(sql, new
BatchPreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement ps, int i) throws SQLException {
     // 设置参数
          ps.setString(1,accounts.get(i).getAccountName());
          ps.setString(2,accounts.get(i).getAccountType());
          ps.setDouble(3,accounts.get(i).getMoney());
          ps.setString(4,accounts.get(i).getRemark());
          ps.setInt(5,accounts.get(i).getUserId());
          ps.setInt(6,accounts.get(i).getAccountId());
       }
        @Override
        public int getBatchSize() {
            return accounts.size();
       }
   }).length;
    return rows;
}        

测试方法

/**
  * 批量更新账户记录,返回受影响的行数
  */
@Test
public void testUpdateAccountBatch(){
    // 准备要修改的数据
    Account account = new Account("a3","建设银行3",300.0,"零花钱加倍3",3);
    account.setAccountId(3);
    Account account2 = new Account("a4","建设银行4",400.0,"零花钱加倍4",3);
    account2.setAccountId(4);
    List<Account> accountList = new ArrayList<>();
    accountList.add(account);
    accountList.add(account2);
    int rows =accountDao.updateAccountBatch(accountList);
    System.out.println("批量修改账户记录返回受影响的行数:" + rows);
}

账户记录删除实现

删除账户记录

/**
  * 删除账户记录,返回受影响的行数
  * @param accountId
  * @return
  */
@Override
public Integer deleteAccoutById(Integer accountId) {
    String sql = "delete from tb_account where account_id= ? ";
    Object[] objs = {accountId};
    return jdbcTemplate.update(sql,objs);
}

测试方法

/**
  * 删除账户记录,返回受影响的行数
  */
@Test
public void testDeleteAccount(){
    // 删除ID为1的账户记录
    int row = accountDao.deleteAccoutById(1);
    System.out.println("删除账户记录返回受影响的行数:" + row);
}

批量删除账户记录

/**
   * 批量删除账户记录,返回受影响的行数
   * @param ids
   * @return
   */
@Override
public int deleteAccountBatch(Integer[] ids) {
    String sql = "delete from tb_account where
account_id = ?";
    int row = jdbcTemplate.batchUpdate(sql, new
BatchPreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement ps, int i) throws SQLException {
            ps.setInt(1,ids[i]);
       }
        @Override
        public int getBatchSize() {
            return ids.length;
       }
   }).length;
    return row;
}

测试方法

/**
  * 批量删除账户记录,返回受影响的行数
  */
@Test
public void testDeleteAccountBatch(){
    // 删除多个id的账户记录
    Integer[] ids = new Integer[]{2,3};
    int rows =accountDao.deleteAccountBatch(ids);
    System.out.println("批量删除账户记录返回受影响的行数:" + rows);
}

Spring事务控制

转账场景模拟实现

接口方法定义

    /**
     * 支出
     * @param accountId
     * @param money
     * @return
     */
    public int outAccount(Integer accountId, Double money);

    /**
     * 收入
     * @param accountId
     * @param money
     * @return
     */
    public int inAccount(Integer accountId, Double money);

}

实现对应接口

​ 对于转账涉及到双方账户以及对应转账金额,所以有入账和出账两个方法。

/**
     * 支出
     * @param accountId
     * @param money
     * @return
     */
    @Override
    public int outAccount(Integer accountId, Double money) {
        String sql = "update tb_account set money = money - ? where account_id = ? ";
        Object[] objs = {money, accountId};
        return jdbcTemplate.update(sql,objs);
    }

    /**
     * 收入
     * @param accountId
     * @param money
     * @return
     */
    @Override
    public int inAccount(Integer accountId, Double money) {
        String sql = "update tb_account set money = money + ? where account_id = ? ";
        Object[] objs = {money, accountId};
        return jdbcTemplate.update(sql,objs);
    }

转账方法实现

package com.xxxx.service;

import com.xxxx.dao.IAccountDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service
public class AccountService {

    @Resource
    private IAccountDao accountDao;

    /**
     * 转账业务操作
     * @param outId 支出账户
     * @param inId  收入账户
     * @param money 金额
     * @return  1=成功,0=失败
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public int toupdateAccountByTranfer(Integer outId, Integer inId, Double money){
        int code = 0; // 成功或失败  1=成功,0=失败

        /**
         * 账户A向账户B转账100元
         *  账户A:金额-100
         *  账户B:金额+100
         */
        // 账户A 支出,修改账户金额,返回受影响的行数
        int outRow = accountDao.outAccount(outId,money);

        int i = 1/0;

        // 账户B 收入,修改账户金额,返回受影响的行数
        int inRow = accountDao.inAccount(inId, money);

        // 如果支出和收入两个操作都执行成功,表示转账成功
        if (outRow == 1 && inRow == 1) {
            code = 1; // 成功
        }

        return code;
    }

}

​ 仔细思考代码会发现,在程序运行中无法保证 service 层业务代码不发生异常,如果通过 jdbc 的方式处理事务,此时需要手动方式控制事务,这样的话凡是涉及到事务控制的业务方法均需要开发人员手动来进行事务处理,无法满足生产的需要。

Spring事务概念

事务的四大特性(ACID)

  • 原子性(Atomicity)

​ 共生死,要么全部成功,要么全部失败!

  • 一致性(Consistency)

​ 事务在执行前后,数据库中数据要保持一致性状态。(如转账的过程 账户操作后数据必须保持一致)

  • 隔离性(Isolation)

​ 事务与事务之间的执行应当是相互隔离互不影响的。(多个角色对统一记录进行操作必须保证没有任何干扰),当然没有影响是不可能的,为了让影响级别降到最低,通过隔离级别加以限制:

  1. READ_UNCOMMITTED (读未提交)

​ 隔离级别最低的一种事务级别。在这种隔离级别下,会引发脏读、不可重复读和幻读。

  1. READ_COMMITTED (读已提交)

​ 读到的都是别人提交后的值。这种隔离级别下,会引发不可重复读和幻读,但避免了脏读。

  1. REPEATABLE_READ (可重复读)

​ 这种隔离级别下,会引发幻读,但避免了脏读、不可重复读。

  1. SERIALIZABLE (串行化)

​ 最严格的隔离级别。在Serializable隔离级别下,所有事务按照次序依次执行。脏读、不可重复读、幻读都不会出现。

  • 持久性(Durability)

​ 事务提交完毕后,数据库中的数据的改变是永久的。

Spring 事务核心接口

image-20241231154937779

​ Spring 并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给 Hibernate 或者 JTA 等持久化机制所提供的相关平台框架的事务来实现。

​ Spring 事务管理器的接口org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring 为各个平台如 JDBC、Hibernate 等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下:

public interface PlatformTransactionManager(){
    // 由 TransactionDefinition 得到TransactionStatus 对象
    TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
    // 提交
    void commit(TransactionStatus status) throws TransactionException;
    // 回滚
    void rollback(TransactionStatus status) throws TransactionException;
 }

​ 从这里可知具体的具体的事务管理机制对 Spring 来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以 Spring事务管理的一个优点就是为不同的事务 API 提供一致的编程模型,如 JTA、JDBC、Hibernate、JPA。下面分别介绍各个平台框架实现事务管理的机制。

JDBC事务

​ 如果应用程序中直接使用 JDBC 来进行持久化,此时使用DataSourceTransactionManager 来处理事务边界。为了使用DataSourceTransactionManager,需要使用如下的 XML 将其装配到应用程序的上下文定义中:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 <property name="dataSource" ref="dataSource"/>
</bean>

​ 实际上,DataSourceTransactionManager 是通过调用java.sql.Connection 来管理事务,而后者是通过 DataSource 获取到的。通过调用连接的 commit() 方法来提交事务,同样,事务失败则通过调用 rollback() 方法进行回滚。

Hibernate事务

​ 如果应用程序的持久化是通过 Hibernate 实现的,那么需要使用HibernateTransactionManager。对于 Hibernate3,需要在Spring 上下文定义中添加如下的声明:

<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
 <property name="sessionFactory"ref="sessionFactory" />
</bean>

​ sessionFactory 属性需要装配一个 Hibernate 的 session 工厂,HibernateTransactionManager 的实现细节是它将事务管理的职责委托给 org.hibernate.Transaction 对象,而后者是从 HibernateSession 中获取到的。当事务成功完成时,HibernateTransactionManager 将会调用 Transaction 对象的commit() 方法,反之,将会调用 rollback() 方法。

Java持久化API 事务(JPA)

​ Hibernate 多年来一直是 Java 持久化标准,但是现在 Java 持久化API 作为真正的 Java 持久化标准进入大家的视野。如果你计划使用JPA 的话,那你需要使用 Spring 的 JpaTransactionManager 来处理事务。你需要在 Spring 中这样配置JpaTransactionManager:

<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
 <property name="sessionFactory"ref="sessionFactory" />
</bean>

​ JpaTransactionManager 只需要装配一个 JPA 实体管理工厂(javax.persistence.EntityManagerFactory 接口的任意实现)。JpaTransactionManager 将与由工厂所产生的 JPA EntityManager合作来构建事务。

Java原生API事务

​ 如果应用程序没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),此时需要使用JtaTransactionManager:

<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
 <property name="transactionManagerName" value="java:/TransactionManager" />
</bean>

JtaTransactionManager 将事务管理的责任委托给javax.transaction.UserTransaction 和javax.transaction.TransactionManager 对象,其中事务成功完成通过UserTransaction.commit() 方法提交,事务失败通过UserTransaction.rollback() 方法回滚。

Spring事务控制配置

​ 通过 jdbc 持久化事务,对于事务配置实现由两种方式即:Xml 配置,注解配置。

XML配置

Spring 事务配置流程:
    1. 添加事务与AOP的命名空间
    2. 开启AOP代理
    3. 配置事务管理器
    4. 配置事务通知
    5. 配置AOP
添加命名空间

在spring.xml配置文件的添加事务和aop的命名空间

事务

xmlns:tx="http://www.springframework.org/schema/tx"

 http://www.springframework.org/schema/tx
 http://www.springframework.org/schema/tx/spring-tx.xsd

AOP

xmlns:aop="http://www.springframework.org/schema/aop"

 http://www.springframework.org/schema/aop
 http://www.springframework.org/schema/aop/spring-aop.xsd

配置如下

<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"
      xmlns:aop="http://www.springframework.org/schema/aop"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      https://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context.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">
设置aop代理
<!-- 开启AOP代理 -->
<aop:aspectj-autoproxy />
配置事务管理器
<!-- 事务管理器定义 -->
<bean id="txManager"class="org.springframework.jdbc.datasource.DataS
ourceTransactionManager">
    <!--数据源 -->
    <property name="dataSource" ref="dataSource">
</property>
</bean>
配置事务相关通知

​ 一般来说增删改方法 propagation=Required,对于查询方法使用read-only=“true”

<!-- 配置事务通知   transaction-manager属性表示这个事
务通知是哪个事务管理器管理的-->
<!--
    tx:method的属性:
       name
			是必须的,表示与事务属性关联的方法名(业务方法名),对切入点进行细化。
	   		通配符(*)可以用来指定一批关联到相同的事务属性的方法。
       			如:'get*'、'handle*'、'on*Event'等等.
       propagation
			不是必须的,默认值是REQUIRED
       		表示事务传播行为, 包括:
				REQUIRED,SUPPORTS,MANDATORY,NEVER
				REQUIRES_NEW,NOT_SUPPORTED,NESTED
       isolation
			不是必须的,默认值DEFAULT
            表示事务隔离级别(数据库的隔离级别)
       timeout
			不是必须的,默认值-1(永不超时)
            表示事务超时的时间(以秒为单位)
       read-only
			不是必须的,默认值false不是只读的
            表示事务是否只读
       rollback-for
			不是必须的
            表示将被触发进行回滚的 Exception(s);以逗号分开。
            	如:'com.foo.MyBusinessException,ServletException'
       no-rollback-for
			不是必须的
            表示不被触发进行回滚的 Exception(s);以逗号分开。
            	如:'com.foo.MyBusinessException,ServletException'
            	任何 RuntimeException 将触发事务回滚
    -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- 定义什么方法需要使用事务处理:以add update delete query 开头的方法都使用事务 -->
        <tx:attributes>
            <!-- name属性代表的是方法名(或方法匹配) -->
            <!-- 匹配以 add 开头的所有方法均加入事务 -->
            <tx:method name="add*" propagation="REQUIRED"/>
            <!-- 匹配以 update 开头的所有方法均加入事务 -->
            <tx:method name="update*" propagation="REQUIRED"/>
            <!-- 匹配以 delete 开头的所有方法均加入事务 -->
            <tx:method name="delete*" propagation="REQUIRED"/>
             <!-- 匹配以 query 开头的所有方法均加入事务 -->
            <tx:method name="query*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
事务传播行为介绍:
    @Transactional(propagation=Propagation.REQUIRED)
       如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)

    @Transactional(propagation=Propagation.NOT_SUPPORTED)
        容器不为这个方法开启事务

    @Transactional(propagation=Propagation.REQUIRES_NEW)
        不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务

    @Transactional(propagation=Propagation.MANDATORY)
         必须在一个已有的事务中执行,否则抛出异常

    @Transactional(propagation=Propagation.NEVER)
    	 必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY 相反)

    @Transactional(propagation=Propagation.SUPPORTS)
            如果其他 bean 调用这个方法,在其他bean中声明事务,那就用事务.
            如果其他 bean 没有声明事务,那就不用事务.

    @Transactional(propagation=Propagation.NESTED)
		支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。
配置aop
<!-- aop 切面定义 (切入点和通知) -->
<aop:config>
    <!-- 设置切入点 设置需要被拦截的方法 -->
 <aop:pointcut expression="execution(* com.xxxx.service..*.*(..) )" id="cut" />
    <!-- 设置通知 事务通知 -->
    <aop:advisor advice-ref="txAdvice" pointcutref="cut"/>
<aop:adviso

注解配置

配置事务管理器
<!-- spring 注解式事务声明 -->
<!-- 事务管理器定义 -->
<bean id="txManager"class="org.springframework.jdbc.datasource.DataSo
urceTransactionManager">
 <property name="dataSource" ref="dataSource">
</property>
</bean>
配置注解支持
<tx:annotation-driven transaction-manager="txManager"/>
方法上加入事务注解

​ Service 方法上在需要添加事务的方法上加入事务注解

@Override
@Transactional(propagation=Propagation.REQUIRED)
public void saveUser(String userName,String userPwd){
    User user1=new User();
    user1.setUserName(userName);
    user1.setUserPwd(userPwd);
    userDao.saveUser(user1);
    userDao.delUserById(2);
}

备注:

默认 spring事务只在发生未被捕获的runtimeexcetpion时才回滚。

spring aop 异常捕获原理:

被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认情况下aop只捕获runtimeexception的异常,但可以通过配置来捕获特定的异常并回滚,换句话说在service的方法中不使用try catch或者catch中最后加上throw new RunTimeexcetpion(),这样程序异常时才能被aop捕获进而回滚.

© 2024 - 2025 雪中烛

 

在线工具资源网站

在线工具官网

ProcessOn:👉ProcessOn在线作图

腾讯文档:👉腾讯文档

FlowUs 息流:👉FlowUs 息流

Mermaid Live Editor:👉在线流程图和图表编辑器 - 美人鱼实时编辑器

ME2在线工具(加密工具):👉ME2在线工具-在线开发学习生活工具箱

Textln:👉Textln文本智能云平台-通用文本解析

MinerU:👉MinerU数据提取工具

Crontab:👉Cron Expression Examples - Crontab.guru

正则表达式测试网站:👉regex101: build, test, and debug regex

技术资源网站

技术资源官网

Spring:👉Spring官网

MyBatis中文网 :👉MyBatis 中文网

Redis中文网:👉Redis中文网

Zookeeper:👉Apache ZooKeeper官网

Raft算法:👉Raft Consensus Algorithm官网

Hadoop:👉Apache Hadoop官网

Hive:👉Apache Hive官网

Phoenix:👉Apache Phoenix官网

Bootstrap中文网:👉Bootstrap中文网

Element:👉组件 | Element

Layui :👉Layui - 极简模块化前端 UI 组件库(官方文档)

FreeMarker :👉FreeMarker 中文官方参考手册

ztree树插件: 👉zTree – jQuery 树插件官网

x File Storage:👉x File Storage官网