bean scope属性讲解

说明:

(1) scope:范围、规模、广度、眼界的意思;

(2) 本篇博客的内容比较重要,篇幅也比较长,文字叙述较多;但是,只要理解了其中的内容,发现其实本篇博客的内容还是比较少的;

(3) 本篇博客内容:是【Bean对象的作用域和生命周期】中的【<bean的scope属性简介】;


一:<bean>的scope属性介绍

bean scope简介:

(1) 默认情况下,bean对象在IoC容器创建后就会被创建,然后这个对象是全局唯一的;

scope用法:

scope属性的可选值:

(0) singleton和prototype比较基础和重要; request,session,global session,websocket目前仅作了解即可。

(1)singleton: 这是bean中scope属性的默认值,即如果不设置scope属性的话,其默认就是singleton;

这个对象,在容器中有且仅有一个实例,并且这个实例被全局共享;即,无论在程序什么地方,使用getBean()方法时,对于同一个beanId其指向的是同一个对象;所以,这又被称为单例模式;

(2)prototype: 在每次要使用这个<bean对应的对象的时候,由IoC容器自动的创建一个全新的实例;所以,这又被称为多例模式;

(3) request:在以后使用spring开发web应用时,设置了scope=request,代表每一次独立的请求中都会存在唯一的实例,但是不同的请求其实例是不同的;即,某个实例的生存范围就是在其对应的那次请求中;

(4) session:每一次用户会话都有唯一的实例,但是不同的用户会话其实例是不同的;

(5) global session:用在portlet这种轻量级web应用的共享session中,这个不太常用;

(6) websocket:web应用中比较先进的通信技术,其允许浏览器和服务器之间产生双向的长连接,websocket特别适合应用在如【网络在线客服】之类的功能;因为通过websocket,客户端和服务端可以创建一个用于通信的管道,数据从客户端可以发送到服务端,而服务端也能主动的推送数据到客户端,这是原有的http这种单向协议所不具备的;


二:<bean>的scope=singleton

singleton示意图:

(1)上示意图说明:

●上图共有四个<bean,id分别是“b1”,“b2”,“b3”,“userDao”;如果“userDao”这个bean的scope设置为singleton的话,那么在IoC容器启动的时候,这个个<bean>对应的对象被创建的时候,其在IoC容器中有且仅有一个实例,并且这个实例被全局共享;

●如上图所示,id是“b1”,“b2”,“b3”的<bean对应的对象,都引用了id=“UserDao”这个<bean>对应的对象;即“UserDao”这个对象被其他多个对象共享;

(2)为什么Spring的 <bean>的scope默认值被设置成singleton单例模式嘞?:

●如果每一次在需要某个对象的时候,都去创建一个,会额外的占用内存的空间,创建对象也要占用CPU的计算资源;频繁创建对象所带来的资源损耗,在小应用中可以忽略不计,但是在大型应用中,这个资源损耗就不能忽略了;

●IoC的<bean>\的scope默认singleton,可以有效的解决创建对象时资源占用的问题;(因为,全局只用创建一次就可以了,而且还是在IoC容器启动的时候就创建好了的)

**(3)一个问题:如果【id是“b1”,“b2”,“b3”的<bean>对应的这三个对象】同时发起调用【id=“UserDao”这个<bean对应的对象】的操作,会不会出现阻塞问题? **

● 不会出现阻塞:因为Spring IoC容器默认设置【scope=singleton】时,是单例多线程的,全局只有一个的【id=“UserDao”这个<bean对应的对象】,可以同时为其他的调用者提供服务,其执行效率也是很高的。

● 单例情况下还支持多线程,那么,在使用【scope=singleton】单例模式的时候,就需要注意线程安全的问题;

【scope=singleton】单例模式的线程安全问题:

单线程的情况下:不会出现问题

●如上A用户(红色箭头代表),调用了a对象的setNum()方法,设置a对象的num属性值为1;然后过了几毫秒后,A用户(红色箭头代表)去读取a对象的num属性值,自然就是1;


多线程的情况下:会出现问题

●如上A用户(红色箭头代表),调用了a对象的setNum()方法,设置a对象的num属性值为1;但是,A用户(红色箭头表示)还没来得及读取a对象的num值时,B用户(蓝色箭头表示),调用了a对象的setNum()方法,设置a对象的num属性值为2;因为全局只有一个a对象,而且这个a对象被A用户和B用户共享,自然a对象的num值就变成了2;这样以后,A用户(红色箭头代表)去调用输出a对象的num属性值时,num的值就变成了2了。由此,问题就出现了。

● 如上阐述的就是线程安全问题,在多线程环境下,线程安全是需要首要考虑的问题;

