Spring学习记录

为什么要学Spring?

在回答这个问题时,我们先来看看现在的Java程序是如何实现的,以最简单的服务层与持久层为例,其遵循接口与具体实现类的这种方式:

Service层接口:BookService.java

package service;
public interface BookService {
    public void save();
}

Service层具体实现BookServiceImpl.java

package service.impl;

import dao.BookDao;
import dao.impl.BookDaoImpl;
import service.BookService;
public class BookServiceImpl implements BookService {
    BookDao bookdao=new BookDaoImpl();
    public void save() {
        System.out.print("执行service...\n");
        bookdao.save();
    }
}

Dao接口:BookDao.java

package dao;
public interface BookDao {
    public void save();

}

BookDao接口实现类 BookDaoImpl.java

package dao.impl;
import dao.BookDao;
public class BookDaoImpl implements BookDao {
    public void save(){
        System.out.print("执行dao...");
    }
}

定义一个main方法来执行,看下结果:

import service.BookService;
import service.impl.BookServiceImpl;

public class Test {
    public static void main(String[] args) {
        BookService bs=new BookServiceImpl();
        bs.save();
    }
}

在这里插入图片描述
可以看到这是Java的实现方式,这存在什么问题呢?
如果这时我们的业务层实现我们重写了的话,那么原本在Service层的定义也要发生改变,造成这样问题的原因便是由于我们在类里定义了其他类的实现,这就导致代码耦合度过高。

在这里插入图片描述
那么该如何解决呢,其实很简单,那就让代码这不要出现其他类的实现呗,即只定义一个接口,但这样会报错的。
在这里插入图片描述

而Spring说,不就是想要一个对象吗?我来给提供,从而实现了解耦。

Spring是如何解耦的?

前面已经说到,造成Java项目耦合度过高的原因便是由于在类中定义了其他类的实现(对象),那么Spring是如何做的呢?
在使用对象时,不再通过程序自己new一个对象,而是通过外部提供对象,这里的外部即为Spring容器,又称IOC容器 ,而实现这种方法的思想被称为IOCInversion of control,控制反转)指的是生成对象的控制权限被反转了。
IOC容器赋值对这些对象的创建与管理,这些对象在IOC容器中被称为Bean

在这里插入图片描述

此时再次运行还是会出错的,因为只是创建了一个Service对象,而Service对象的运行是需要依赖于Dao对象的,这个问题该如何解决呢,Spring提供了一种思想DI (Dependency Injection,依赖注入),将SeriveDao的依赖关系进行绑定。
最终达到的效果:在使用对象时,不仅可以从IOC容器中直接获取,还可以获得其依赖的对象。

创建Spring的配置文件之前,需要先导包Spring-context
随后new一个XML 配置文件,选择Spring配置文件即可。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    1.创建Spring容器之前需要先导包:Spring-Context-->
<!--    2.创建Spring配置文件。-->
<!--    3.配置bean。id代表名字,class代表要给bean定义的类型-->
    <bean id="bookservice" class="service.impl.BookServiceImpl"></bean>
    <bean id="bookdao" class="dao.impl.BookDaoImpl"></bean>
</beans>

随后我们要获取IOC容器,然后获取容器内的对象:

import dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.BookService;
public class TestSpring {
    public static void main(String[] args) {
        //1.要想获得Bean,应该先得到IOC容器
        ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
//        BookDao bdao= (BookDao) ctx.getBean("bookdao");
//        bdao.save();
        BookService bs= (BookService) ctx.getBean("bookservice");
        bs.save();
    }
}

在只获取BookDao的对象时,执行没有问题,但是在获取BookSerive的的对象时,却报错了:
在这里插入图片描述
造成这个原因便是由于我们只是获取了BookService对象,但该对象的实现中我们已经去除了原本new一个BookDao对象的代码:

package service.impl;
import dao.BookDao;
import dao.impl.BookDaoImpl;
import service.BookService;
public class BookServiceImpl implements BookService {
    //BookDao bookdao=new BookDaoImpl();
    BookDao bookdao;//这里只有一个BookDao接口,没有new其具体实现
    public void save() {
        System.out.print("执行service...\n");
        bookdao.save();
    }
}

