Spring基础(2w字---学习总结版)
目录
一、Spirng概括
1、什么是Spring
2、什么是容器
3、什么是IoC
4、模拟实现IoC
4.1、传统的对象创建开发
5、理解IoC容器
6、DI概括
二、创建Spring项目
1、创建spring项目
2、Bean对象
2.1、创建Bean对象
2.2、存储Bean对象(将Bean对象注册到容器中)
2.3、获取Bean对象
【1】使用ApplicationContext接口获取:
【2】使用BeanFactory获取Bean对象:
【3】(面试题)ApplicationContext 和 BeanFactory的区别
3、注解
3.1、五大类注解之间的关系
3.2、使用类注解简易地存储Bean对象
3.2.1、配置扫描路径(必备)
3.2.2、通过注解注册Bean对象及直接获取
3.3、五大类注解Bean对象的命名规则
3.4、使用方法注解@Bean
3.5、方法注解的Bean命名规则/Bean重命名
※特殊情况:方法注解@Bean不能修饰有参的方法
3.6、简易地获取Bean对象(对象装配/对象注入)
1)属性注入
2)Setter注入
3)构造方法注入
4)@Resource注解
3.7、多个同类型Bean对象的指定获取
三、Bean的作用域和生命周期
1、Bean作用域的理解
2、Bean的作用域
2.1、singleton作用域
2.2、prototype作用域
2.3、application作用域
2.4、request作用域
2.5、session作用域
2.6、websocket作用域
2.7、设置Bean对象作用域
3、Spring的执行流程
4、Bean的生命周期
一、Spirng概括
1、什么是Spring
🤔spring即spring Framework (spring框架),它作为一个开源的设计层面框架,解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。
一句话概括,spring是包含了众多工具方法轻量级的 IoC容器(关键)和面向切面(AOP)的开源容器(框架)!
2、什么是容器
容器说到底,就是用于容纳存储数据的一个(基本)装置。
像前面学的javeSE中的list/Map/泛型,都是一个集合,而集合是用于存储数据的,也就是说集合是一个数据存储的容器。
以泛型为例,Listlist,这是一个只能存储和取出String类型数据的集合。
而Tomcat是Web容器.,存储Web项目和Servlet类的容器
3、什么是IoC
IoC(inversion of control)控制(权)反转,IoC是Spring中最核心的一个关键功能(词)。
控制反转,也就是把控制权交给其他方
例如:
本来程序是由一个A类来控制B类的生命周期,在A类中要想使用B类就得new一个B类出来;
但控制反转后,就能把B类的生命周期交给其他人【如spring】控制,此时使用B类就不用在
程序中实例化一个B类对象,而是直接交由spring控制创建即可。
4、模拟实现IoC
4.1、传统的对象创建开发
此处创建一个手机对象程序为例,创建一个逐级依赖的对象关系;
手机(Phone) 依赖于 芯片 (Chip) ,而芯片依赖于 电池 (Battery)
此处的程序,想要运行Phone,就得创建Chin,因为彼此是依赖关系,之后初始化Chin,而Chin依赖于Battery,就得在Chin类中创建一个Battery类,从而才能调用使用Battery类。
①手机类(Phone)
public class Phone {private Chin chin;public Phone() {chin = new Chin();}public void run() {System.out.println("Phone-init");chin.init();}public static void main(String []args){Phone phone = new Phone();phone.run();}
}
②芯片类(Chip)
public class Chin {private Battery battery;public Chin() {this.battery = new Battery();}public void init(){System.out.println("Chin-init");battery.init();}
}
③电池类(Battery)
public class Battery {//默认电池的容量private int capacity = 100;public void init(){System.out.println("Battery-init; capacity:"+this.capacity);}
}
对于上述传统的程序来说,彼此类逐级依赖导致耦合度过高,如果后续对该Phone类中的某个部件进行调整修改,就会导致整个调用链上的类都要更改。
比如此处要修改Battery类中的capacity容量,但是如果要从Phone类中直接传递参数,逐级传参到Battery类,就很麻烦。
代码如下:
①Phone类:
public class Phone {private Chin chin;public Phone(int capacity) {chin = new Chin(capacity);}public void run() {System.out.println("Phone-init");chin.init();}public static void main(String []args){Phone phone = new Phone(200);phone.run();}
}
②Chin类
public class Chin {private Battery battery;public Chin(int capacity) {this.battery = new Battery(capacity);}public void init(){System.out.println("Chin-init");battery.init();}
}
③Battery类
public class Battery {private int capacity = 100;public Battery(int capacity) {this.capacity = capacity;}public void init(){System.out.println("Battery-init; capacity:"+this.capacity);}
}
上述的代码存在最致命的问题,就是底层代码增加或修改需求时,整个调用链上的所有代码都得发生改变。
而导致这种问题的原因,是我们在每一个类的构造方法中处理下一层类的依赖关系,都是自己手动new创建一个实例对象,是主动创建的。
由自己创建的下级类,当下级类发生修改时,此处的创建实例操作也得进行修改。
要解决这个问题,就可以采用控制(权)反转,使用注入的方式,将所需要的参数由本来的从上往下传递创建实例,转为从下往上传递;
原自己创建类,当下级类发生改变时,当前的类也要进行修改;改为注入的方式后,下级类发生改变,当前类就不需要再进行代码修改了。
这就能完成代码的解耦(降低耦合性,耦合性也可以换⼀种叫法叫程序相关性。好的程序代码的耦合性(代码之间的相关性)是很低的,也就是代码之间要实现解耦。)
此处也可以理解成,当客户要求产品进行参数修改时,不再又我们自己调整,而是外包出去交由第三方处理,因此我们就无需出力,照样按原来的流程进行传递即可。
控制权反转后的代码:
①NewPhone类:
public class NewPhone {private NewChin newChin;public NewPhone(NewChin newChin) {this.newChin = newChin;}public void run(){System.out.println("NewPhone_init");newChin.init();}
}
②NewChin类:
public class NewChin {private NewBattery newBattery;public NewChin(NewBattery newBattery) {this.newBattery = newBattery;}public void init(){System.out.println("NewChin_init");newBattery.init();}
}
③NewBattery类:
public class NewBattery {private int capacity = 100;public NewBattery(int capacity) {this.capacity =capacity;}public void init(){System.out.println("NewBattery_init;capacity:"+capacity);}
}
④启动类:
由原来自己手动在类中创建下级类,转为注入方式(控制权反转),把控制权转交其他类(此处以App类为例)处理,再由该类调用传参。
此处可以很明显地观察到反转的特性,由原来创建Phone类依次依赖往下控制创建Battery类,【反转】转为 根据参数()创建NewBattery类,再往后依次注入到上级对象。
通⽤程序的实现代码,类的创建顺序是反的,传统代码是 Phone 控制并创建了Chin,Chin 创建并创建了 Battery,依次往下,⽽改进之后的控制权发⽣的反转,不再是上级对象创建并控制下级对象了,⽽是下级对象把注⼊将当前对象中,下级的控制权不再由上级类控制了,这样即使下级类发⽣任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC 的实现思想。
public class App {public static void main(String[] args) {int capacity = 200;NewBattery battery = new NewBattery(capacity);NewChin newChin = new NewChin(battery);NewPhone newPhone = new NewPhone(newChin);newPhone.run();}
}
5、理解IoC容器
✔Spring是集合众多工具方法的开源Ioc容器,而IoC容器(又为:控制反转容器)。
容器,顾名思义就是用于存储数据和获取数据的一个东西。而作为Spring最核心的东西---IoC容器。
那么Spring最核心也是最基础的功能,就是如何将对象存储到Spring;且怎么从Spring中取出对象的过程。
将对象存储到Spring容器中,当要使用的时候,就从容器中获取即可,Spring使用的是“懒汉模式”,使用这种模式可以提高开发效率,也可以减少
每次创建销毁对象带来带来的内存开销,不然每次使用一个对象就new一个太过于浪费空间。
【懒汉模式会在第一次使用时才创建对象,而在之前则不会创建,从而避免了在系统启动或者初始化时就创建对象的过程。这种方式被称为“懒汉”,
是因为它在初始化阶段并不匆忙,而是等待真正需要时才会采取行动。】
优点:实现代码的解耦、节省系统的资源,提高了程序运行的效率
6、DI概括
DI(Dependency Injection)依赖注入,跟创建Servlet项目中引入依赖类似,要使用什么样的功能就从Maven中央仓库获取jar包引入进去。
DI和IoC其实是在不同维度上的同一实现方法,只是角度不同。所谓依赖注⼊,就是由 IoC 容器在运⾏期间,
动态地将某种依赖关系注⼊到对象之中。所以,依赖注⼊(DI)和控制反转(IoC)是从不同的度的描述的同⼀件事情,就是指通过引⼊IoC 容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦。
说到底,IoC是一种思想(IoC 是“⽬标”也是⼀种思想,⽽⽬标和思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案),而DI是实现该思想的具体方法。要想实现IoC控制反转,有很多的方法而DI只是其中一种。
二、创建Spring项目
1、创建spring项目
①创建Maven项目 【注意】:项目的存储路径不能有中文
②添加Spring的框架依赖
添加完Spring的依赖后,可以理解为该maven项目变为Spring Core项目
在项⽬的 pom.xml 中添加 Spring 框架的⽀持(需要spring context【spring上下文,也就是表示spring】及spring beans(管理对象的模块),可以从Maven中央仓库获取),xml 配置如下:
下述jar包版本号中的Release表示的是当前版本是稳定的,当前更新停止,可以用于发行的版本;如果是snapshot 版本则代表不稳定、尚处于开发中的版本,即快照版本
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.3.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.2.3.RELEASE</version></dependency>
</dependencies>
引入spring框架依赖后,左侧可以看到一些列jar包,除了jdk1.8、spring-beans和spring-context外其他是配套出现的
③添加一个启动类
最后在创建好的项⽬ java ⽂件夹下创建⼀个启动类,包含 main ⽅法(实现方法细节后续再补充)即可:
public class App {//创建启动类public static void main(String[] args) {}
}
2、Bean对象
2.1、创建Bean对象
创建一个Bean对象,跟普通java程序中创建的对象一样,此处随便创建一个对象即可,要想增添方法、属性也可以.
public class User {//创建一个Bean对象public void functino(){System.out.println("Start!");}
}
2.2、存储Bean对象(将Bean对象注册到容器中)
此处的创建Bean对象存入Spring中,此处的存储并不是真正执行存储的动作,而是告诉spring此时要把某些类存储/托管给spring。
(只是告诉spring要存储对象,什么时候存储是与我们没关系,我们只是使用方,要使用该对象的时候,再到spring取就行,不关注什么时候存,只在乎能不能拿到)
✔通过xml配置文件,告诉spring要存储的Bean对象
【注意】:在创建好的项目中添加配置文件,需要将其放到resources根目录下,新建一个自定义命名(非中文)的.xml文件,再将下述配置代码写入即可
此处的Bean对象(User类)是存放在包里的,因此在配置Bean时,bean的class类名也必须要包含包名(即完整路径名)
下述的Spring 配置⽂件的固定格式(此处只是声明要把对象存储到Spring当中)为以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user_bean" class="com.test.element.User"></bean>
</beans>
2.3、获取Bean对象
根据前面在spring配置文件中注册进Bean对象后,要想获取该对象,就得先拿到Spring对象,才可以拿到里面存放的Bean对象。
【1】使用ApplicationContext接口获取:
ApplicationContext是Spring中的一个接口,通过该接口可以获取到Spring的上下文(得到 Spring 的上下⽂对象,创建的时候需要配置 Spring 配置信息).
它作为一个接口需要实现创建一个ClassPathXmlApplicationContext,通过传入spring配置文件的命名去调用其构造方法创建出来。
ApplicationContext context = new ClassPathXmlApplicationContext("配置文件名");
之后通过调用该Spring上下文对象中的getBean()即可获取Bean对象。
而该getBean方法还有三种不同的参数的传递:
- getBean(String) --- 传入在spring配置文件中注册入的Bean对象id,该方法的返回类型是Object类,需要进行强转
public class App1 {public static void main(String[] args) {//1.ApplicationContext context = new ClassPathXmlApplicationContext("spring_config.xml");User user = (User)context.getBean("user_bean");user.function();}
}
- getBean(Class) --- 传入Bean对象的类型映射,由于已经传入了Bean对象的类型,自然无需像上一个进行类型强转
- getBean(String,Class) --- 同时传入Bean对象的id和类型
①此处对于getBean(String)和getBean(Class) 还是有区别的,当返回的Bean对象是null时,第一个方法依然会对null进行类型强转,而第二个方法无需强转。
②当Spring当中出现两个类型一样,id不一样的Bean对象时,如果使用的是getBean(Class) 方法获取,就会出现报错。通过此处的报错NoUniqueBeanDefinitionException,可以知道没有唯一的一个Bean对象,此处相同类型的Bean对象有两个,无法同时获取两个Bean对象,从而报错。【正确做法:就是使用id去获取,或者结合id和类型获取】
spring中的xml配置文件:
//错误的获取Bean方法 public class App1 {public static void main(String[] args) {//1.ApplicationContext context = new ClassPathXmlApplicationContext("spring_config.xml");User user = context.getBean(User.class);User user1 = context.getBean(User.class);user.function();user1.function();} }
③spring配置文件中对于Bean对象的注册,其id标识不能重复使用,不然在根据id标识获取Bean对象,无法准确得知要获取具体哪一个Bean对象。此处的报错内容,明显的可以知道,命名为user_bean的id标识已经被使用过了!(正确做法:指定不重复的id)
//错误代码 public class App1 {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("spring_config.xml");User user = context.getBean(User.class);user.function();} }
【2】使用BeanFactory获取Bean对象:
public class App1 {public static void main(String[] args) {BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring_config.xml"));User user = beanFactory.getBean(User.class);user.function();}
}
【3】(面试题)ApplicationContext 和 BeanFactory的区别
ApplicationContext和BeanFactory效果其实是一样的,都是可以获取到Bean对象。
两者的区别在于:
- ApplicationContext是在初始化的时候就一次性加载获取spring中所有的Bean对象,而BeanFactory是要获取的时候才获取(懒汉),执行速度比较快 (相对轻量)但是ApplicationContext一次性获取后就一劳永逸,后续再想获取其他Bean对象,效率都会很高。
- ApplicationContext接口是BeanFactory接口的子类,java中的继承关系可知,子类继承父类会具有父类所有的属性和方法,而ApplicationContext作为子类,又有自己独特的方法和属性,所以相比于父类BeanFactory功能更多,其还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持。
3、注解
🎈注解说到底就是一种功能声明.
通过多层类注解,可以让后续对代码的管理和维护更方便 ,使其他人更能直观地了解当前类的用途
Spring中的注解可以分为:1)类注解;2)方法注解
①类注解又可以分为:
- @Controller(控制器存储);实现前后端交互的时候,前端分发请求到后端的第一层入口【用途:验证前端传递的参数(安全检查)】
- @Service(服务层);经过安全检查才可以享受服务【此处为服务调用的编排和汇总】(编排:服务之间的优先级编排,哪个先完成;汇总:请求发送到后端后,服务层会实现两个接口的调用,服务当中会把后端需要更新的两张表的所有接口都在这个方法实现,这就是汇总)
- @Repository(仓库存储---数据仓库、数据持久层);直接操作数据库
- @Component(组件存储);通用化的工具类
- @Configuration(配置存储);存放项目中所有的配置;比如:拦截器、过滤器;
@Controller、@Service、@Repository是要满足用户请求的;
而@Component组件是通用化的工具类,例如定义了一个密码类(用于加密和解密功能的),该类就不用前面三大类注解,因为其是和业务没有关系的,而是使用Component组件注解
②方法注解:@Bean(也可以通过方法注解,将当前方法返回的Bean对象存储到Spring中)
3.1、五大类注解之间的关系
1)@Controller
2)@Service
3)@Repository
4)@Configuration
5)Component
通过观察上述的五大类注解源码,可以清楚地知道@Controller、@Repository、@Configuration、@Service四大注解都是依靠@Component注解实现的,说到底这四个注解是针对组件注解的扩充和扩展。
3.2、使用类注解简易地存储Bean对象
3.2.1、配置扫描路径(必备)
为了不让spring一次性进行全盘扫描(耗时间、耗资源),可以在spring配置文件中配置存储一下存储对象的扫描路径即可。
那么spring就只会在这个包名下扫描,哪些类是有被注解修饰的,才可以判断知道哪些是存放到spring中的Bean对象
下述配置了com.test.element这个包:
<?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:content="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><content:component-scan base-package="com.test.element"></content:component-scan>
</beans>
【注意】:如果不设置扫描路径,其不会扫描任何类。
3.2.2、通过注解注册Bean对象及直接获取
在想要注册进spirng的类前添加类注解即可
@Controller
public class User {public void function(){System.out.println("测试!");}
}
public class App {public static void main(String[] args) {//从spring容器中获取Bean对象//1. 通过ApplicationContext来获取Spring上下文//==============================================//通过注解来将Bean对象注册到Spring中,不像原方法直接手动在配置文件中注册,没有指定Bean对象的Id标识//因此在获取Bean对象时,就不清楚要根据什么去获取。而注解默认提供了Bean对象的Id标识为类型小驼峰,如此处的“user”// 根据id和类名去找到对应根路径下的包名,扫描是否有被注解修饰的类给注册到Spring中,从而去获取对应的Bean对象ApplicationContext context = new ClassPathXmlApplicationContext("spring_config.xml");User user = context.getBean("user",User.class);user.function();//2.通过BeanFactory来获取Spring上下文BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring_config.xml"));User user1 = (User)beanFactory.getBean("user");user1.function();}
}
3.3、五大类注解Bean对象的命名规则
默认情况下,五大类注解的Bean名称是按类名首字母小写的规则来获取的。
创建一个Bean对象时,其类名为大驼峰形式,当要想获取Bean对象时,传递的id参数字符串是其类名的首字母小写形式。如:User -> user
@Controller
public class User {public void function(){System.out.println("测试!");}
}
public class App {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("spring_config.xml");User user = context.getBean("user",User.class);user.function();}
}
🤔特殊情况:当创建的对象类名为前两个字母大写,而不是大驼峰形式,如:IException;
还是通过传入首字母小写的id标识去spring中获取Bean对象,是找不到的
下述演示的注册IException对象,就没办法通过首字母小写形式获取。
@Controller
public class IException {public void function(){System.out.println("成功!");}
}
public class App2 {public static void main(String[] args) {ApplicationContext context =new ClassPathXmlApplicationContext("spring_config.xml");IException iException = context.getBean("iException",IException.class);iException.function();}
}
1)要想查清为什么spring根据该命名无法找到Bean对象,就只能进到spring的源代码中了解Spring关于Bean存储时的命名规则。
在IDEA中连续按两次Shift键,进入查询界面,因为要想知道Spring的命名规则,可以根据BeanName这一关键词去搜索。
2)通过观察该类中的方法名,找到这个符合我们需求的一个方法bulidDefaultBeanName(创建默认Bean命名)
3)在该方法中,最关键的就是最后的这个调用了jdk中Introspector类的decapitalize(),【因此此处是spring调用jdk里面的命名规则】
该方法翻译过来是小写化的意思。此处将一个字符串(即Bena对象名字符串)传进去
4)在该方法中,最关键就是第二个if语句,当Bean对象命名中的一个字母 和第二个字母都是大写时,直接返回原命名字符串,这就是为什么在getBean()从Spring中找Bean对象时,根据其原命名规则首字母小写找不到对象。因为我们存入的Bean对象命名是前两个字母大写,在Spring中获取时,是以其原Bean对象命名去查询获取的。
【总结】注解Bean的名称,要么是首字母小写,要么是原类名
因此正确的做法就是在getBean()时,如果是前两个字母为大写,就直接传入原对象类名即可.
//正确做法
@Controller
class IException {public void function(){System.out.println("成功!");}
}public class App2 {public static void main(String[] args) {ApplicationContext context =new ClassPathXmlApplicationContext("spring_config.xml");IException iException = context.getBean("IException",IException.class);iException.function();}
}
3.4、使用方法注解@Bean
前面学习的类注解是只能应用到类上,而此处的方法注解只能用到方法上。可以通过方法注解修饰的方法,返回其类对象,将该对象存储到Spring中。
用法:在想要返回注册的Bean对象方法前面加上@Bean注解
但有最关键的一点,很容易出错,错误代码如下:
public class Test {@Beanpublic User user(){//此处不是说在spring中还是用new创建对象的意思,而是一种构建数据的方法User user = new User();return user;}
}
public class App2 {public static void main(String[] args) {ApplicationContext context =new ClassPathXmlApplicationContext("spring_config.xml");User user = context.getBean("user",User.class);user.function();}
}
从报错信息上看,可以看出并没有找到名为user的Bean对象。
这是因为方法注解必须要配合类注解来使用,此处的场景跟原获取Bean对象时,Spring配置文件当中设置扫描路径差不多。
如果没有配合类注解的话,就会对程序中所有的类进行扫描,看哪些类中出现被@Bean方法注解修饰的方法,再去获取其对应返回的Bean对象,效率太低。配合了类注解,就会直接从加上类注解的类中去查找方法注解修饰的方法,不会扫描到其他普通的方法,大大提高了运行的效率。
3.5、方法注解的Bean命名规则/Bean重命名
🤔对于方法注解默认通过方法名去找Bean对象的规则,其实有一个很大的弊端。
因为可能出现不同包或同一包底下的类中出现具有相同方法名,且返回同一类型对象的方法。这两个方法同时被@Bean注解修饰,就意味着在从spring中获取Bean对象,不知道是获取哪一个。
但实际上次由@Bean注解,将方法的返回值对象注册到Spring中的方法,如果出现一样方法名时,第二个注册进spring的对象会覆盖前一个注册进去的对象,这就会导致当我们想获取的Bean对象是第一个被覆盖的时候,就无法获取到。
这种场景类似于 Map,存入的key值有且只有一个(相当于方法名),往后继续存入同一key值,就会覆盖上一个存入的key,而value值就可以是重复的。
1)User类
public class User {private int id;private String name;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
2)UserService类:
@Controller
public class UserService {@Beanpublic User user(){User user = new User();user.setName("测试一");return user;}
}
3)UserRepository类
@Controller
public class UserRepository {@Beanpublic User user(){User user = new User();user.setName("测试二");return user;}
}
4)启动类
public class App {public static void main(String[] args) {ApplicationContext context =new ClassPathXmlApplicationContext("spring_config.xml");User user = context.getBean("user",User.class);System.out.println(user.getName());}
}
此处调用的结果是 “测试一”而不是“测试二”,这里就验证了同方法名覆盖导致无法获取理想Bean对象的问题。
为了解决这种问题,spring中有一规则,可以针对Bean对象进行重命名,使其可以针对特定命名的Bean对象进行存储或获取的处理。
重命名的方法有三种:
- 在@Bean注解后添加以name作为key值的参数修改。@Bean(name=" ")
- 直接在Bean注解后添加重命名的字符串 @Bean(" ")
- 也可以使用花括号形式【@Bean(name={"test1","test2"})】,进行多组命名,让一个Bean对象同时具有多个名字。【name也可以省略,如@Bean({"test1","test2"})】
当给这几个被@Bean注解修饰的方法进行重命名后,就可以通过指定新命名去获取Bean对象了。
比如:
@Controller
public class UserRepository {@Bean(name = "user2")//也可以是@Bean("user2")public User user(){User user = new User();user.setName("测试二");return user;}
}
public class App {public static void main(String[] args) {ApplicationContext context =new ClassPathXmlApplicationContext("spring_config.xml");User user = context.getBean("user2",User.class);System.out.println(user.getName());}
}
//调用输出的结果就为:测试二
【注意】使用@Bean注解重命名后,就无法再用原首字母小写的命名规则获取或存储Bean对象了 ;(以上述为例,已经重命名了@Bean("user2"),但还是使用方法名去spring找Bean对象,就会找不到)
public class App {public static void main(String[] args) {ApplicationContext context =new ClassPathXmlApplicationContext("spring_config.xml");User user = context.getBean("user",User.class);System.out.println(user.getName());}
}
※特殊情况:方法注解@Bean不能修饰有参的方法
在同一类中,出现多个方法重载(返回类型一样,参数列表不同)的场景中,如果要同时将这些方法返回的类对象注册到Spring中是无法做到的。
@Bean方法注解只能修饰在无参的方法上,如果是有参的方法,Spring初始化存储时并不能给该方法提供一个参数,因此就会出现该方法无法实现返回Bean对象给注册到Spring中。
3.6、简易地获取Bean对象(对象装配/对象注入)
Spring中获取Bean对象的方法称为:“对象装配”,也可以叫做“对象注入”,即把对象从spring中取出来放到某个类属性中。
对象注入一共可以分为三种方法:
- 属性注入;
- Setter注入;
- 构造方法注入;
1)属性注入
属性注入,直接在类中的成员变量前使用@AutoWired (自动装配)即可。
【注意】:不能直接(在main方法中)使用属性注入后的变量,,这是由于spring的加载比较靠后 ,对于静态方法来说,它是JVM启动的时候就加载可以使用的。
(static方法执行的时间非常早,比此处的属性注入还早,这就导致main执行后,属性注入还没有拿到spring中的Bean对象。此处主要是SpringCore项目才会出现这样的问题,后续的Spring boot、SpringMVC项目就不会担心出现这种情况,因为不需要main方法就可以直接启动spring,main方法已经内置在spring容器里(依靠内置容器启动),所以就可以随心所欲的进行对象注入)
正确的做法(此处是将User对象注入到UserService,之后在启动类中获取UserService对象,就可以调用该类中的getUser()方法输出user,因为UserService中已经通过属性注入获取spring中的user对象):
✔属性注入的缺点:
1)功能缺陷:不能注入final修饰的属性(final修饰的变量,一定得满足以下任一否则编译出错,这是java中对final这个关键字的限制 )
对于final修饰的使用:
①使用时得赋值不然报错;
②如果不在使用时就赋值,就得在构造方法中赋值)
2)通用性问题: 只适用于IoC容器(框架),如果移植到其他框架就无法使用
3)单一设计原则:属性注入过于简单地使用,会导致滥用,从而更容易地违背单一设计原则(比如,一个类设计出来就是单一职责,不能既干那又干那)
✔属性注入的优点:
1)属性注入优点也就是操作简单,只需在成员变量前加上注解,即可完成对象注入,并使用获取到的Bean对象
2)Setter注入
对于set方法,加上@Autowired注解后,spring会自动调用set方法,从spring中获取Bean对象,并注入到成员变量里去。
【注意】:上述代码运行成功的前提条件是,spring已经存进了Bean对象(User),不然对象注入也没得注入 。
✔Setter注入的优点:
1)符合单一设计原理,因为一个Setter方法就只针对一个对象
✔Setter注入的缺点::
1)注入的对象可能被修改,setter方法作为方法是可以被多次调用的,那么就有被修改的风险
2)不能注入一个不可变的变量
3)构造方法注入
构造方法注入,跟上述属性注入和Setter注入使用方法差不多,都是在一个类中对另一个类对象进行注入。
此处是在构造方法前加上@Autowired注解,spring就会自动调用该构造方法,从容器中获取对应的Bean对象并传进去。
UserController类:
@Controller
public class UserController {//构造方法注入private User user;@Autowiredpublic UserController(User user) {this.user = user;}public User getUser() {return user;}public void function(){System.out.println("UserController!");System.out.println(user.getName());}
}
User类:
public class User {private int id;private String name;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}
}
启动类:
public class App3 {public static void main(String[] args) {ApplicationContext context =new ClassPathXmlApplicationContext("spring_config.xml");UserController controller = context.getBean("userController", UserController.class);controller.function();}
}
🎈构造方法注入和其他两个注入最大的区别是:
如果当前类只有一个构造方法注入,可以省略@Autowired,spring依然会去找对应的Bean对象进行注入;但当出现多个构造方法注入时,就不能省略了,spring不知道要对具体哪个进行构造方法注入。
@Controller
public class UserController {//构造方法注入private User user;// @Autowired //即使省略了还是可以进行构造方法注入public UserController(User user) {this.user = user;}public User getUser() {return user;}public void function(){System.out.println("UserController!");System.out.println(user.getName());}
}
而且需要注意的是,一个类中有且只能有一个被@Autowired注解修饰的构造方法,否则spring不知道要对哪个构造方法进行对象注入。如果加多了就会报错。spinrg规定只能在一个类中使用一次@Autowired修饰。
构造方法的注入可以一次性注入多个对象,但切记注入的对象一定得是spring中有的,这是最基本的要求,如果spring中都没有该Bean对象,那谁传一个参数过去呢🙌?
①构造方法的优点:
1)可以使用注入被final修饰的对象,这是因为不可变量要想能使用,
①在声明的时候就得进行赋值,否则报错
②在构造方法中进行赋值;这两种规范都是jdk的要求,即使是spring也得服从; 而构造方法注入正好满足不可变量要想使用的第二个条件;由于构造方法是在类加载的时候就调用,也就是说在在类加载的时候就执行构造方法注入,因此就会直接从spring中获取Bean对象注入到该不可变量中(给它赋值了)。
2)注入的对象(依赖对象)一定会完全初始化,因为依赖对象是在构造方法中执行的,而构造方法是在类加载的时候就执行,
因此依赖对象会在第一时间就初始化完成。
3)通用性好,不仅适用于IOC,还能使用多种框架,这是因为构造方法是jdk支持的(最底层的框架),所以更换任何框架,都是适用的。
4)注入的对象不会被修改,因为在一个类的生命周期中,构造方法只调用一次。
②构造方法的缺点:
构造方法的缺点也很明显,它可以进行多个注入,这就不满足单一设计原则了。
4)@Resource注解
✔@Resource注解和@Autowired注解功能一样,都是能进行对象注入;这两个注解各有各的优点、缺点,可以根据场景需求使用其中一个进行对象注入。
用法和@Autowired一样,只是没有构造方法注入
@Resource和@Autowired的区别:
- 指定spring中的Bean对象名称,从而获取Bean对象;这也是@Autowired无法做到的,它里面就只有一个属性required,默认为true,表示当前注解一定得注入一个存在的Bean对象,否则会报错。
- @Resource和@Autowired出身不同,@Resource是jdk提供的,而@Autowired是spring提供的
- @Resource只能进行属性注入和Setter注入,无法进行构造方法注入,这是因为@Resource比较特殊,注入的时机比较慢,是在对象创建后才注入;而构造方法注入是类加载的时候就注入了,因此无法@Resource无法用于构造方法注入;
3.7、多个同类型Bean对象的指定获取
当spring存入了多个同类型的Bean对象时,如果只是使用@Autowired进行对象注入,很有可能无法准确拿到想要的Bean对象
@Autowired对象注入,首先会根据类名去Spring中寻找符合的类型的Bean对象,但由于Spring当中存在多个同类型Bean对象,因此就会按变量名去寻找,但有时注入对象的命名跟Spring当中的Bean对象没有一个是匹配的,那么此时就很麻烦,在不更改注入对象命名的前提下,没法拿到对应的Bean对象。
以下述为例:(Spring 当中存入两个User类型的Bean对象,命名分别是user1、user2)
@Service
public class UserService {@Beanpublic User user1(){User user = new User();user.setName("1");return user;}//使用方法注解存储Bean对象@Beanpublic User user2(){User user = new User();user.setName("2");return user;}}
此处通过属性注入,获取Bean对象
@Controller
public class UserController {@Autowiredprivate User user;public void function(){System.out.println("UserController!");System.out.println(user.getName());}
}
启动类:
public class App3 {public static void main(String[] args) {ApplicationContext context =new ClassPathXmlApplicationContext("spring_config.xml");UserController controller = context.getBean("userController", UserController.class);controller.function();}
}
启动类中调用UserController类中注入的User对象,发现没有找到对应的对象。这是因为在进行对象注入的时候,没有在spring当中找到User类名为user的Bean对象,前面存入spring中的对象有User user1,User user2,没有一个是User user。所以此处要获取到准确的Bean对象,需要在属性注入处进行指定。
1) 使用@Qualify
由于@Autowired不像@Resource一样有多个参数可以设置,但spring引入了另外一个注解来辅助@Autowired去指定Bean对象,也就是@Qualify注解
@Qualify(value="")
@Controller
public class UserController {@Autowired@Qualifier(value = "user1")private User user;public void function(){System.out.println("UserController!");System.out.println(user.getName());}
}
2)使用@Resource
通过设置@Resource中的属性name,来指定Bean对象
@Controller
public class UserController {@Resource(name = "user2")private User user;public void function(){System.out.println("UserController!");System.out.println(user.getName());}
}
三、Bean的作用域和生命周期
1、Bean作用域的理解
对于传统作用域,能想到的就是,例如:一个方法中定义了一个局部变量a,那么该变量a的作用域就只是作用在当前方法中,出了这个方法就没有用了,除非将它作为返回值或参数传递出去。
而Bean作用域不一样,它表示的是Bean对象在Spring当中的某一种行为模式,比如 单例作用域(单例行为模式),一个Bean对象在spring当中有且仅有一份(共享的),如果谁使用了这个Bean对象,并对其进行修改;其他方法去获取时,就会发现该对象发生了修改。
UserController类
@Controller
public class UserController {@Resource(name = "user1")private User user;public void function(){//此处声明一个新变量指向注入的Bean对象User user1 = user;user1.setName("修改了");System.out.println("UserController");System.out.println(user1.getName());}
}
UserRepository类
@Repository
public class UserRepository {public User user;@Autowired@Qualifier(value = "user1")public void setUser(User user){this.user = user;}public void function(){System.out.println("UserRepository");System.out.println(user.getName());}
}
启动类:
public class App3 {public static void main(String[] args) {ApplicationContext context =new ClassPathXmlApplicationContext("spring_config.xml");UserController controller = context.getBean("userController", UserController.class);UserRepository repository = context.getBean("userRepository",UserRepository.class);//获取spring中Bean对象user1controller.function(); //此处里面重新声明了一个user1repository.function(); //使用的是原spring中Bean对象user//得到的结果理想是://UserController//修改了//UserRepository//1}
}
得到的真实结果是:
上述UserController类中,对属性注入后的user,又重新声明了一个新变量user1指向里面的user。但根据java语法来说,其实声明的user1是指向Bean对象本身的(只是更改了指向方而已),因此在里面对user1进行属性修改,会影响到Bean对象本身。
又由于Bean中的单例作用域,所有的同一个Bean对象都是共享,有一方对其进行修改,会影响到其他方的获取跟实际原对象信息不一样。
2、Bean的作用域
Bean的作用域一共可以分为五种,分别是:
-
- singleton:单例作用域
- prototype:原型作用域,又称为多例作用域
- application:全局作用域
- request:请求作用域
- session:会话作用域
- websocket:HTTP WebSocket作用域
上述的application、request、session、websocket作用域都是属于spring MVC中的。只适用于web环境中Spring ApplicationContext中有效。
2.1、singleton作用域
singleton作用域,在该作用域下IoC容器中的Bean只存在一个实例,且无论是通过ApplicationContext获取spring上下文从而获取Bean对象,还是使用对象注入的方式获取的Bean对象都是共享的,有且仅有一份,如果有一个类注入使用修改该Bean对象,另外一个类去使用时,该Bean对象就已经发生修改了。
适用场景:通常得是无状态的Bean对象才使用该作用域。(无状态:表示该Bean对象不发生任何属性修改)
【注意】:Spring存储的Bean对象默认是 singleton作用域
2.2、prototype作用域
prototype作用域,称为原型作用域又称为多例作用域,根据意思就知道跟singleton作用域恰好相反了,单例作用域中的Bean对象是共享的;而原型作用域中的Bean对象则不是,在spring中每次获取的Bean对象都是创建的一个新的实例,也就是说每次获取的Bean对象都是新的。就不会说A类修改a这个Bean对象,B类去获取这个a对象,该对象会发生修改,原型模式中是不存在这种情况。
适用场景:通常得是有状态的Bean对象才使用该作用域
2.3、application作用域
application作用域:在一个Http servlet context下定义了一个Bean对象,是全局的。但跟singleton又不一样,application作用域可以在多个类中创建获取上下文,因此这些类中上下文中的Bean对象又不是共享的(一个上下文中就有一个,多个上下文就有多个),只是在当前类中创建的上下文定义的Bean对象才是共享的。singleton作用域的Bean是spring容器中有且仅有一份,且彼此共享。
application作用域是作用于servlet容器,且是属于spring web的作用域
2.4、request作用域
request作用域:相比于prototype作用域而言,prototype作用域是每访问一次Bean对象就创建一个新的实例;而request作用域是每收到一次Htpp请求就创建一个实例,有可能收到的请求中包含多个Bean对象的访问,但一次请求就只创建一次实例,跟prototype差别还是挺大的
⼀次http的请求和响应的共享Bean,且仅支持在 Spring MVC 中使用
2.5、session作用域
session作用域,每一次http session都会创建一次实例,第一次登陆完成后创建session,之后登陆都是用的这个会话;当把会话关了,重新创建会话,这时就算重新登陆一次账号,此时获取的Bean对象是不一样的,因为删掉原有会话重新创建的session id是跟原来不一样的【(session不一样)-> Bean对象也就不一样了。】
用户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息,且仅支持在 Spring MVC中 使用
2.6、websocket作用域
在⼀个HTTP WebSocket的生命周期中,定义⼀个Bean实例;对于网络聊天,聊天双方不可以用http进行通信,因为发送http请求是由服务器接受并被动的返回响应;
而用户双方的聊天一定是双向发送,双方都可以接受和发送,就得用到websocket,双方都是客户端WebSocket的每次会话中,保存了⼀个Map结构的头信息,将用来包裹客户端消息头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean;限定Spring WebSocket中使⽤
2.7、设置Bean对象作用域
使用@Scopre来设置Bean对象的作用域,设置作用域的方法有两种:
1)直接设置:@Scope(" "),字符串参数,直接指定作用域名,比如singleton、prototype
2)枚举设置:@Scope(ConfigurableBeanFactory.xxxx),xxx为一个字符串,如下图两个
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
下述演示修改将存入的Bean对象设置为 原型模式prototype
BeanUser类:
@Controller
//使用类注解将该对象存入spring当中
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class BeanUser {private String name = "Bean";private int id = 1;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}
}
BeanService类(对Bean对象进行修改):
@Service
public class BeanService {@Autowiredprivate BeanUser beanUser;public void function(){System.out.println("原Bean,id="+beanUser.getId()+"name="+beanUser.getName());BeanUser beanUser1 = beanUser;beanUser1.setId(2);beanUser1.setName("Bean");System.out.println("BeanService对bean对象修改后,id = "+ beanUser1.getId()+";name = "+beanUser1.getName());System.out.println("哈希地址"+beanUser1);System.out.println();}
}
BeanService2类(输出Bean对象的属性,看是否是同一个)
@Service
public class BeanService2 {@Autowiredprivate BeanUser beanUser;public void function(){System.out.println("BeanService只是获取Bean对象,id = "+ beanUser.getId()+";name = "+beanUser.getName());System.out.println("哈希地址"+beanUser);}
}
启动类:
public class App {public static void main(String[] args) {ApplicationContext context =new ClassPathXmlApplicationContext("spring_config.xml");BeanService beanService =context.getBean("beanService",BeanService.class);beanService.function();BeanService2 beanService2 =context.getBean("beanService2",BeanService2.class);beanService2.function();}
}
通过输出结果,可以知道,确实prototype作用域下,每次获取的Bean对象都是重新创建的一个新实例。
3、Spring的执行流程
1)首先通过获取Spring Context上下文,来启动容器
2)配置spring中的xml文件
3)Bean的初始化(配置xml文件中的Bean、配置扫描包路径并加载看包路径下的类是否加上了注解)
4)将Bean对象存到到容器中
5)将Bean对象注入/装配到类中使用它
6)销毁Spring
4、Bean的生命周期
Bean的生命周期是Bean从创建到销毁的这一过程。Bean的生命周期(执行流程)相比于Spring的执行流程大有不同,Spring的执行流程是框架的,而Bean的执行流程属于是Spring执行流程中的一个环节
Bean的生命周期可以分为5个部分:
1. Bean的实例化(分配内存空间)
2. 将当前类依赖的 Bean 属性,进行注入和装配
3. Bean的初始化(该过程又可以分为以下几个步骤)【Bean对象的重命名是发生在初始化中的。@Bean(value="")】
- 实现了各种 Aware 通知的方法,如 BeanNameAware、BeanFactoryAware、ApplicationContextAware 的接口方法;(给Bean起一个名称,为一个事件设置完名称之后,就会有一个通知BeanNameAware)
- 执行BeanPostProcessor初始化的前置方法(初始化的前置方法:也就是在初始化之前的/构造方法之前执行的方法)
- 执行Bean的初始化方法(执⾏ @PostConstruct 初始化⽅法,依赖注⼊操作之后被执⾏)
- 执行BeanPostProcessor初始化的后置方
4. 使用Bean (在程序当中使用Bean对象)
5. 销毁Bean(使用@PreDestory、DisposableBean 接⼝⽅法、destroy-method)
【注意】:
1)实例化不等于初始化;实例化:分配内存空间,将之前自变量变成具体的内容,根据内存地址可以拿到它。
2)实例化和属性设置是 Java 级别的系统“事件”,其操作过程不可人工干预和修改;而初始化是给开发者提供的,可以在实例化之后,类加载完成之前进行自定义“事件”处理。
演示Bean对象生命周期的过程
spring的配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:content="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><content:component-scan base-package="com.test"></content:component-scan><bean id="beanService" class="com.test.service.BeanService" init-method="initMethod"></bean>
</beans>
BeanService类(此处为Bean的实例化,属性设置、Bean对象初始化的过程):
🎈配置Bean对象初始化的方法有两种:
1)使用@PostConstruct注解;
2)在xml配置文件中配置 Bean对象中的 init-method属性,指定一个方法为初始化方法,该属性的值对应初始化方法的方法名;
这两种方式创建的Bean的初始化方法有优先之分,注解配置的执行要快于xml中配置的。(init method属于是旧版本用的,后面才使用的PostConstruct,PostConstruct优先级高于init method创建(用新不用旧)
@Service
public class BeanService implements BeanNameAware {//实现BeanNameAware接口,重写setBeanName通知方法@Overridepublic void setBeanName(String s) {System.out.println("执行Bean对象的Aware通知"+s);}//进行Bean对象的初始化
// @PostConstruct
// public void function(){
// //执行Bean对象初始化方法
// System.out.println("@PostConstruct执行Bean对象初始化方法");
// }//也可以使用xml中配置一个初始化方法public void initMethod(){System.out.println("init-执行Bean对象初始化方法");}public void useBean(){System.out.println("使用Bean对象");}@PreDestroypublic void destory(){System.out.println("执行Bean对象的销毁");}}
BeanProcessor类:
(将Bean对象的初始化前置和后置方法单独定义在一个类中使用,因为一般前置和后置方法是所有Bean对象通用使用的,不是单独为一个Bean对象所服务的(我为人人),所以这两个方法不能写在某个具体的 Bean 中,否则(这两个方法)不会执行)
@Service
public class BeanService implements BeanNameAware {@Overridepublic void setBeanName(String s) {System.out.println("执行Bean对象的Aware通知"+s);}//进行Bean对象的初始化
// @PostConstruct
// public void function(){
// //执行Bean对象初始化方法
// System.out.println("@PostConstruct执行Bean对象初始化方法");
// }//也可以使用xml中配置一个初始化方法public void initMethod(){System.out.println("init-执行Bean对象初始化方法");}public void useBean(){System.out.println("使用Bean对象");}@PreDestroypublic void destory(){System.out.println("执行Bean对象的销毁");}}
启动类:
public class App {public static void main(String[] args) {//此处使用ConfigurableApplicationContext来获取spring中的上下文,因为ApplicationContext类中没有close()---处理spring的销毁方法ConfigurableApplicationContext context =new ClassPathXmlApplicationContext("spring_config.xml");BeanService beanService = context.getBean("beanService",BeanService.class);beanService.useBean(); //使用Beancontext.close(); //销毁Spring}
}
🤞注意:Bean的注入和初始化的执行顺序不可以交换
一般我们总会认为,无论是对于要使用的变量还是对象,都是先初始化后再使用。
但对于Spring来说,Bean对象一定得先注入/装配到一个类中,才可以进行初始化。
因为Bean对象的初始化过程中,可能会使用到Bean对象本身,但此时如果是先初始化后注入,初始化方法中拿到的Bean对象就是一个空对象,从而引发 “空指针异常”,所以应该对于Bean的生命周期中,一定得先注入了Bean对象后才可以初始化。
对于spring的基础操作总结到这里就结束了,上述文字量较多,但讲的都是很基础,如果有什么错误,希望各路大神可以指点一二。
相关文章:
Spring基础(2w字---学习总结版)
目录 一、Spirng概括 1、什么是Spring 2、什么是容器 3、什么是IoC 4、模拟实现IoC 4.1、传统的对象创建开发 5、理解IoC容器 6、DI概括 二、创建Spring项目 1、创建spring项目 2、Bean对象 2.1、创建Bean对象 2.2、存储Bean对象(将Bean对象注册到容器…...
07 目标检测-YOLO的基本原理详解
一、YOLO的背景及分类模型 1、YOLO的背景 上图中是手机中的一个app,在任何场景下(工业场景,生活场景等等)都可以试试这个app和这个算法,这个app中间还有一个button,来调节app使用的模型的大小,更大的模型实时性差但精…...
每日一题 78子集(模板)
题目 78 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1: 输入:nums [1,2,3] 输出:[[],[1],[2]…...
OpenCV之形态学操作
形态学操作包含以下操作: 腐蚀 (Erosion)膨胀 (Dilation)开运算 (Opening)闭运算 (Closing)形态梯度 (Morphological Gradient)顶帽 (Top Hat)黑帽(Black Hat) 其中腐蚀和膨胀操作是最基本的操作,其他操作由这两个操作变换而来。 腐蚀 用一个结构元素…...
设计模式:享元模式
设计模式:享元模式 什么是享元模式 首先我们需要简单了解一下什么是享元模式。享元模式(Flyweight Pattern):主要用于减少创建对象的数量,以减少内存占用和提高性能。享元模式的重点就在这个享字,通过一些共享技术来减少对象的创建ÿ…...
汉诺塔问题(包含了三台柱和四台柱)——C语言版本
目录 1. 什么是汉诺塔 2. 三座台柱的汉诺塔 2.1 思路 2.2 三座台柱的汉诺塔代码 3. 四座台柱的汉诺塔 3.1 思路 3.2 四座台柱的汉诺塔代码 1. 什么是汉诺塔 汉诺塔代码的功能:计算盘子的移动次数,由数学公式知,汉诺塔的盘子移动次数与…...
【实训项目】滴滴电竞APP
1.设计摘要 2013年国家体育总局决定成立一支由17人组成的电子竞技国家队,第四届亚室会中国电竞代表队 出战第四届亚洲室内和武道运动会。 2014年1月13日CCTV5《体育人间》播放英雄联盟皇族战队的纪录片。 在2015到2019年间,我国电竞战队取得的无数值得…...
C++核心编程--类篇
C核心编程 1.内存分区模型 C程序在执行时,将内存大方向分为4个区域 意义:不同区域存放数据,赋予不同的生命周期,更能灵活编程 代码区:存放函数体的二进制代码,由操作系统进行管理的全局区:存放…...
java中用feign远程调用注解FeignClient的时候不重写Encoder和Decoder怎么格式不对呢?
如果在使用 Feign 进行远程调用时,没有重写 Encoder 和 Decoder,但仍然遇到格式不对的问题,可能是由于以下原因之一: 服务端返回的数据格式与客户端期望的格式不匹配:Feign 默认使用基于 Jackson 的 Encoder 和 Decode…...
记录使用Docker Compose 部署《XAPI项目》遇道的问题及解决方案
《XAPI项目》:GitHub仓库(勿打🚫小破站一个) 这篇文档,主要内容是记录使用Docker Compose 部署《XAPI项目》遇道的问题及解决方案 目录 📚 本地MySQL数据如何导入到容器内的MySQL中❎ 解决报错:…...
腾讯云OCR实践 - 降低客服财务运营成本
一、 前言: 随着图片时代的飞速发展,大量的文字内容为了优化排版和表现效果,都采用了图片的形式发布和存储,这为内容的传播和安全性带来了很大的便利,需要做重复性劳动。 OCR文字扫描工具也逐渐的应运而生,…...
springboot+vue上传图片
这里是一个简单的示例,演示了如何在Spring Boot中从Vue.js上传图像: 1.前端Vue.js代码: <template><div><input type"file" change"handleFileUpload"><button click"uploadImage">…...
高压电缆护层接地环流及温度在线监测系统
高压电缆的金属护层是电缆的重要组成部分,当缆芯通过电流时,会在金属护层上产生环流,外护套的绝缘状态差、接地不良、金属护层接地方式不正确等等都会引起护套环流异常现象,严重威胁电缆运行安全。 当电缆金属护层环流出现异常时…...
无涯教程-JavaScript - IPMT函数
描述 IPMT函数根据定期,固定的还款额和固定的利率返回给定投资期限内的利息支付。 语法 IPMT (rate, per, nper, pv, [fv], [type])争论 Argument描述Required/OptionalRateThe interest rate per period.RequiredPerThe period for which you want to find the interest a…...
【EI会议征稿】第三届机械自动化与电子信息工程国际学术会议(MAEIE 2023)
第三届机械自动化与电子信息工程国际学术会议(MAEIE 2023) 第三届机械自动化与电子信息工程国际学术会议(MAEIE 2023)将于2023年12月15-17日在江苏南京举行。本会议通过与业内众多平台、社会各团体协力,聚集机械自动…...
手写实现LRN局部响应归一化算子
1、重写算子的需求 芯片推理过程中遇到很多算子计算结果不对的情况,原因是封装的算子会在某些特殊情况下计算超限,比如输入shape特别大或者数值特别大时,LRN算子计算会出现NAN值,所以需要重写算子。先对输入数据做一个预处理&…...
朗思科技数字员工通过统信桌面操作系统兼容性互认认证
近日,朗思科技数字员工与统信桌面操作系统V20进行了兼容互认,针对上述产品的功能、兼容性方面,通过共同严格测试表明——朗思科技数字员工在统信桌面操作系统 V20上整体运行稳定,满足功能及兼容性测试要求。 北京朗思智能科技有限…...
十六、Webpack常见的插件和模式
一、认识插件Plugin Webpack的另一个核心是Plugin,官方有这样一段对Plugin的描述: While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset m…...
ChatGPT新增超强插件:文本直接生成视频、海报,支持自定义修改!
全球著名在线设计平台Canva,在ChatGPT Plus(GPT-4)上推出了插件功能,用户通过文本提示,几秒钟就能生成演示文稿、PPT插图、电子书封面、宴会邀请函等各种精美设计海报,同时支持生成视频。 该插件最强大的功…...
亚像素边缘提取的例子
求帮忙下载: 1.http://download.csdn.net/detail/pkma75/925394 pkma75 资源积分:1分 备注:pdf格式,用曲线拟合的方法计算亚像素,编程易实现,具有较强的实用价值 2.http://download.csdn.net/detail/kua…...
Wayland:推动Linux桌面进入下一代图形显示时代
文章首发地址 Wayland是Linux系统下的一种图形显示协议,旨在替代X Window System(X11)作为Linux桌面环境的图形显示服务。下面是对Wayland的详细解释: 背景: 传统的Linux桌面环境使用X Window System(X11&…...
mysql外键(foreign key)
简介 MySQL的外键约束用来在两个表数据之间建立链接,其中一张表的一个字段被另一张表中对应的字段约束。也就是说,设置外键约束至少要有两种表,被约束的表叫做从表(子表),另一张叫做主表(父表&…...
内网穿透——Windows搭建服务器
文章目录 1.前言2. Emby网站搭建2.1. Emby下载和安装2.2 Emby网页测试 3. 本地网页发布3.1 注册并安装cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar内网穿透本地设置 4.公网访问测试5.结语 1.前言 在现代五花八门的网络应用场景中,观看视频绝对是主力应用场景之一&…...
UE5.1 + Android 环境搭建
官方文档:一定一定一定要参照官方文档,因UE不同版本对应的环境搭建并不完全一致。 准备工作 通过EpicGameLaunch下载Android目标平台。 必须安装jdk1.8并配置环境变量,UE5.1不要使用最新的jdk20;下载地址 安装 Android Studio …...
华为python面试题目
华为Python常见的面试问题包括: Python是如何被解释的?什么是PEP8?Python是怎样管理内存的?什么是Python装饰器?Python提供哪些内置类型?Python中的异常处理是怎样的?什么是Python的上下文管理器?Python中的列表推导式是什么?Python中的生成器是什么?什么是Python的装…...
IP代理安全吗?如何防止IP被限制访问?
你是否遇到过可以正常上网,但访问某个网站却被禁止?注册某个网站账号,却被封号?那都是因为IP出现问题!您的IP地址透露很多关于您的信息,包括您的位置和互联网活动。 在本文中,我们将一起了解IP地…...
使用 gst-template 创建自己的 gstreamer 插件
系列文章目录 创建 gstreamer 插件的几种方式 使用 gst-template 创建自己的 gstreamer 插件 使用 gst-plugins-bad 里面的 gst-element-maker 工具创建gstreamer 插件 文章目录 系列文章目录前言一、如何获取 gst-template 仓库代码二、gst-template 相关的软件依赖1. 根据自…...
nginx反向代理,用户访问服务器1的80端口,请求转发至服务器2,3的8882端口
两台应用服务器,一台nginx,用户访问nginx服务器80端口,将请求转发至服务器2和服务器3的8882端口。 1、修改nginx配置文件 upstream backend {server 10.60.16.187:8882;server 10.60.16.188:8882;}server {listen 80;server_name 10.6…...
Python学习笔记:导入txt、xlsx文件并做简单函数处理
1.txt文件 1.1路径 file_path "E:\Python Project\temp.txt" with open(file_path) as f:content1 f.read() 导入文件时,如果直接放文件绝对路径上去会报错,这是因为\P是转义字符 所以在绝对路径前面加r可以避免将引号内的内容识别成转义…...
uniapp 轮播列表左右滑动,滑动到中间放大
html <!-- 轮播 --><view class"heade"><swiper class"swiper" display-multiple-items3 circulartrue previous-margin1rpxnext-margin1rpx current0 change"swiperChange" ><block v-for"(item,index) in list"…...
python web网站开发/媒体发稿平台
C程序设计实验报告 实验项目: 1、利用复化梯形公式计算定积分2、计算Ackerman函数3、编写计算x的y次幂的递归函数getpower(int x,int y),并在主程序中实现输入输出4、编写计算学生年龄的递归函数5、编写递归函数实现Ackman函数 姓名:张时锋 …...
网站建设无锡/口碑营销的产品
在发送通知邮件的时候, 假如可以有漂亮的邮件模板就更好了,但是出于安全的原因, 邮件一般不支持 link 或者 style 样式,只能通过内联的方式。找到了 The Automatic CSS Inliner Tool。有了自动转换工具,那就简单了。首…...
java做网站需要的技术/百度网首页登录入口
昨天花了一个下午才升级成功,今天费了点儿周折才打上补丁,不想同道中人再浪费不必要的时间,把以把我的步骤给大家说一下,供参考。 使用工具:x65Flasher与VK 升级文件及工具下载:http://yizhe.net/c65/ 步骤: 1.关机&am…...
有域名了怎么做网站/百度信息流推广技巧
目录一、RequestMapping1.作用范围:2.属性二、请求参数的绑定三、请求参数乱码问题四、自定义类型转换器1.定义一个转换类实现Converter接口2.在springmvc的配置文件中配置类型转换器3.在springmvc配置文件中引用配置的类型转换五、使用Servlet原生API六、一些常用注…...
wordpress上传文件类型限制/深圳高端seo公司助力企业
写在前面 嗯...打算开始每天写点啥了,不知道能坚持多久。 准备以一周为单位来进行更新,周一~周三写一些图形方面的内容,四~六是和图形没有什么太大关联的内容(意会就好),周日作为一个更新重点试着写一些乱七…...
英文版wordpress安装/seo超级外链工具
欢迎关注头条号:老顾聊技术精品技术文章分享,知识的组装工目录前言先更新数据库,再更新缓存先更新缓存,再更新数据库先删除缓存,再更新数据库先更新数据库,再删除缓存删除缓存失败,导致不一致读…...