● 为了解决线程安全问题,有很多解决办法;比如,【使用 synchronized关键字加锁,使得在设置和读取的过程中,让当前代码处于独占的状态】,这就相当于把【多线程并行的运行】变成了【多线程串行的排队执行】;(这儿可以参考下Java线程四:线程同步

●为了解决线程安全问题,还可以为A用户和B用户各分配一个只属于他自己的a对象,各操作个的,大家互补影响,这样就不会产生信息错乱的问题了。这种要创建多个对象的设置,在Spring IoC容器中,就是接下来的【scope=prototype】多例模式了;


三:<bean的scope=prototype

prototype示意图:

(0) 已知【scope=prototype】,代表在每次要使用这个<bean>对应的对象的时候,由IoC容器自动的创建一个全新的实例;所以,这又被称为多例模式;

(1)上示意图说明:

●如上,在每一次【ref=“userDao”,产生对象注入】或者【getBean(“userDao”),获取id=UserDao这个<bean的对象】时,IoC容器都会创建一个新的实例;这就意味着【id是“b1”,“b2”,“b3”的<bean对应的对象】都拥有的是自己独占的一个【id=“UserDao”这个<bean对应的对象】;

● 由此,每个线程的工作互不影响,这自然就解决了线程安全的问题;

(2)【scope=prototype】缺点:

● 由于,创建对象实例的过程中会占用内存和CPU的资源,所以相对执行效率低一些;

【scope=singleton】和【scope=prototype】的区别:

(1) 在实际工作中,如果场景是小数据量、小访问量、小用户量的情况;这两种的性能差异可以忽略不计;但是,如果场景是多用户和高并发的情况,就需要考虑性能上的损失了;

(2) 介绍@PreDestroy注解时的内容。 @Scope定义为prototype类型,则实例创建之后spring就不在管理了,它只是做了new操作而已;@Scope定义为singleton类型,则实例创建之后spring会继续管理这个实例对象;

bean scope属性实际运用

说明: (1) 本篇博客主要内容是演示【scope=singleton】和【scope=prototype】在使用时的区别;


零:为了方便演示,创建一个基于maven的项目s06;

readme.md:项目说明文档

演示【scope=singleton】和【scope=prototype】在使用时的区别;

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <groupId>com.imooc.spring</groupId>
     <artifactId>s06</artifactId>
     <version>1.0-SNAPSHOT</version>
     <repositories>
         <repository>
             <id>aliyun</id>
             <name>aliyun</name>
             <url>https://maven.aliyun.com/repository/public</url>
         </repository>
     </repositories>
 
     <dependencies>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-context</artifactId>
             <version>5.3.9</version>
         </dependency>
     </dependencies>
 
 </project>

说明:

(1) 没什么好说的,就是设置国内maven仓库;引入spring框架的依赖;

applicationContext.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">
 </beans>

说明:

(1) 就是xml的文档说明;spring配置文件的schema约束;


二:演示

1.当【不设置scope属性】或者【设置scope=singleton】时:在IoC容器初始化的时候,就会创建这个<bean对应的对象

UserDao类:

package com.imooc.spring.ioc.dao;
 
 public class UserDao {
     public UserDao() {
         System.out.println("提示性语句:UserDao对象已创建。" + this);
     }
 }

说明:

(1) 很简单,UserDao只在默认构造方法中,输出了一句话。那么在基于【默认构造方法】创建UserDao对象的时候,就会输出这句话;

applicationContext.xml:不设置scope属性

<?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="useDao" class="com.imooc.spring.ioc.dao.UserDao"></bean>
 </beans>

说明:

(1) 因为我们当前工程并没有设置web,所以scope只有两个备选项;

(2) 上面是基于【默认构造方法】创建UserDao对象;

(3) 如果不设置scope属性,其默认就是singleton;

SpringApplication程序入口类:演示

package com.imooc.spring.ioc;
 
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
     public static void main(String[] args) {
         ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
}

说明:

(1) 上面的程序,只是加载了applicationContext.xml文件,即只是初始化了IoC容器;

(2) 运行结果:在IoC容器初始化的时候,这个对象就被创建了;


也可以发现,这个对象是唯一的;

2.当【设置scope=prototype】时:在IoC容器初始化的时候,是不会创建这个<bean>对应的对象的;只有在【ref引用这个<bean>对应的对象】或者【getBean()去获取这个<bean>对应的对象时】才会创建这个<bean>对应的对象

在applicationContext.xml中设置这个 <bean>的scope=prototy:

SpringApplication程序入口类:演示

package com.imooc.spring.ioc;
 
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
     public static void main(String[] args) {
         ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
}

说明:

(1) 上面的程序,只是加载了applicationContext.xml文件,即只是初始化了IoC容器;

(2) 运行结果:


当【设置scope=prototype】时:在IoC容器初始化的时候,是不会创建这个bean对应的对象的;只有在【ref引用这个bean对应的对象】或者【getBean()去获取这个bean对应的对象时】才会创建这个<bean对应的对象;


也可以发现,这个对象不是唯一的,即每次getBean()去获取对象的时候,都会现创建一个对象;

3.当【设置scope=prototype】时:在IoC容器初始化的时候,是不会创建这个<bean对应的对象的;只有在【ref引用这个bean对应的对象】或者【getBean()去获取这个bean对应的对象时】才会创建这个bean对应的对象

UserService类:

package com.imooc.spring.ioc.service;
 
 import com.imooc.spring.ioc.dao.UserDao;
 
 public class UserService {
     private UserDao userDao;
 
     public UserService() {
         System.out.println("提示性语句:UserService对象已创建。" + this);
     }
 
     public UserService(UserDao userDao) {
         this.userDao = userDao;
     }
 
     public UserDao getUserDao() {
         return userDao;
     }
 
     public void setUserDao(UserDao userDao) {
         this.userDao = userDao;
         System.out.println("提示性语句:setUserDao()方法被执行人,其userDao参数是:" +userDao);
}
}

说明:

(1) 后面我们使用【利用setter实现对象依赖注入】的策略创建UserService对象,所以在UserService的无参构造和setUserDao()方法中,输出了一点提示性的东西;

applicationContext.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.imooc.spring.ioc.dao.UserDao"
scope="prototype"></bean>
     <bean id="userService"
class="com.imooc.spring.ioc.service.UserService">
         <property name="userDao" ref="userDao"/>
     </bean>
 </beans>

说明:

(1) 创建UserService对象,采用的是【利用setter实现对象依赖注入】的策略;

SpringApplication程序入口类:演示

package com.imooc.spring.ioc;
 
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
     public static void main(String[] args) {
         ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
}

说明:

(1) 上面的程序,只是加载了applicationContext.xml文件,即只是初始化了IoC容器;

(2) 运行结果:


如果这样:


由此,应该具备这个能力:当看到一个applicationContext.xml文件时候,应该能够知道在IoC容器初始化的时候,到底创建了哪些对象。


三:那么在实际开发中,Dao和Service那些具体的类,到底应该是【scop=singleto单例】还是【scope=prototype多例】的呐?

(1) Dao类,Service类,设置是SpringMVC中的Controller控制器类,在绝大多数情况下都设置成单例;

(2) 之所以单例是会出现线程安全的问题,这是因为某个对象的属性,在运行过程中可能会不断的变化;

可以参考上篇中有关线程安全的描述;

(3) 但是,在实际环境中,如果Dao类,Service类都设置成单例的,一旦Service对象被创建,又因为如下图所所示,Service对象中注入了同样是单例的Dao对象;这就意味着,以后无论在哪儿使用Service对象去调用Dao对象的时候,都是调用的同一个Dao对象,而这也是符合【Dao对象是Service对象的一个属性,而对于一个Service对象来说,其Dao对象属性也不需要经常变动】的客观情况;那么,仅仅在这个范围来说,并不会出现线程安全的问题。

但是,如果有多个用户访问这个系统,由于Service是单例的,也是会出现线程安全的问题。由于,目前对并发的内容知之甚少,所以这个问题无法进一步解决。?????

某一属性在运行过程中恒定不变,使用单例设置,但是如果不断变化,就要使用多例模式啦

深刻感觉到,了解并熟练使用并发技术的重要性!

bean的生命周期

说明:

(1) 本篇博客主要根据案例阐述对象的声明周期;

(2) 其中,比较重要的是注意下这个对应关系:

(3) 还有就是调用【registerShutdownHook()】销毁IoC容器;


一:bean对象生命周期说明

bean的生命周期:

说明:

(1)第一步: 【IoC容器准备初始化,解析applicationContext.xml文件】:先解析applicationContext.xml文件,看下当前xml中,需要创建哪些对象,需要为哪些对象注入什么属性。即,这一步,是先看看情况,确定要做什么;

(2)第二步: 【对象实例化,执行构造方法】:IoC根据applicationContext.xml配置文件中的内容,自动利用反射技术,去实例化对应的bean对象;同时,基于java的规则,在实例化对应bean对象的时候,对应的构造方法也会执行。即,这一步,仅仅是得到了对象;

(3)第三步:【为对象注入属性】:当获取了对象后,根据解析applicationContext.xml文件,IoC容器会知道,要为刚创建的对象注入哪些属性,IoC容器完成对象属性注入的工作。即,这一步,是IoC容器确定,要为对象注入哪些属性,并给对象设置好这些属性;

(4)第四步: 【调用init-method初始化方法】:当IoC容器为对象注入属性后,接下来IoC容器会自动的调用对象的init-method方法,来完成一些剩余的工作,彻底完成对象的初始化工作。即,这一步,是在对象注入属性之后,通过init-method()方法彻底完成对象的初始化工作;

(5)第五步: 【IoC容器初始化完毕】:经过上面的步骤后,IoC容器的初始化工作就完成了;

(6)第六步: 【执行业务代码】:当IoC容器初始化完成后,那些对象也都创建好了,就可以通过代码调用这些对象的业务代码了;

(7)第七步: 【IoC容器准备销毁】:如果不想要IoC容器了,就可以调用对应的方法,去启动销毁IoC容器;

(8)第八步: 【调用destroy-method方法,释放资源】:IoC容器开始销毁时,IoC容器就会调用配置文件中声明的destroy-method方法,去释放对应的资源;

(9)第九步: 【IoC容器销毁完毕】:当所有bean对象的destroy-method()方法执行完后,IoC容器就销毁完毕了;


二:创建对象的演示

这儿代码,继续沿用s06项目中的代码;

1.创建对象的过程

在readme.md中,增加一点项目说明:

创建entity包,添加Order类:

Order类:

package com.imooc.spring.ioc.entity;
 
 public class Order {
     private Float price;//单价
     private Integer quantity;//采购数量
     private Float total;//订单的总价
 
     public void init() {
         System.out.println("执行了init()方法");
         total = price * quantity;
     }
     /**
      * 支付方法
      */
     public void pay() {
         System.out.println("订单金额为:" + total);
     }
 
     public Order() {
         System.out.println("通过无参构造方法,创建Order对象" + this);
     }
 
     public Order(Float price, Integer quantity, Float total) {
         this.price = price;
         this.quantity = quantity;
         this.total = total;
     }
 
     public Float getPrice() {
         return price;
     }
 
     public void setPrice(Float price) {
         this.price = price;
         System.out.println("执行了setPrice()方法,去注入price属性;");
     }
 
     public Integer getQuantity() {
         return quantity;
     }
 
     public void setQuantity(Integer quantity) {
         this.quantity = quantity;
         System.out.println("执行了setQuantity()方法,去注入Quantity属性;");
     }
 
     public Float getTotal() {
         return total;
     }
 
     public void setTotal(Float total) {
         this.total = total;
     }
 }

说明:

(1) Order类添加了一个init()方法;这个init()方法,在下面介绍applicationContex.xml中的init- method属性时,会用得到;

(2) 因为在后面applicationContext.xml中,是利用【setter方法完成对象的创建和注入】,所以,上面在对应的方法处都输出了一些提示性的文字;

applicationContext.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="order1" class="com.imooc.spring.ioc.entity.Order" init-
method="init">
         <property name="price" value="19.8"/>
         <property name="quantity" value="1000"/>
     </bean>
 </beans>

说明:

(1) init-method=“init”:那么在IoC容器初始化的过程中,IoC容器会去调用Order类中的init()方法;

SpringApplication程序入口类:

package com.imooc.spring.ioc;
 
 import com.imooc.spring.ioc.dao.UserDao;
 import com.imooc.spring.ioc.entity.Order;
 import com.imooc.spring.ioc.service.UserService;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
import sun.awt.IconInfo;
public class SpringApplication {
     public static void main(String[] args) {
         ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
System.out.println("============IoC容器已经初始化============");
Order order1 = context.getBean("order1", Order.class);
order1.pay();
}
}

说明:

(1) 运行结果:这个过程还是比较清晰的;

那么如果想要销毁这个容器嘞?

2.销毁IoC容器演示

在Order类中添加destroy()方法,并设置 <bean的destroy-methd属性指向这个方法;

说明:

(1) 在销毁IoC容器的过程中,会执行<bean>的destroy-method属性对应的方法;

(2) Order类中的destroy()方法的作用是,在销毁IoC容器(也就是销毁对象)的时候,需要执行的一些代码,一般是释放与order1对象相关的资源;在程序运行中,所谓资源,可以是一个文件,也可以是一个网络的连接,也可以是其系统方法的调用;而,一般在实际开发中,我们要把释放这些资源的代码写在destroy()方法中;

(3) 比如,在商品进入海关的时候,除了对这个订单进行申报,还需要将这个订单信息写入到某个文件中;如果在这个过程中出现了异常情况,此时IoC容器要进行销毁,那么此时就要触发order对象的destroy()方法,那么在这个destroy()方法中,我们就要编写代码,将这个正写入的文件进行保存的操作,并且将这个文件资源进行释放,这样的话才能在销毁IoC容器的时候,实现平稳销毁,不出乱子的效果;

SpringApplication程序入口类:

说明:

(1) 【registerShutdownHook()】:销毁IoC容器的方法;这个方法不是在ApplicationContext接口中定义的,而是ClassPathXmlApplicationContext这个ApplicationContext接口的实现类中定义的;

(2) 在调用【registerShutdownHook()】销毁IoC容器的过程中,会自动的调用在applicationContext.xml的<bean中设置的destroy-medhod对应的方法;

(3) 运行效果:可以看到,在调用【registerShutdownHook()】销毁IoC容器的过程中,自动的调用在applicationContext.xml的<bean中设置的destroy-medhod对应的方法;

至此,一个对象的完整生命周期就完成了。


三:补充

实现极简Ioc容器

说明:

(1) 这篇博客的目的是,帮助加深对Spring IoC容器的理解;主要是理解,Spring IoC容器背后是如何利用反射机制完成对象的创建和数据的注入的; (2) 这篇博客中的内容不需要记忆,仅作提升理解Spring IoC容器用;

(3) 通过本篇博客的介绍,能够感受到IoC容器的原理并不复杂;IoC容器本质的是一个Map键值对对象;

(4) 本篇博客仅仅是IoC容器的基础实现;Spring框架中的IoC容器远比本篇博客中的内容复杂的多;


为了演示,创建一maven工程:s07

1.创建Apple类:

Apple类:

package com.imooc.spring.ioc.entity;
 
 import javax.print.attribute.standard.RequestingUserName;
 
 public class Apple {
     private String title;
     private String color;
     private String origin;
 
     public Apple() {
     }
 
     public Apple(String title, String color, String origin) {
         this.title = title;
         this.color = color;
         this.origin = origin;
     }
 
     public String getTitle() {
         return title;
     }
 
     public void setTitle(String title) {
         this.title = title;
     }
 
     public String getColor() {
         return color;
     }
 
     public void setColor(String color) {
         this.color = color;
     }
 
     public String getOrigin() {
         return origin;
     }
 
     public void setOrigin(String origin) {
         this.origin = origin;
     }
 }

说明:

(1) 这个Apple类没什么好说的,一个普通的javaBean;

然后,我们去创建一个配置文件,然后在配置文件中,按照Spring IoC的规则和样式,写一个Apple对象的信息;

2.创建applicationContext.xml配置文件

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
 <beans>
     <bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
         <property name="title" value="红富士"/>
         <property name="color" value="红色"/>
         <property name="origin" value="欧洲"/>
     </bean>
 </beans>

说明:

(1) 这个配置文件名字可以随便起,那就干脆和Spring IoC一样,也叫做applicationContext.xml吧;

(2) 这个applicationContext.xml中,按照Spring IoC配置文件的规则和样式,写了一个Apple对象的信息;

配置文件写好了之后,对于这个配置文件,是如何在运行时,创建对象嘞?我们需要自己去实现IoC容器;下面按照Spring IoC的规则和样式,去加载xml配置文件,完成IoC容器的初始化工作;

3.创建ApplicationContext接口和ClassPathXmlApplicationContext实现类

ApplicationContext接口:

package com.imooc.spring.ioc.context;
 
 /**
  * 这个接口,就来模拟Spring中的ApplicationContext接口;
  */
 public interface ApplicationContext {
     public Object getBean(String beanId);
 }

说明:

(1) 模拟Spring IoC容器,在ApplicationContext接口中,创建一个getBean()方法;


ClassPathXmlApplicationContext实现类:

package com.imooc.spring.ioc.context;
 
 import org.dom4j.Document;
 import org.dom4j.Element;
 import org.dom4j.Node;
 import org.dom4j.io.SAXReader;
 
 import java.io.File;
 import java.lang.reflect.Method;
 import java.net.URLDecoder;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 /**
  * 这个类模拟Spring中的ClassPathXmlApplicationContext类;
  * 这个类的作用:(1)实现ApplicationContext接口;(2)完成IoC容器的创建过程;
  */
 public class ClassPathXmlApplicationContext implements
ApplicationContext{
private Map iocContainer = new HashMap();
public ClassPathXmlApplicationContext(){
try{
String filePath =
this.getClass().getResource("/applicationContext.xml").getPath();
filePath = new URLDecoder().decode(filePath, "UTF-8");
SAXReader reader = new SAXReader();
Document document = reader.read(new File(filePath));
List<Node beans =
document.getRootElement().selectNodes("bean");
for (Node node : beans) {
Element element = (Element) node;
String id = element.attributeValue("id");
String className = element.attributeValue("class");
Class c = Class.forName(className);
Object obj = c.newInstance();
List<Node properties = element.selectNodes("property");
for (Node p : properties) {
Element property = (Element) p;
String propName = property.attributeValue("name");
String propValue = property.attributeValue("value");
String setMethodName = "set" + propName.substring(0,
1).toUpperCase() + propName.substring(1);
Method setMethod = c.getMethod(setMethodName,
String.class);
setMethod.invoke(obj, propValue);//通过setter方法注入数据
}
iocContainer.put(id, obj);
}
System.out.println("IoC容器初始化完毕。");
}catch (Exception e){
e.printStackTrace();
}
}
public Object getBean(String beanId) {
return iocContainer.get(beanId);
}
}

说明:

(1) 这个类的职责就是实现ApplicationContext接口,并且完成IoC容器的创建过程;可以这样理解:每一个ClassPathXmlApplicationContext对象,都对应一个IoC容器,这个容器本身是用来存储对象的;

(2) 在Java中,IoC容器使用Map去保存对象,key对应了beanId,value对应了相应的对象;

(3) 在实例化ClassPathXmlApplicationContext对象的过程中,去加载处理applicationContext.xml配置文件,所以主要代码都写在了ClassPathXmlApplicationContext的默认构造方法中了。同时,因为在这个过程中可能会产生异常,所以写在了try块中;

(4) 获取applicationContext.xml这个配置文件的地址;

(5) 获得了applicationContext.xml文件的地址后,就是解析这个文件了;由于这个文件是xml文件,所以可以利用现有的轮子,以提高程序的效率;引入dom4j和jaxen;

dom4j:是 java 的 xml 解析组件,其可以把 xml 文件转成一个 document 对象,帮助我们读取和操作 xml 的内容;jaxen:在使用 Dom4j,利用 XPath 查询时候,必须要先下载 Jaxen 的 jar 包;在快速查询 xml 文件的时候,需要引入这个 dom4j 底层依赖的 Jaxen;这部分如有需要可以快速参考 Java操作XML 及附近相关文章;

(6) 解析 xml 文件:这部分如有需要可以快速参考 Java操作XML 及附近相关文章;

至此,这个IoC容器的初始化的代码就写好了;

4.创建SpringApplication类,去测试

SpringApplication类:

package com.imooc.spring.ioc;
 
 import com.imooc.spring.ioc.context.ApplicationContext;
 import com.imooc.spring.ioc.context.ClassPathXmlApplicationContext;
 import com.imooc.spring.ioc.entity.Apple;
 
 public class SpringApplication {
     public static void main(String[] args) {
         ApplicationContext context = new ClassPathXmlApplicationContext();
Apple sweetApple = (Apple) context.getBean("sweetApple");
System.out.println(sweetApple);
}
}

运行结果:


这儿打一个断点,进一步观察:

四种注解类型

说明:

(1) 自本篇博客开始,接下来的内容是【使用注解方式实现Spring IoC】;

(2) 【xml方式,实现IoC】,【注解方式,实现IoC】,【java Config方式,实现IoC】,这三种方式只是具体的配置方式不同,在底层本质的原理是一样的;

(3) JDK1.5以后提供了一种特殊的语法:在类、属性或者方法上,添加一个如【@+某个特定的类名】的东西,就是注解了;

(4) 注解的作用,通常就是对所描述的类、属性、方法,进行额外的功能扩展或者增强;

(5) Spring也提供了大量的注解,来简化开发和配置的工作;

(6)本篇博客的核心内容就是:四种【组件类型】注解:@Repository、@Service、@Controller、@Component;


注解的优势:

(1) 前面介绍的【使用XML方式实现Spring IoC】:

● 虽然其把所有的bean都放在了文本文件中管理,但是这个配置过程还是非常繁琐的;

● 可以设想,如果有很多bean需要配置的话,那么这个applicationContext.xml文件的尺寸将会非常大;

● 同时程序员的开发体验也会很差,比如每一次要使用一个bean的时候,都需要从xml中去翻阅到底用哪个beanId;同时,在进行对象设置的时候,也要频繁的在源代码和xml配置文件中进行不断的切换,这是开发体验是很差的;

● 即,在实际开发中,【使用XML方式实现Spring IoC】并不太受欢迎;程序员喜欢在编码过程中,顺便就设置一下这个bean;由此,就引出了【使用注解方式实现Spring IoC】这种更好的解决方案了;

(2) 可以这样理解,注解是写在源代码中的配置信息;注解基于“声明式”的原则,更适合现代轻量级的企业应用;

(3) 使用注解可以提高程序的可读性,开发体验也更好;

在Spring中,按照注解的功能划分,有三类注解:【组件类型】注解,【自动转配】注解,【元数据】注解

【组件类型】注解:有四种

(0) 这四种注解都是要放在java类上的;

(2) 当对一个类使用这四种注解(中的某一个)时,意思是,对于当前的类,需要被IoC容器进行创建对象和管理;

(3) 利用注解,其还可以通知IoC容器,当前这个类的职责是什么。

(4) 【@Repository】:该注解通常是放在业务持久层的类上的;IoC容器在初始化的过程中,会对所有的类进行扫描,如果看到某个Dao类上有【@Repository】注解,IoC容器就会自动的创建这个Dao类的对象并进行管理;

(5) 【@Service】:业务逻辑类上使用这个注解;IoC容器扫描到某个Service类上有【@Service】注解后,IoC容器会自动的对其进行实例化并管理;同时,Ioc看到这是个service服务类,IoC有很多扩展模块是专门去增强service类的,这些模块就会自动的应用到这个Service对象上,进而实现功能的增强;

(6) 【@Controller】:用来描述MVC模式中的Controller类的;

(7)【@Component】:component是组件的意思。在实际开发中,对于一个类我们无法确定其是Controller类,还是Service类,还是Dao类,即这个类的边界是模糊的;此时,就可以对这个类使用【@Component】注解。【@Component】注解是一个最统称的注解,【@Repository】、【@Service】、【@Controller】这三个注解是【@Component】注解的细化。

(8) 这四种注解要想被Spring IoC识别的话,需要在applicationContext.xml中开启组件扫描:

● 如上图设置后,IoC容器在初始的过程中,就会自动扫描【com.imooc】包下所有的类,扫描过程中,一旦发现某个类上有 【@Repository】、【@Service】、【@Controller】、【@Component】这四个注解,IoC容器就会对其实例化,这个实例化的对象也会被IoC容器管理;

●又如,上面我们设置了扫描【com.imooc】包下的所有的类,但是对于【com.imoo.exl】包下的类,我们不希望IoC对其进行管理,那么就可以如上所示那样去设置。type=“regex”代表后面的expression属性的内容是一个正则表达式,只要我们的类名符合expression属性的那个正则表达式,那么这个类就会被排除在外,不会被IoC容器实例化和管理;(PS:在实际开发中,这个用的并不多)

基于注解初始化IoC容器

说明:

(1) 本篇博客主要演示:【@Repository】、【@Service】、【@Controller】、【@Component】这四种【组件类型】注解; (2) 【使用注解方式开发Spring IoC的,applicationContext.xml的schema约束】和【前面使用XML方式开发Spring IoC的,applicationContext.xml的schema约束】是不同的;

(4) 本篇博客仅仅涉及【@Repository】、【@Service】、【@Controller】、【@Component】这四种【组件类型】注解的最简单使用;即本篇博客仅仅涉及到【得到一个最简单的对象】,而没有涉及【对象的注入等对象初始化的工作】;


1.为了演示,创建一个Maven项目s08;

2.在pom.xml中引入Spring的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>com.imooc.spring</groupId>
    <artifactId>ioc</artifactId>
    <version>1.0-SNAPSHOT</version>
 
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.9</version>
        </dependency>
    </dependencies>
</project>

3.创建applicationContext.xml:

applicationContext.xml:

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

说明:

(1) 不是说好的使用注解方式,可以摆脱xml的配置吗,这儿为什么还要写applicationContext.xml?:使用注解方式来实现Spring IoC容器,也是需要applicationContext.xml的;这是因为,在使用注解方式实现Spring IoC容器时,一些最基础的配置也还是要写在applicationContext.xml配置文件中的

(2) 【使用注解方式开发Spring IoC的,applicationContext.xml的schema约束】和【前面使用XML方式开发Spring IoC的,applicationContext.xml的schema约束】是不同的;

(3) 【使用注解方式开发Spring IoC的,applicationContext.xml的schema约束】和【前面使用XML方式开发Spring IoC的,applicationContext.xml的schema约束】区别分析:

●【基于注解开发时,schema中多了一个名为context的命名空间】;命名空间就像java中的包名;有关命名空间的内容可以参考:

● 关于这部分内容,可以参考【补充:Spring XML 配置文件中命名空间;(可能存在理解偏差的地方,随时补充和修正……)】;

● 引入了context命名空间后,就可以使用该命名空间中的内容了;

(4)<ontext:component-scan意思是组件扫描,其作用是:在IoC容器初始化时,自动扫描【@Repository】、【@Service】、【@Controller】、【@Component】这四种【组件类型】注解,并完成实例化;

4.一个简单的例子:主要目的是走一遍这个流程: @Repository注解

首先,写一个UserDao类,用于演示:

UserDao类:

package com.imooc.spring.ioc.dao;
 
 import org.springframework.stereotype.Repository;
 //组件类型注解,默认beanId为类名(首字母小写);如下,该类的bean对象的beanId默认为【userDao】;
 @Repository("udao")
 public class UserDao {
 }

说明:

(1) 要想在IoC容器初始化时,也实例化UserDao类的对象,只需要在UserDao类上使用【@Repository】注解,表示这是一个在业务持久层的类;

(2) 组件类型注解,在IoC容器中默认beanId为类名(首字母小写);如上,UserDao类的的bean对象的beanId默认为【userDao】;在实际中我们一般也约定俗称的采用默认命名, 这样以后,所有的开发工程师在进行bean的引用和协作时,就不用进行额外的询问了,因为大家都才用了【采用默认命名】的这个统一标准;

(3) 自然,也可以通过上面的方式,自定义UserDao类的对象的beanId为“udao”;

然后,编写SpringApplication入口列,去观察效果:

SpringApplication类:

package com.imooc.spring.ioc;
 
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
     public static void main(String[] args) {
         ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
String[] beansName = context.getBeanDefinitionNames();
for (String beanName : beansName) {
System.out.println(beanName + ":" + context.getBean(beanName));
}
}
}