那么难道是要我们再获取一下BookDao对象吗,不需要的,前面已经说过,Spring另一个功能称为依赖注入。具体该怎么做呢?很简单,只需要在配置文件中使用ref参数即可。
如下面代码所示,将bookservice这个Bean内加一个属性property,名字叫bookdao,然后ref(依赖)前面创建的bookdao这个Bean即可。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    1.创建Spring容器之前需要先导包:Spring-Context-->
<!--    2.创建Spring配置文件。-->
<!--    3.配置bean。id代表名字,class代表要给bean定义的类型-->
    <bean id="bookdao" class="dao.impl.BookDaoImpl"></bean>
    <bean id="bookservice"  class="service.impl.BookServiceImpl">
        <property name="bookdao" ref="bookdao"></property>
    </bean>
</beans>

此时我们运行会发生报错:大致意思是在创建bookservice时需要依赖bookdao,尽管生成了,但没有对应的写入方法(set方法)造成这个问题的原因是什么呢?因为现在的在bookservice.java中的成员变量bookdao是没有值的,即没有将Spring创建的bookdao与这个bookservice.java中的bean联系起来。

Error creating bean with name 'bookservice' defined in class path resource [applicationContext.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'bookdao' of bean class [service.impl.BookServiceImpl]: Bean property 'bookdao' is not writable or has an invalid setter method.

在BookServiceImpl.java中添加对应的set方法:

 public void setBookdao(BookDaoImpl bookdao) {
        this.bookdao=bookdao;
    }

再次运行就OK了,这里要明确在xml配置文件中的含义,第一个bookdao是名字要与BookServiceImpl.java中的成员变量bookdao一致,或者叫Bookdao也可,但叫BookDao则会报错,这可能是由于set方法起名就叫Bokkdao的原因,第二个bookdao,即依赖的bookdao是要与xml文件中生成的bookdao这个Bean一致,它会作为参数传入到set方法中。

<property name="bookdao" ref="bookdao"></property>

至此,Spring最大的两个特点:控制反转与依赖注入便讲解完成了。

Bean的配置

前面已经学习了Bean的基础配置,如通过id来指定名字,通过ref来引用其他Bean,此次则是对Bean的别名、作用范围的学习。
Bean的别名,由于每个人的命名习惯不同,因此Spring提出使用Bean的别名来标识同一个Bean,从而方便使用。
如下修改后的配置文件,使用name来表示别名,多个别名可以通过逗号、分号以及空格来分隔,但依旧建议使用id来表示。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    1.创建Spring容器之前需要先导包:Spring-Context-->
<!--    2.创建Spring配置文件。-->
<!--    3.配置bean。id代表名字,class代表要给bean定义的类型-->
    <bean id="bookdao" class="dao.impl.BookDaoImpl"></bean>
    <bean id="bookservice"  name="service,books1;bookl books" class="service.impl.BookServiceImpl">
        <property name="bookdao" ref="bookdao"></property>
    </bean>
</beans>

在这里插入图片描述

Bean的作用范围,即创造的Bean到底是单例对象还是多例对象呢,我们从Spring容器中获取两个Bean并输出其地址,发现两者的地址相同,这说明Spring创建的Bean是单例的。

BookService bs= (BookService) ctx.getBean("service");
BookService bs1= (BookService) ctx.getBean("service");
System.out.print(bs+"\n");
System.out.print(bs1);

在这里插入图片描述
那么如果我们想让创建的Bean是多例的呢,这就涉及到Bean的作用范围了,Spring为我们提供了一个参数:scope

<bean id="bookservice"  name="service,books1;bookl books" class="service.impl.BookServiceImpl" scope="prototype">
        <property name="bookdao" ref="bookdao"></property>
</bean>

在这里插入图片描述

事实上,Spring创建的单例对象都是可以复用的,可以大大的减缓了数据存储量。那么,那些对象不适合使用Spring来创建单例对象呢,那些封装实体的域对象,比如其内成员变量有给定值时。在大多数情况下,表现层,业务层以及工具层对象都是可以被复用的。

Spring中的Bean是如何创建的?

在Java中,对象都是构造方法new出来的,那么Spring是如何创建的对象呢
事实上,Spring中的对象也是通过构造方法创建的。
我们重写了BookDaoImpl的构造方法,发现该构造方法被执行了。

public class BookDaoImpl implements BookDao {
    public  BookDaoImpl(){
        System.out.print("bookdao constructor running...\n");
    }
    public void save(){
        System.out.print("执行dao...");
    }
}

在这里插入图片描述
这说明Spring也是通过构造方法来构建Bean的。
不仅如此,当我们将构造方法私有后,已经可以调用,这是通过反射实现的。

private  BookDaoImpl(){
        System.out.print("bookdao constructor running...\n");
    }

此外,如果我们此时将无参构造方法中添加一个参数,此时则会创建失败,这证明Spring是通过无参构造方法来创建Bean的。

编写一个类时没有添加无参构造方法,那么编译器会自动添加无参构造方法;(如果自己添加构造函数,无论有参数或是没参数,默认构造函数都将无效)
编写时添加了有参构造方法而未添加无参构造方法,那么编译器只认有参构造方法而不会默认添加无参构造方法!
如果需要使用无参构造方法,一定要在类里面添加

因此,这个无参构造函数是被默认创建的,即不需要我们自己创建。

Spring使用工厂创建Bean

静态工厂创建Bean

(这种方法已经过时了,但要知道,通过静态工厂创建对象可以解耦)

关于Java中的工厂,我们需要知道:

实例化对象不使用new,用工厂方法创建对象 使用工厂统一管理对象的创建,将调用者跟实现类解耦

在这里插入图片描述

实例工厂创建Bean

在这里插入图片描述

然而,这种实例工厂创建Bean的方式有以下问题:

在这里插入图片描述
因此,Spring提出了FactoryBean,即Spring给创建一个工厂Bean,我们直接拿到这个工厂Bean来获取对象即可。

package dao.impl;
import org.springframework.beans.factory.FactoryBean;
public class BookDaoImplFactoryBean implements FactoryBean<BookDaoImpl> {
    @Override
    public BookDaoImpl getObject() throws Exception {
        return new BookDaoImpl();
    }
    @Override
    public Class<?> getObjectType() {
        return BookDaoImpl.class;//字节码
    }
    @Override
    public boolean isSingleton() {//是否是单例
        return true;
    }
}
<bean id="bookdao" class="dao.impl.BookDaoImplFactoryBean">
</bean>

在这里插入图片描述

Bean的生命周期

简单来讲,Bean的生命周期即为从Bean的创建到销毁的过程,为什么用它呢,事实上,Bean在创建前与销毁前一般都是需要完成一系列操作的,我们可以模拟一下Bean的创建与销毁操作

package dao.impl;
import dao.BookDao;
public class BookDaoImpl implements BookDao {
    public BookDaoImpl(){
        System.out.print("bookdao constructor running...\n");
    }
    public void save(){
        System.out.print("执行dao...");
    }
    public void init(){
        System.out.print("init...");
    }
    public void destory(){
        System.out.print("destory..");
    }
}

这个init与destory方法是我们自己写的,是不会执行的,因此我们还需要在配置文件中配置一下:指明初始化方法与销毁方法

<bean id="bookdao" class="dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"></bean>

此时运行代码发现只执行了init方法,这很容易理解,因为我们的程序是运行在JVM虚拟机上的,创建这个过程被执行了,但程序在执行完后,就直接关闭虚拟机了,根本就没有执行销毁操作。
怎么办呢?我们需要在程序中进行主动销毁,因为在Spring中,是使用关闭Spring容器来销毁Bean的,因为在Spring容器中的Bean是一同创建的,销毁那么也就一起销毁。
那么如何销毁容器呢,使用的是close方法。

在这里插入图片描述

在这里插入图片描述

但这种方式太过暴力,现在多是采用注册关闭钩子的方式来销毁Bean的,这种注册钩子与close的区别在于,注册钩子放在任何位置都可以,即告诉程序只要在退出JVM之前执行一下就可以,而close则只能在所有的Bean操作都完成后才能够执行,否则就会报错。

ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bdao= (BookDao) ctx.getBean("bookdao");
        ctx.registerShutdownHook();

以上是我们自己生成的初始化与销毁方法,事实上,Spring为了我们操作方便,为我们提供了对应的方法,只不过我们需要按照他的标准来操作:
我们需要实现两个接口:InitializingBean, DisposableBean

package service.impl;
import dao.BookDao;
import dao.impl.BookDaoImpl;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import service.BookService;
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
    //BookDao bookdao=new BookDaoImpl();
    BookDao bookdao;
    public void save() {
        System.out.print("执行service...\n");
        bookdao.save();
    }
    public void setBookdao(BookDaoImpl bookdao) {
        this.bookdao=bookdao;
    }
    @Override
    public void destroy() throws Exception {
        System.out.print("bookservice destory..\n");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.print("bookservice init..\n");
    }
}

注意,尽管在下面的代码中我们只使用了Bookdao的Bean,但事实上在加载xm文件时,Service的Bean也是被创建的。

ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bdao= (BookDao) ctx.getBean("bookdao");
bdao.save();
ctx.registerShutdownHook();

在这里插入图片描述
同时博主还发现,当BookService的Bean被指定为多例时,其不执行init和destory过程,此时,只有当我们调用了BookService的Bean时才会执行。

ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bdao= (BookDao) ctx.getBean("bookdao");
bdao.save();
BookService bs= (BookService) ctx.getBean("service");
ctx.registerShutdownHook();

并且,其依旧没有执行BookService的destory
在这里插入图片描述
单例时的结果:
在这里插入图片描述
这是由于什么呢,因为Spring提供的方法afterPropertiesSet()叫做属性设置之后,而多例对象是需要调用时给定属性的,此时不调用也就不会给定属性,自然也就不会执行init了。
Spring中的Bean遵循下面的过程:
其中创建对象就相当于new对象。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/466807.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mysql笔记:22. 事务隔离级别的一种通俗讲解

事务隔离级别&#xff0c;是为了解决多个并行事务竞争导致的数据安全问题的一种规范。具体来说&#xff0c;多个事务竞争可能会产生三种不同的现象。假设有两个事务T1、T2同时执行&#xff0c;有如下三种不同的情形&#xff1a; T1可能会读到T2未提交的数据&#xff0c;但是未…