说明:

(1) 【context.getBeanDefinitionNames();】获取容器内所有对象的beanId集合,可以参考【Spring IoC容器与Bean管理15:查看容器内对象;

(2) 运行效果

5.剩余补充: @Service注解、@Controller注解、@Component注解

说明:

(1) 可以看到,上面三个类对应的beanId都使用了默认命名;

(2) 运行结果

(3) 上面得到的四个bean是singleton单例模式的;(这个很容易理解~~)

自动装配与Autowired注解

说明:

(1) 在上文中,仅仅介绍了实例化对象,但是没有涉及为对象的属性注入数据;本博客中介绍的【自动装配注解】,其目的就是实现对象的依赖注入;

(2) 【自动装配注解】包括【按类型装配注解】和【按名称装配注解】;

(3) 本篇博客主要介绍以【@Autowired】注解为例,介绍【自动装配注解】中的【按类型装配注解】的使用和缺点;


一:自动装配注解简介

【按类型装配】注解和【按名称装配】注解简述:

【按名称装配】: 在【Spring IoC容器与Bean管理12:IoC在项目中的作用;】中的s04项目为例:

在绝大多数场景下,我们都是采用【按名称装配】的策略;

【按类型装配】: 不需要关心在IoC容器中,bean的名称是什么;在运行过程中为属性注入值时,只需要从IoC容器中获取对应类型的对象,然后完成自定注入;