粤嵌6818开发板通过MobaXterm使用SSH连接开发板

链接&#xff1a;https://pan.baidu.com/s/18ISP4Ub1HtQx6jCvTQTUHw?pwdfjmu 提取码&#xff1a;fjmu 1.把SSH_config.tar.bz 下载到开发板中 2.解压 SSH_config.tar.bz 解压命令&#xff1a;tar -xzvf SSH_config.tar.bz 3.配置SSH 进入SSH/openssh目录&am…

关于Zookeeper分布式锁

背景 之前说到分布式锁的实现有三种 1、基于数据库实现的分布式锁 2、Redis分布式锁 3、Zookeeper分布式锁 前者redis分布式锁博客已具体介绍&#xff0c;此博客最终决定补齐关于Zookeeper分布式锁的实现原理。 简述 Zoopkeeper&#xff0c;它是一个为分布式的协调服务&…

Vertex cover preprocessing for influence maximization algorithms

Abstract 影响力最大化问题是社交网络分析中的一个基本问题&#xff0c;其目的是选择一小组节点作为种子集&#xff0c;并在特定的传播模型下最大化通过种子集传播的影响力。本文研究了独立级联模型下影响力最大化算法中执行顶点覆盖作为预处理的效果。所提出的方法从主要计算过…

考研复习C语言进阶(4)

1. 为什么存在动态内存分配 我们已经掌握的内存开辟方式有&#xff1a; int val 20;//在栈空间上开辟四个字节 char arr[10] {0};//在栈空间上开辟10个字节的连续空间 但是上述的开辟空间的方式有两个特点&#xff1a; 1. 空间开辟大小是固定的。 2. 数组在申明的时候&#…

Stm32-使用TB6612驱动电机及编码器测速

这里写目录标题 起因一、电机及编码器的参数二、硬件三、接线四、驱动电机1、TB6612电机驱动2、定时器的PWM模式驱动电机 五、编码器测速1、定时器的编码器接口模式2、定时器编码器模式测速的原理3、编码器模式的配置4、编码器模式相关代码5、测速方法 六、相关问题以及解答1、…

软件测试入门基础

说到软件测试&#xff0c;那么首先得和没有基础的同学们&#xff0c;讲解一下&#xff0c;平时我们使用的那些app&#xff0c;比如淘宝&#xff0c;微信是怎么进行交互的呢&#xff1f;在淘宝上下个订单&#xff0c;按钮按出去为什么就能下单成功呢&#xff1f;微信看朋友圈&am…

组建对等网

一、概念 对等网络&#xff08;Peer-to-Peer, P2P&#xff09;是一种分布式网络架构&#xff0c;其中每个参与节点&#xff08;称为"对等体"或"节点"&#xff09;既可以作为客户端也可以作为服务器&#xff0c;直接与网络中的其他节点分享资源&#xff08…

windows上安装虚拟机及搭建Linux环境

虚拟机的安装 VMware Workstation Player(虚拟机)&#xff0c;下载网址如下: VMware Workstation Player | VMwarehttps://www.vmware.com/content/vmware/vmware-published-sites/us/products/workstation-player.html.html?srcWWW_Player7Pro_US_HPPromo_Introducing 进入网…

8.Python从入门到精通—Python 字符串,转义字符,字符串运算符