【按类型装配】的注解:@Autowired和@Inject:

● @Autowired:这个是Spring提供的,即这是Spring自己提供的规范;

●@Inject:由JSR-330(Java规范要求第330号文件,这个文件是Java领域的标准和业界的规范)提供的标准;自然Spring对JSR-330也提供了支持;

● @Autowired和@Inject这两个【按类型装配】的注解,不推荐使用;更多的时候,鼓励使用【按名称装配】的注解;

【按名称装配】的注解:@Named和@Resource:

●@Named:这个注解要和@Inject注解匹配使用;即在@Inject注解后,增加@Named注解,其会按照属性名(或者其他自定义的规则)完成对象的装配;同时@Named也是JSR-330(Java规范要求第330号文件,这个文件是Java领域的标准和业界的规范)提供的标准

●@Resource:这个注解出现的较早,是JSR-250提供的标准;这个注解不但可以按照名称进行依赖注入,如果不满足按名称进行依赖注入时,其也能自动按类型装配;@Resource这个注解是目前功能最强大的自动供装配注解;

二:自动装配注解:之【按类型装配】注解:之@Autowired注解

● 沿用代码s08;

● 在实际工作中,不推荐使用【按类型装配】注解;

●事先说明:MVC架构模式采用分层的方式依次的逐级调用的,即Controller调用Service(也就是Controller依赖于Service啦),Service调用Dao(也就是Service依赖于Dao);由于前面,我们在UserController类、UserService类、UserDao类中使用了对应的注解,所以在IoC容器初始化的时候,这三个类的bean对象就会被创建了;