8.Python从入门到精通—Python 字符串,转义字符,字符串运算符 Python 字符串创建字符串访问字符串中的字符字符串切片字符串操作符字符串方法 Python 转义字符Python字符串运算符 Python 字符串 在 Python 中&#xff0c;字符串是一种基本数据类型&#xff0c;用于表示文本数据…

深度学习pytorch——基本数据类型创建Tensor(持续更新)

声明&#xff1a;本深度学习笔记基于课时18 索引与切片-1_哔哩哔哩_bilibili学习而来 All is about Tensor 定义&#xff1a;Tensors are simply mathematical objects that can be used to describe physical properties, just like scalars and vectors. In fact tensors a…

地址转换函数(ip地址在计算中的识别方式,ipv4与ipv6)

ip地址在计算中的识别方式 ip地址如192.168.3.103是字符串 在计算机中该字符串ip用整型保存并识别。 ipv4与ipv6 ipv4 有四组&#xff0c;每组一个字节&#xff0c;一共4x832位 ipv4一共有 2^32 42 9496 7296 个地址。 ipv6 IPv6是由八组&#xff0c;每组四位16进制数字…

Java:设计模式

文章目录 参考简介工厂模式简单工厂模式工厂方法模式抽象工厂模式总结 单例模式预加载懒加载线程安全问题 策略模式 参考 知乎 简介 总体来说设计模式分为三类共23种。 创建型模式&#xff0c;共五种&#xff1a;工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模…

【晴问算法】入门篇—贪心算法—区间选点问题

题目描述 给定n个闭区间&#xff0c;问最少需要确定多少个点&#xff0c;才能使每个闭区间中都至少存在一个点。 输入描述 输出描述 输出一个整数&#xff0c;表示最少需要确定的点的个数。 样例1输入 3 1 4 2 6 5 7输出 2 解释 至少需要两个点&#xff08;例如3和5&#xff…

Java基础夯实【进阶】——八股文【2024面试题案例代码】

1、Java当中什么是线程和进程 在Java中&#xff0c;线程和进程是两个非常重要的概念。进程可以被视为一个执行中的程序的实例&#xff0c;它拥有自己的内存空间和系统资源。而线程则是进程中的一个实体&#xff0c;由进程创建&#xff0c;并允许程序在同一时刻执行多个任务。J…

Jenkins实现CICD(4)_Jenkins和gitlab进行交互

文章目录 一、实现功能二、操作思路三、插件安装四、jenkins与gitlab集成配置2.1、需求2.2、gitlab生成 API认证token2.2.1、创建token 2.3、jenkins使用gitlab API通信2.3.1、创建凭据2.3.2、查看创建结果 2.4、jenkins 集成 Gitlab2.4.1、配置2.4.2、操作流程 参考&#xff1…

XDAG节点版本更新(0.6.5升级到0.7.0)

1、拉取最新的xdagj源码 mkdir /root/xdagj-0.7.0 && cd /root/xdagj-0.7.0 git clone https://github.com/XDagger/xdagj.git cd xdagj mvn clean package -Dmaven.test.skiptrue2、创建新的数据目录并解压程序包 mkdir /data/docker-compose/xdagj-7.0/bin -p cd /…

Linux使用git命令行教程

. 个人主页&#xff1a;晓风飞 专栏&#xff1a;数据结构|Linux|C语言 路漫漫其修远兮&#xff0c;吾将上下而求索 文章目录 git安装git仓库的创建.git 文件添加文件git 三板斧(add,commit,push)解释拓展git log.gitignore git安装 首先输入git --version看看有没有安装git 如…

STM32F4+薄膜压力传感器(FSR)AO模拟输出程序ADC模数转换器详解

前言&#xff1a;博主在使用STM32F4加薄膜压力传感器用来测量压力时&#xff0c;发现给的例程只有STM32F1系列的&#xff0c;而STM32F4系列库函数程序不太一致&#xff0c;博主实战解决了该问题&#xff0c;用STM32F4标准库开发。有关ADC模数转换器的详细知识点详情点击我的博文…

【深度长文】聊一聊 Java AbstractQueuedSynchronizer 以及在 ReentrantLock 中的应用

文章目录 AQS 原理简述内部数据结构公平锁 vs. 非公平锁ReentrantLock 非公平锁ReentrantLock 公平锁 AQS 源码分析加锁过程addWaiteracquireQueuedshouldParkAfterFailedAcquirecancelAcquire 解锁过程unparkSuccessor AbstractQueuedSynchronizer (AQS) 是 Java 并发包中&…