@Autowired注解用法 :【在属性上使用@Autowired注解】和【在set方法上使用@Autowired注解】

策略一:在userDao属性上使用@Autowired注解

运行:

通过运行结果,可以看到上面实现了对象注入,但是其并不是通过setter方法来实现的;

但是,如果采用下面的策略:


策略二:在setUserDao()方法上使用@Autowired注解

重新运行:


区别分析:

(1) 【在属性上使用@Autowired注解】和【在set方法上使用@Autowired注解】,都可以完成对象的注入;

(2) 【在属性上使用@Autowired注解】没有执行set方法;【在set方法上使用@Autowired注解】执行了set方法;

(3) 如果装配注解放在set方法上,则自动按类型或者名称对set方法参数进行注入;

(4) 如果装配注解放在属性上,IoC容器会自动通过反射技术,将属性private修饰符自动更改为public,由于属性的访问修饰符成了public,这就意味着从外侧可以直接对这个属性赋值,不再执行set方法,然后赋值完成后,再将修饰符改回到private;(这个过程是在运行时,动态完成的,对程序员不可见)

(5) 由于(4)中的原因,在实际开发中,如果使用注解来实现对象依赖注入的话,通常是不用写set方法的;

@Autowired注解用法 :缺点和问题

(1)演示:为什么,在工作中,不推荐使用【按类型装配注解】;

既然有了Dao接口,那么UserDao类应该实现这个接口(在实际开发中,这也是约定俗成的开发方式):

因为有了所有Dao的接口,那么在Service中,按照面向对象编程的理念,userDao属性的类型也应该修改为IUserDao接口:

至此,还没有什么问题。

然后,比如原有的UserDao是基于Mysql数据库写的,如果后面需要改为Oracle数据库,直接的想法是新创建一个类,这个类实现IUserDao接口,然后按照Oracle开发就行了;

但是,这样以后就会出问题了:运行SpringApplication类,就会报错了:NoUniqueBeanDefinitionException:

该行报错信息完整摘录如下:

Caused by:org.springframework.beans.factory.NoUniqueBeanDefinitionException: No
qualifying bean of type 'com.imooc.spring.ioc.dao.IUserDao' available:
expected single matching bean but found 2: udao,userOracleDao

其大意是,我期望在容器只有唯一的、匹配的bean,但是在容器中发现了两个bean(分别是userDao和userOracleDao);

之所以会注入失败,就是因为@Autowired注解是【按类型装配】的注解;

(2)使用【按类型装配注解】,如何解决【由于IoC容器中出现多个相同类型的对象,从而导致的NoUniqueBeanDefinitionException 】的问题?

解决办法1: 去掉被抛弃的那个Dao类的@Repository注解;(目的是,IoC容器中不要出现类型重复的Dao对象)

解决办法2: 引入@Primary注解

运行结果:

【按类型装配】注解之所以容易出问题的原因就是:在容器中,可能会出现多个相同类型的对象,如果稍不注意就会出现NoUniqueBeanDefinitionException;为了避免这个问题,在实际开发中,多使用【按名称装配】的注解,因为名称在容器中都是唯一的,所以可以有效避免刚才错误的发生;而【按名称装配】的注解在下篇博客中会给予介绍;

Resource注解按名称装配

说明:

(1) 通过上文介绍已经知道,【自动装配注解】中的【按类型装配注解】有一些问题;为了解决这个问题,在实际工作中,推荐使用【自动装配注解】中的【按名称装配注解】;

(2) 【自动装配注解】中的【按名称装配注解】,主要就是@Resource注解;这也是本篇博客的主要内容;


​​​​​​

@Resource:这个注解出现的较早(出现的早,不代表其落后啦),是JSR-250提供的标准,是业界的标准;这个注解不但可以优先按照名称进行依赖注入,如果不满足按名称这个条件时,其还能按类型进行智能的匹配,以完成注入的工作;@Resource这个注解是目前功能最强大的自动供装配注解;

一:【@Resource】注解的基本使用规则

【@Resource】注解的规则:是本篇博客的核心

1.@Resource,如果设置了name属性,那么在装配时,其会按照name属性的值,在IoC容器中寻找对应的bean,完成注入的工作;如果没有找到对应的bean,会报错;

2. @Resource,如果没有设置name属性,那么首先,会使用属性的属性名作为bean name在IoC容器中去查找bean,如果找到了则自动注入,如果没有找到,则按类型进行匹配(等沦落到需要按类型进行匹配时,其规则和@Autowired注解相同,为了避免NoUniqueBeanDefinitionException的问题,也需要使用@Primary去解决类型冲突的问题);

3. 使用建议:@Resource需要尽量避免沦落到类型匹配的地步,所以推荐设置name属性,或者严格保证属性的属性名和bean name保持一致;

案例:

案例1:推荐使用策略

运行结果:

案例2:推荐使用策略;在实际中,案例1中的策略和案例2中的策略都使用的比较多;

运行结果:

案例3:不推荐使用的策略

运行结果:


二:【@Resource】:【在属性上使用@Resource注解】和【在set方法上使用@Resource注解】(与@Autowired类似)

(1) 和@Autowired注解一样,如果@Resource注解使用在属性上,那么其不使用setter方法,就能完成对象的注入;其本质也是使用反射技术,先把属性的修饰符从private修改为public,然后对属性直接赋值,赋值完成后,再将属性的修饰符改回到private;

(2) 和@Autowired注解一样,如果@Resource注解使用在set方法上,那么其就是通过调用setter方法,完成属性的;

(3) 这儿比较容易理解,真心没必要举例子了;


三:【@Resource】【@Autowired】的区别

PS:这些注解得在实际中多用才行,多用才能熟悉、才能体味到目前感觉不到的东西;

元数据注解

说明: (1) 本篇博客的核心是@Value注解;

(2) 和【利用XML方式实现Spring IoC容器】相比,【利用注解方式实现Spring IoC容器】是一个妥协和权衡的结果:


元数据注解的作用:IoC容器管理对象时,提供一些辅助信息;

一:元数据注解简介

(1) @Primary注解:按类型装配时,如果出现多个相同类型的对象,则优先注入@Primary描述的那个对象;前面已经介绍过了;

(2) @PostConstruct注解:

● init-method在【Bean对象的作用域及生命周期三:对象生命周期】中介绍过,是IoC容器对属性进行注入后,自动执行的初始化方法;

● @PostConstruct注解的作用和init-method是作用类似;

(3) @PreDestroy注解:

● destroy-method在【Bean对象的作用域及生命周期三:对象生命周期】中介绍过,在销毁IoC容器的过程中,会执行<bean>的destroy-method属性对应的方法;

● @PreDestroy注解的作用和destroy-method是作用类似;

(4) @Scope注解:设置bean的scope属性,即决定这个对象的生存周期;

● 在【<bean>的scope属性;(主要是【scope=singleton】和【scope=prototype】的分析和区别)】及后续的几篇文章中,介绍了bean的scope属性,并且演示了如何使用XML的方式去设置;

● @Scope注解,则是【使用注解方式去设置Scope属性】的方式;

(5) @Value注解:为属性注入静态数据;


二:案例演示

1.@Scope注解


2.@PostConstruct注解


3.@PreDestroy注解:这个注解在日常开发中很少用到;

通过运行结果可以看到,这儿还有问题!~~~

这儿之所以,没有调用UserService对象的destroy()方法, 是因为UserService的@Scope定义为prototype类型,则实例创建之后spring就不在管理了,它只是做了new操作而已,想要使用@preDestroy注解,则需要将scope中的参数改成singleton。


4.@Value注解:为某个属性设置静态数值(核心!!!)

(1)引文:@Value注解的不推荐的使用方式

(2)@Value注解的推荐的使用方式:

步骤一:创建配置文件

config.properties代表应用程序的配置信息;比如,上面的UserService的metaData就可以在config.properties中配置:

需要注意,这个配置文件中,如果有多个键值对,需要保证每个键都是不同的;

步骤二:加载配置文件

config.properties文件要想被Spring识别的话,需要在applicationContext.xml文件中将其进行加载:

上面加载好了之后,就可以在UserService类中,使用@Value注解进行引用了:

步骤三:在需要用到配置文件中数据的地方,直接使用@Value(”${}“)的方式去引用数据

运行,看下效果:发现,其已经成功设置属性了

(3)@Value注解原理分析

@Value原理:在属性上使用@Value注解:其是在运行时,将属性的访问修饰符从private修改为public,然后给属性直接赋值,设置完了之后,再将其改回到private;(即,和@Autowired和@Resource,在属性属性上使用,原理是一样的)

(4)config.properties配置文件的几点说明

● 如config.properties的配置文件的使用很广泛,比如在连接数据库时,数据库的driver,url,username,password等都可以剥离出来,存放到config.priperties中:(以前做项目的时候,自己也是这么干的)

● 一个不错的命名规则:(在实际开发中,可以考虑采用)


三:Summary:和【利用XML方式实现Spring IoC容器】相比,【利用注解方式实现Spring IoC容器】是一个妥协和权衡的结果

(1) 至此,Spring核心的注解,就这些了;

(2) 这些注解,只是XML配置方式的一种延伸,利用注解开发的体验比利用XMl开发体验要更好;所以,在日常开发中,注解也是主要的开发方式;

(3) 和【利用XML方式实现Spring IoC容器】相比,【利用注解方式实现Spring IoC容器】是一个妥协和权衡的结果:

● 注解虽好,但其实这些注解依旧是写在源代码中,如果需要修改注解,还是需要修改源代码;

● XML方式基于配置文件,【不用修改源代码】的特性很强,但是程序员的开发体验不好;注解方式,使用方便,但注解是写死在程序中,不容易维护; 这个问题,也没办法,鱼和熊掌不可兼得;

●由于Spring框架主要面向Java开发者,作为Spring这个组织来说,实现功能前提下,首先要考虑程序员使用起来是否体验好,如果任何操作都需要大量的XML配置,那么程序员肯定不爱用,那么Spring就会慢慢被淘汰掉,Spring组织也就慢慢不会盈利了;所以,Spring增加注解,也是Spring为了迎合市场(程序员的口味)来设计的;即,增加注解方式,是一个权衡的结果。

JavaConfig-对象实例化

说明:

(1) 自本篇博客开始,接下来的内容是【使用Java Config方式实现Spring IoC】;

(2) 【xm方式,实现IoC】,【注解方式,实现IoC】,【java Config方式,实现IoC】,这三种方式只是具体的配置方式不同,在底层本质的原理是一样的;

(3) 已经知道,为了满足【如果需求变更,尽量不要修改源代码,而是通过修改配置文件,来实现变更需求】的目的,【xm方式,实现IoC】是最好的,但是【xm方式,实现IoC】的编程体验又比较差;所以,在反复权衡、妥协下,Spring推出了【注解方式,实现IoC】,对于程序员来说,【注解方式,实现IoC】方式开发体验更好,这也是在实际中用的比较多的开发方式;,虽然基于【注解方式,实现IoC】开发时,某些情况下需要修改源代码,但是你懂的,鱼和熊掌不可兼得,总之在权衡利弊之后【注解方式,实现IoC】还是一种比较不错的开发方式;

(4) 本篇博客介绍第三种Spring IoC的配置方式:【java Config方式,实现IoC】;

(5) 本篇博客,主要介绍【java Config方式,实现IoC】中的对象实例化的内容;对象的依赖注入会在下篇博客介绍;


【java Config方式,实现IoC】是在Spring3.0之后,推出的一种全新的配置方式,其主要原理是通过Java代码来替代传统的xml文件;

一:Java Config简介

(1)前面的【xm方式,实现IoC】,基本是使用xml配置去实现IoC容器,我们也知道这种方式很麻烦,需要频繁配置xml文件;然后,【注解方式,实现IoC】,让我们在某种程度上摆脱了“需要频繁配置xml的”问题;那么,【java Config方式,实现IoC】就更进一步,这种方式完全不需要xml文件,而是使用Java类来替代原始的xml配置文件;

(2) 和【注解方式,实现IoC】相比,【java Config方式,实现IoC】可以对对象进行集中管理;

●【注解方式,实现IoC】:需要在每一个类上添加如@Controller、@Service等注解;这些注解是分散在每一个类中的,如果工程比较庞大,这些注解配置信息都放在了不同的类中,实际管理起来还是比较麻烦的;

● 【java Config方式,实现IoC】:可以对这些对象进行集中的管理和创建;

(3) 【java Config方式,实现IoC】:可以在程序编译时候进行检查,不容易出错;

●前面的【xm方式,实现IoC】、【注解方式,实现IoC】;对于某些错误的配置,虽然也会检查和提示;但是,对于大部分对象的创建和属性的注入的操作,如果弄错了,只能等程序运行后才能会报错;

● 【java Config方式,实现IoC】因为完全是基于Java来开发的,所以在编译时就能及时发现错误的地方;


二:Java Config核心注解简介

(1) @Configuration:这个注解,放在配置类上; 即,如果一个类使用了@Configuration注解,说明这个类就是个Java Config的配置类,这个配置类的作用就是完全替代xml文件;

(2) @Bean:这个注解用在方法上,那么这个方法返回的对象将被IoC容器管理,同时这个bean的bean Id默认为方法名;

(3) @ImportResource:放在类上,用于加载静态文件;

(4) @ComponentScan:组件扫描;其作用类似于【组件类型注解(对象实例化);@Repository,@Service,@Controller,@Component】中第一次介绍的【context:compoment-scan标签】的作用;


三:案例:【java Config方式,实现IoC】:对象实例化;

1.创建工程s09:创建工程,pom.xml引入Spring依赖,创建readme.md文件

创建工程:s09

在pom.xml中引入依赖:

创建readme.md文件,对项目做简单说明:

2.创建演示用的类:UserDao,UserService,UserController;

UserDao类:

package com.imooc.spring.ioc.dao;
 
 public class UserDao {
 }

UserService类:

package com.imooc.spring.ioc.service;
 
 import com.imooc.spring.ioc.dao.UserDao;
 
 public class UserService {
     private UserDao userDao;
 
     public UserDao getUserDao() {
         return userDao;
     }
 
     public void setUserDao(UserDao userDao) {
         this.userDao = userDao;
     }
 }

说明:

(1) 一般情况下,UserService类需要调用UserDao类中的方法;即,UserService对象需要依赖UserDao对象,即在UserService类中添加了UserDao属性;

(2) 【java Config方式,实现IoC】是需要保留setter方法的;即,添加了UserDao属性后,需要添加setUserDao()方法;

UserController类:

package com.imooc.spring.ioc.controller;
 
 import com.imooc.spring.ioc.service.UserService;
 
 public class UserController {
     private UserService userService;
 
     public UserService getUserService() {
         return userService;
     }
 
     public void setUserService(UserService userService) {
         this.userService = userService;
     }
 }

说明:

(1) 一般情况下,UserController类需要调用UserService类中的方法;即,UserController对象需要依赖UserService对象,即在UserController类中添加了UserService属性;

(2) 【java Config方式,实现IoC】是需要保留setter方法的;即,添加了UserService属性后,需要添加setUserService()方法;

3.创建Config配置类,SpringApplication入口测试类;

Config类:

package com.imooc.spring.ioc;
 
 import com.imooc.spring.ioc.controller.UserController;
 import com.imooc.spring.ioc.dao.UserDao;
 import com.imooc.spring.ioc.service.UserService;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
 public class Config {
 
     @Bean
     public UserDao userDao() {
         UserDao userDao = new UserDao();
         return userDao;
     }
 
     @Bean
     public UserService userService() {
         UserService userService = new UserService();
         return userService;
     }
 
     @Bean
     public UserController userController() {
         UserController userController = new UserController();
         return userController;
     }
 }

说明:

(1)合理性解释: 可以看到在Config配置类中,是通过代码的方式创建的对象,比如上面依旧使用如【UserDao userDao = new UserDao();】的方式去创建对象,这不还是走了没有使用Spring框架之前的老路啊;对此可以这样理解:可以不把Config类看工程的一部分,把这个类和其中的代码看成配置文件;

(2)代码分析:

SpringApplication入口类:

package com.imooc.spring.ioc;
 
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringApplication {
     public static void main(String[] args) {
         ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
String[] ids = context.getBeanDefinitionNames();
for (String id : ids) {
System.out.println(id + ":" + context.getBean(id));
}
}
}

说明:

(1) 【new AnnotationConfigApplicationContext(Config.class);】:因为这儿没有xml文件,而是一个Config.java类;所以使用【new AnnotationConfigApplicationContext(Config.class)】(意思是,基于注解配置的应用程序上下文),传入的参数是Config.java这个类;这样有,就能完成【使用Java Config方式实现Spring IoC一】的IoC容器的初始化工作;

(2) 【context.getBeanDefinitionNames();】获取容器中所有对象的bean name的数组;

(3) 运行结果:

(4) 容器中的对象,是按照在Config类中的前后书写顺序,依次进行实例化的

JavaConfig-对象依赖注入

说明:

(1) 本篇博客内容:介绍【java Config方式,实现IoC】中的对象依赖注入的问题; (2) 为了应对按类型注入时的【同类型,有个对象的问题】,也可以在方法上使用@Primary注解;为了使对象是多例的,也可以在方法上使用@Scope注解;

(3) 本篇博客还介绍了【java Config方式,实现IoC】和【使用注解的类】是兼容的问题,这其中涉及到了@ComponentScan;

案例:【java Config方式,实现IoC】:注入对象依赖;

需求:这个依赖注入,需要通过setter方法来实现;


解决办法:


运行结果:


说明1:上面的过程是按类型注入,还是按名称注入?:

先按名称注入,如果不行再按类型注入;(如果沦落到按类型注入,可以使用@Primary,来解决【同类型的多个对象问题】

其会先按name注入,如果不成功,则会按类型注入;

PS:因为按类型注入我们应该尽量避免,所以在方法名和属性名的对应上,我们最好对应好。

自然,万一沦落到需要按类型注入时候,为了避免【同类型的多个对象问题】我们也是可以在方法上使用@Primary注解的:


说明2:如果想让某个对象是多例的,也可以在方法上使用@Scope注解


案例:【java Config方式,实现IoC】和【使用注解的类】是兼容的;

使用@ComponentScan注解,同时去扫描和加载【使用了注解的类】;

说明:@ComponentScan:组件扫描;其作用类似于【组件类型注解(对象实例化);@Repository,@Service,@Controller,@Component】中第一次介绍的【<context:compoment-scan标签】的作用;


【java Config方式,实现IoC】也可以依赖注入那些【使用注解的类】;

@ImportResource:用于加载静态配置文件,比如导入某个配置文件,让配置文件里面的内容生效;

这个没有深入介绍,以后接触到Spring Boot的时候,再深入了解吧~~

注解的Summary;

(1) 【java Config方式,实现IoC】中的Config类,其作用就是完全替代applicationContext.xml的;只是,外在表现上,是通过代码方式实现的而已;

(2) 【java Config方式,实现IoC】这种方式的好处:在实际使用时,因为都是java代码,如果写错了,IDEA马上就能给出错误提示,即在编译阶段就能及时发现问题。

(3) 【java Config方式,实现IoC】这种方式的坏处,与【xm方式,实现IoC】和【注解方式,实现IoC】相比,【java Config方式,实现IoC】虽然开发体验更好,而且对象被集中的创建和管理,但是这毕竟是源代码,如果产品发布后需要调整,则必须要修改源代码,修改源代码就要重新测试、编译、申请、审批、上传等一系列流程;

(4) 在日常工作中,【java Config方式,实现IoC】更多的用在敏捷开发中;特别适合需要快速迭代和快速上线的工程;比如,后面要接触的Spring boot框架,这个框架是Spring体系中的敏捷开发框架;Spring boot默认就基于【java Config方式,实现IoC】进行配置;

(5) 【xml方式,实现IoC】和【注解方式,实现IoC】更多的是用在大型项目的团队协作中,通过配置文件将每一个功能或模块切分开,然后将切分的模块分配给不同的团队去开发,各司其职;

(6) 【xml方式,实现IoC】,【注解方式,实现IoC】,【java Config方式,实现IoC】这三者,【遇到需要修改功能的时,需要修改源代码,的特性】依次增强,【开发体验】依次更好;在实际项目中,按需选择。

(7)这三种方式,扯再多没用,得实际去使用,在使用中才能更好的感受其中的优劣和选用的时机;

Spring与JUnit4整合

说明:

(1) 本文合理性说明:

● 以前我们知道,每开发完一个模块或功能后,需要及时测试,而JUnit4是我们常采用的测试框架;

● 自然,使用Spring框架开发时,也需要及时的测试;

● JUnit4是个很给力的工具,在测试Spring开发的代码时候,也采用了JUnit4;

● 但是,因为要想测试Spring开发内容,需要涉及到IoC容器的初始化,对象注入等内容;即如何使得JUnit4和Spring融合是个问题;

● 为此,Spring本身提供了【Spring Test】模块,这个模块的作用就是【在Spring中使用JUnit4】;

● 而,本博客的主要内容就是【Spring Test】模块的简单使用;其具体内容,就是【Spring Test如何使用JUnit4,以实现测试基于Spring框架编写的代码】;

(2) 本篇博客只是一个简单的介绍,但要知道本篇博客的内容还是比较重要的,在以后的开发中,会经常使用到本博客中的内容;毕竟,及时的单元测试是一个非常好的习惯;

一:Spring Test模块简介:

(1) Spring框架中,有一个特殊的模块,Test模块,其专用于系统测试;如下图所示;(在【使用XML方式实现Spring IoC【对象的实例化】:基于构造方法实例化对象,之基于【默认构造方法】实例化对象】中,介绍了下面这个图;)

(2) 【Spring Test】在日常开发中,最常用的功能就是【和JUnit单元测试框架进行整合】;

(3) 所谓整合,就是【通过Spring Test可以在JUnit单元测试开始的时候,自动初始化IoC容器】;这个过程是基于注解来完成的,不需要像前面介绍的那样【必须手动的初始ApplicationContext对象】;

(4) 【spring-test】模块,在日常开发中会经常使用;


二:【Spring】和【JUnit4】整合过程

(1) 第一步:Maven工程,需要引入spring-test模块;

(2) 第二步:@RunWith注解 :将JUnit4的运行过程交给Spring来完成,通过这个注解,可以让Spring接管JUnit4的控制权,完成IoC的初始化工作; @ContextConfiguration注解:用于说明,在初始化IoC容器过程中,要加载哪个配置文件

(3) 第三步:创建测试类去测试;


三:代码演示

1.准备工作:创建项目,引入依赖,创建基础类,创建applicationContext.xml配置文件;

(1)创建基于Maven的演示用工程s10;

(2)在pom.xml中引入Spring依赖:【spring-context】和【spring-test;】

<?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <groupId>com.imooc.spring</groupId>
     <artifactId>s10</artifactId>
     <version>1.0-SNAPSHOT</version>
 
     <repositories>
         <repository>
             <id>aliyun</id>
             <name>aliyun</name>
             <url>https://maven.aliyun.com/repository/public</url>
         </repository>
     </repositories>
 
     <dependencies>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-context</artifactId>
             <version>5.3.9</version>
         </dependency>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-test</artifactId>
             <version>5.3.9</version>
         </dependency>
     </dependencies>
 
 </project>

说明:

(1) 在【使用XML方式实现Spring IoC:IoC容器完成【对象的实例化】】中第一次介绍了如下情况,引入【spring-context】这个依赖后,其会自动下载6个相关的依赖和底层依赖;

(2) 但是,还需要引入【spring-test】模块;这个模块的版本需要和【spring-context】保持一致;

(3)创建UserDao类,UserService类;

UserDao类:

package com.imooc.spring.ioc.dao;
 
 public class UserDao {
 
     /**
      * 一个测试用的方法
      */
     public void insert() {
         System.out.println("调用UserDao中的insert()方法,向数据库中,插入了一条用户数据;");
     }
 }

UserService类:

package com.imooc.spring.ioc.service;
 
 import com.imooc.spring.ioc.dao.UserDao;
 
 public class UserService {
     private UserDao userDao;
 
     /**
      * 一个测试用的方法
      */
     public void createUser() {
System.out.println("调用UserService中的createUser()方法,即调用创建用户的业务代码;");
userDao.insert();
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
         this.userDao = userDao;
     }
 }

说明:

(1) UserDao类和UserService类没什么好说的,就是模拟一下,简单的调用关系而已;

(4)在resources目录下,创建applicationContext.xml文件;

applicationContext.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.imooc.spring.ioc.dao.UserDao"/>
     <bean id="userService"
class="com.imooc.spring.ioc.service.UserService">
         <property name="userDao" ref="userDao"/>
     </bean>
 </beans>

说明:

(1) 上面通过【使用XML方式,实现Spring IoC容器】的策略,分别创建了UserDao的对象,UserService对象;同时,利用setter实现对象依赖注入;

2.正式开始测试

(0)JUnit情况说明;

在前面,我们都是创建SpringApplication入口类去测试;


但是,如果在实际开发中,我们往往经常需要边开发边测试,而且有时还需要多个测试用例,所以像上面那种【创建入口类去测试的策略】显然不好;此时JUnit4就是一个很给力的测试工具(通过这个测试工具,能够让我们步步为营,一步一个脚印的去开发);有关JUnit4的内容可以参考【单元测试与Junit4】中的内容;JUnit是Java开发中,最常用的单元测试框架,这是个非常给力的工具;

(1) 在pom.xml中 引入JUnit的依赖;

(2)在test包下,创建测试用例类:需要使用到【@RunWith】和【@ContextConfiguration】;(核心!)

SpringTestor类:

import com.imooc.spring.ioc.service.UserService;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
 import javax.annotation.Resource;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(locations = {"classpath:applicationContext.xml"})
 public class SpringTestor {
     @Resource
     private UserService userService;
 
     @Test
     public void testUserService() {
         userService.createUser();
     }
 }

说明:

(1) 代码分析

分为以下几步:首先,使用【@RunWith,@ContextConfiguration】来初始化IoC容器;然后,在当前类中,从IoC容器中注入所需要的对象;最后,通过注入的对象去调用需要测试的方法;

(2) 测试运行结果: