前言:
在Spring还未出现的年代,都是自己写原始的Servlet来实现接口的开发,写完Servlet接口我们还需要在web.xml中配置这些接口的地址,使用Spring则是一到两个注解就可以搞定,首先开发效率上肯定会提高很多,但是Spring带我们的远远不止这些,Spring的出现对于Java来说可以说是一场主要的变革,下面就来一起学习下Spring吧。
一、认识Spring
Spring主要提供了三大场景的功能,分别是控制反转IOC、属性注入DI、面向切面AOP;这三大场景的实现都有一个核心支撑,就是需要依赖Bean容器(也可以叫Spring容器、IOC容器),Spring的鲜明特点是轻量级、一站式、多组件支持的一个框架,他提供了Java开发的一站式解决方案,无论是MVC还是ORM,Spring都有很好的支持。
1.Spring是什么
Spring是一个免费开源,轻量级非侵入式的一站式框架,他的核心是IOC、DI、AOP和事务的支持,他支持各种框架的整合,可以将Spring视为后端的一个脚手架。下面是一些Spring相关的网址,记在这里方便获取。
官网:https://spring.io/projects/spring-framework
github:https://github.com/spring-projects/spring-framework
maven地址:https://mvnrepository.com/artifact/org.springframework/spring/5.3.9
<dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>5.3.9</version> <type>pom</type> </dependency>
2.Spring带来了什么,为什么必须用Spring
Spring提供了IOC的功能
IOC会帮助我们创建对象。在使用Servlet的年代,Servlet对象的创建需要依赖Tomcat(jBoss等)容器,其他业务层对象、数据访问层对象都需要我们手动来管理,或者自己写工程类来创建。当我们使用Spring以后,这些都可以交给Spring来做,接口使用Controller、业务层使用Service、数据访问层使用Repository,同时加上组件扫描的注解ComponentScan就可以代替以前很多繁琐的代码,还有很多对象我们都可以交给Spring来创建,比如SpringBoot中他的自动配置原理就是通过Spring自动创建了大量的对象,这些对象就是我们默认的配置信息,通过这样实现了自动配置。
Srping提供了DI的功能
DI就是依赖注入,我们既然往Spring容器中存入了对象,肯定是需要把他取出来用的,DI就是将将Spring容器中的对象取出来使用。
Spring提供了AOP的功能
AOP即面向切面。面向切面是一种思想,这个思想必须使用过一段时间才能理解深刻,刚开始理解不深很正常,java编码时经历过这几种编程风格的变化:刚开始我们习惯写面向对象的代码,因为刚开始学java时老师就告诉我们万事万物皆对象,后来我们又学会了面向父类、面向接口编程,再然后又有了面向切面编程的思想。面向对象编码时我们想要修改逻辑肯定必须更改对象代码才可以,面向接口编程时,我们发现只需要更改接口就可以实现对所有实现类的管理,面向切面编程时则更简单了,我们只需要更改切面即可,这样所有面向切面的程序都会达到修改的目的。比如说我们所有的程序都面向了日志切面,那我们只需要更改日志切面就可以实现所有面向这个切面编程的实现。
Spring支持声明式事务
其实Spring支持声明式事务和编程式事务两种事务。大部分都是使用声明式事务,因为编程式事务对代码的侵入比较高。事务是什么,应该没人不知道,事务具有ACID特性,它用以保证一组逻辑的正常执行。Spring支持事务就支持呗,咋还自己起了个声明式事务的名字呢,啥又是声明式事务呢?声明式顾名思义就是这种事务是我们显示声明的,我们可以自己定义事务的传播机制,事务的回滚策略等,所以它叫声明式事务。声明式事务是使用AOP的思想实现的,对代码可以做到0侵入,在开发中可以省去我们很多繁琐的操作,使用过的人都说好,哈哈。
3.Spring的主要模块
上面简单介绍了Spring的IOC、DI、AOP,他们都是Spring最核心的东西,不过Spring并不只是这一点东西,官方将Spring划分为成了如下几个模块,下面对各个模块做下简单的介绍,我们学习Spring最需要掌握的还是他的IOC、DI、AOP这三点,以下各个模块信息介绍来源为官网。
核心容器:Core Container
Core Container由spring-core,spring-beans,spring-context,spring-context-support和spring-expression(Spring 表达语言)模块组成。
spring-core和spring-beans模块提供框架的基本部分,包括 IoC 和依赖注入功能。 BeanFactory是工厂模式的复杂实现。它消除了对编程单例的需求,并允许您将依赖项的配置和规范与实际程序逻辑脱钩。
Context(spring-context)模块构建在核心和 bean 类模块提供的坚实基础上:这是一种以类似于 JNDI 注册表的框架样式方式访问对象的方法。 Context 模块从 Beans 模块继承其功能,并增加了对国际化(例如,使用资源束),事件传播,资源加载以及通过 Servlet 容器透明创建上下文的支持。上下文模块还支持 Java EE 功能,例如 EJB,JMX 和基本远程处理。 ApplicationContext接口是上下文模块的焦点。 spring-context-support支持将常见的第三方库集成到 Spring 应用程序上下文中以进行缓存(EhCache,Guava,JCache),邮件(JavaMail),调度(CommonJ,Quartz)和模板引擎(FreeMarker,JasperReports,Velocity)。
spring-expression模块提供了功能强大的Expression Language,用于在运行时查询和操作对象图。它是对 JSP 2.1 规范中指定的统一表达语言(统一 EL)的扩展。该语言支持设置和获取属性值,属性分配,方法调用,访问数组,集合和索引器,逻辑和算术运算符,命名变量以及按名称从 Spring 的 IoC 容器中检索对象的内容。它还支持列表投影和选择以及常见的列表聚合。
AOP和检测:AOP、Aspects、Instrumentation
spring-aop模块提供了AOP Alliance 兼容的面向方面的编程实现,例如,您可以定义方法拦截器和切入点,以干净地解耦实现应分离功能的代码。使用源级元数据功能,您还可以将行为信息以类似于.NET 属性的方式合并到代码中。
单独的spring-aspects模块提供与 AspectJ 的集成。
spring-instrument模块提供了在某些应用程序服务器中使用的类检测支持和类加载器实现。 spring-instrument-tomcat模块包含 Spring 的 Tomcat 的检测代理。
消息:Messaging
该模块具有来自* Spring Integration *项目的关键抽象,例如Message,MessageChannel,MessageHandler等,这些模块可作为基于消息的应用程序的基础。该模块还包括一组 Comments,用于将消息 Map 到方法,类似于基于 Spring MVCComments 的编程模型。
数据访问:Data Access
“数据访问/集成”层由 JDBC,ORM,OXM,JMS 和事务模块组成。
spring-jdbc模块提供了JDBC-抽象层,从而无需进行繁琐的 JDBC 编码和解析数据库供应商特定的错误代码。
spring-tx模块支持程序性和声明性 TransactionManagement,以实现实现特殊接口的类以及所有 POJO(普通的 Java 老式对象)。
spring-orm模块为流行的object-relational mapping API 提供集成层,包括JPA,JDO和Hibernate。使用spring-orm模块,您可以将所有这些 O/RMap 框架与 Spring 提供的所有其他功能结合使用,例如前面提到的简单的声明式事务 Management 功能。
spring-oxm模块提供了一个支持Object/XML mapping实现的抽象层,例如 JAXB,Castor,XMLBeans,JiBX 和 XStream。
spring-jms模块(Java 消息服务)包含用于生成和使用消息的功能。从 Spring Framework 4.1 开始,它提供了与spring-messaging模块的集成。
WEB交互:Web
Web 层由spring-web,spring-webmvc,spring-websocket和spring-webmvc-portlet模块组成。
spring-web模块提供了面向 Web 的基本集成功能,例如 Multipart 文件上传功能以及使用 Servlet 侦听器和面向 Web 的应用程序上下文对 IoC 容器进行初始化。它还包含 HTTPClient 端和 Spring 远程支持的 Web 相关部分。
spring-webmvc模块(也称为 Web-Servlet 模块)包含 Spring 的 model-view-controller(MVC)和针对 Web 应用程序的 REST Web Services 实现。 Spring 的 MVC 框架在域模型代码和 Web 表单之间提供了清晰的分隔,并与 Spring 框架的所有其他功能集成在一起。
spring-webmvc-portlet模块(也称为 Web-Portlet *模块)提供了可在 Portlet 环境中使用的 MVC 实现,并镜像了基于 Servlet 的spring-webmvc模块的功能。
测试:Test
spring-test模块通过 JUnit 或 TestNG 支持 Spring 组件的unit testing和integration testing。它提供了 Spring ApplicationContext的loading和那些上下文的caching。它还提供了mock objects,可用于隔离测试代码。
二、IOC
上面一通废话说完了,其实真正使用Spring时,我们就是使用Spring提供的四点特色功能:IOC、DI、AOP、声明式事务,其他的功能不算他的特色,也没有什么难度,我们着重掌握这四点即可,下面先介绍下IOC。
IOC(Inversion of Control)控制反转,也就是对象创建的控制权的反转,那为什么要反转呢?我们自己创建不是更好吗?控制反转最直观的效果就是可以解耦合,一个接口我们可以注入多种实现类,具体使用哪个就由调用方决定好了,但是若是自己创建一旦变动就需要更改实现,这是很麻烦的,一个程序优秀与否耦合度是一个重要指标。那Spring又是怎么实现控制反转的呢?事实上Spring通过反射加工厂模式实现了IOC的功能,这篇文章不会详细分析IOC的底层,我们只做使用层面的探究。总结一句话:控制反转是一种通过xml或者注解描述,同时通过第三方生产或获取对象的方式,在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入DI。
1.xml实现IOC-属性注入
<?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> <bean name="student2" id="student" class="com.cheng.pojo.Studeng"> <property name="name" value="cheng"/> <property name="age" value="18"/> </bean> </beans> </beans>
通过这样一个简单的配置我们就将实体类com.cheng.pojo.Studeng交给了IOC容器来管理,下面我们来测试下,如下:
我们发现正常拿到了注入的对象,说明IOC容器确实帮我们创建出了一个对象。那我们就来总结下这个过程吧。
导入依赖---->创建applicationcontext.xml配置文件---->创建测试类。就这么三步就完成了一个IOC的开发,是不是非常简单呢?虽然很简单,不过配置文件中的信息我们还是需要解读下的,一起来看下配置文件的信息吧。
bean的属性id
用来声明当前bean在容器中的唯一标识,获取bean示例时就是通过它来找到唯一的对象的。
bean的属性name
别名设置,与MyBatis中的别名有异曲同工之秒,其实别名最终也是通过id来找到bean的,只不过是给了使用者多一个选择的操作而已,在获取bean时我们也可以通过传入别名来获取bean。值得说到的是,当我们不设id只设置了name时,id默认与name同名,且id不支持特殊字符,name可以支持特殊字符。
bean的属性class
class的值应该是类的全限定名,IOC容器正是通过class属性来找到待管理的类。
property的属性name
property顾名思义就是属性的意思了,这里的name指向属性的名称。
property的属性value
value指向属性的值,不过通过value我们只能声明常量类型的基本数据类型的值,而不能声明引用数据类型。
上面一通介绍,我们应该会使用属性注入的方式为对象赋值了,但我们可以发现这些属性都是基本数据类型,要是对象的属性是一个自定义的引用类型怎么办呢?这时就需要使用property的其他属性了:
bean的属性ref
该属性用来指向其他bean的id或者name,从而将其他bean引入到当前对象中,借助bean的这个属性,我们就可以实现引用数据类型的赋值了。下面展示下该属性的使用
<beans> <bean name="student2" id="student" class="com.cheng.pojo.Studeng"> <property name="name" value="cheng"/> <property name="age" value="18"/> <property name="studeng" ref="studeng"/> </bean> <bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng"> <property name="name" value="jin"/> <property name="age" value="3"/> </bean> </beans>
上面的代码中,我们就完成了引用数据类型的赋值,一个studeng对象中维护了另一个studeng对象,此时就可能会有另一个问题产生:若是这两个对象相互引用怎么办?就会有循环依赖的现象产生,这个问题后面会说,我们可以先一起思考思考。
此外,我们对象是创建出来了,也可以正常获取,那多次获取的对象会不会是一个呢?事实上当我们不作指定时,IOC容器中的对象都是单例的模式,我们可以验证下,如下所示,可以看到多次获取后的对象他们地址相等,说明了是IOC容器中的对象都是单例的。
那要是,就是不想让对象是单例的该怎么处理呢?此时需要引入bean的另一个属性了scope:
bean的属性scope
该属性用来表示bean的创建方式是单利还是多例的,单例是singleton这个也是默认值,多例是prototype。不过大部分人还是习惯将这个属性称之为作用范围。
这样就基本介绍完了xml实现IOC时使用属性注入的实现,有一点需要特别注意属性注入底层需要调用get、set方法,这些是必须要提供的。
2.xml实现IOC-构造器注入
已经懂了属性注入,那么构造器注入其实就相当简单了,使用构造器注入无非就是将property标签换成constructor-arg而已,其他大致相同,不过使用构造器注入官方给出了三种可以使用的方式,如下所以:
方式一:name-value模式
该模式使用与属性注入完全一样,都是通过name来声明bean的属性名,通过value或者ref来声明属性值。
<bean id="studengContru" class="com.cheng.pojo.Studeng"> <constructor-arg name="name" value="mu" ></constructor-arg> <constructor-arg name="age" value="0" ></constructor-arg> <constructor-arg name="studeng" ref="studentNew" ></constructor-arg> </bean> <bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng"> <property name="name" value="jin"/> <property name="age" value="3"/> </bean>
方式二:type-value模式
该模式不推荐使用,该模式的原理是通过bean的类型与这里声明的类型进行匹配来映射的,若是多个参数是同种数据类型就会无法处理了,所以这种赋值的方式不推荐。
<bean id="studengContru2" class="com.cheng.pojo.Studeng"> <constructor-arg type="java.lang.String" value="mu1" ></constructor-arg> <constructor-arg type="java.lang.Integer" value="0" ></constructor-arg> <constructor-arg type="com.cheng.pojo.Studeng" ref="studentNew" ></constructor-arg> </bean> <bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng"> <property name="name" value="jin"/> <property name="age" value="3"/> </bean>
方式三:index-value模式
该模式是通过下标来匹配参数的,使用起来也没有什么问题,只不过相对于通过属性名来映射来说,可读性并不是太高,综合以上三种实现构造器注入的方式,建议使用与property相同的name-value模式来进行属性的注入。
<bean id="studengContru3" class="com.cheng.pojo.Studeng"> <constructor-arg index="0" value="mu2" ></constructor-arg> <constructor-arg index="1" value="0" ></constructor-arg> <constructor-arg index="2" ref="studentNew" ></constructor-arg> </bean> <bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng"> <property name="name" value="jin"/> <property name="age" value="3"/> </bean>
下面来测试下三种通过构造器注入对象属性的方式是否好用,如下所示,可以看到都是好用的,说明我们写的没有什么问题,不过日常开发使用中若是使用构造器注入建议使用name-value的模式,若是不是必须使用构造器建议还是使用属性注入的方式为bean的属性赋值。
从上面的代码中我们可以看出也就一个标签constructor-arg没有见过,此外还有他的index和type属性没有使用过,其他与property的属性是没有区别的,下面介绍下这些新出现的标签和属性吧。
constructor-arg标签
使用构造器来进行bean属性赋值时,需要使用该标签,他里面的name对象bean的属性,value对应基本数据类型的值,ref对应引用数据类型的值,这些都与property中的属性别无二致。
上面例子中
constructor-arg的属性index
用以声明当前的参数对应到构造器中参数的位置,该下标从0开始。
constructor-arg的属性type
用以声明当前的参数对应构造器中参数的类型,若是多个参数类型相同,则无法映射,所以不建议使用该参数。
IOC的注入方式其实也就是这两大类(通过xml实现时),一类是通过属性注入底层调用set方法,一种通过构造器注入底层调用构造方法,下面画个导图总结下这两种IOC实现所需要的不同标签:
3.构造器注入与属性注入的区别
事实上构造器注入并不是一种特别推荐的方式,推荐的还是使用属性注入,因为当发生循环依赖时,构造器注入就会报错,这是因为使用构造器注入,必须保证需要的所有的属性已经创建完了,才能使用构造器注入,但是循环依赖时因为有循环所以对象不可能全部创建就会报错,所以一般还是建议使用属性注入的方式,如下所示,这两个bean互相引用时,获取任何一个bean都是失败的,那为什么使用属性注入不会呢?这就必须说Spring的三级缓存了,这个单独一个章节讲这里就不细说了,先对这个概念有个了解即可。
<bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng"> <constructor-arg name="name" value="yu" index="0"></constructor-arg> <constructor-arg name="age" value="0" index="1"></constructor-arg> <constructor-arg name="studeng" ref="studengContru" index="2"></constructor-arg> </bean> <bean id="studengContru" class="com.cheng.pojo.Studeng"> <constructor-arg name="name" value="mu" index="0"></constructor-arg> <constructor-arg name="age" value="0" index="1"></constructor-arg> <constructor-arg name="studeng" ref="studentNew" index="2"></constructor-arg> </bean>
4.component注解实现IOC
component组件的意思,Spring中通过这个注解可以将对应的javaBean的对象创建权交给IOC容器,不过component需要与component-scan配合使用,这样我们就无需在手动的写bean标签了,在真正使用中也都是使用这种模式来将javaBean的创建权交给IOC容器的。需要说的是component还有三个衍生注解:controller、service、repository,这三个注解只是为了区分不同的业务场景而设定的,其实他们三个和component没有任何区别,都可以混用,因为没有任何区别,这里就只展示component与component-scan的使用饿了。
5.使用Configuration、Bean注解实现IOC
上面已经介绍了使用xml通过构造器或者属性来实现的IOC、通过component配合component-scan实现IOC,其实还有一种比较常用的方式就是通过Configuration和Bean注解来实现IOC,这种IOC的实现方式在SpringBoot中最为常见,SpringBoot的默认大于配置其实就是通过大量的Configuration+Bean注解来实现的,一起看下要怎么使用他们吧。
三、DI
DI(Dependency Injection)依赖注入的意思,上个章节里介绍了IOC,其实里面已经使用过了DI了,我们使用property来进行属性注入时就是DI的过程。依赖注入顾名思义就是将依赖注入进来,这个依赖可能是常量也可能是来自于IOC容器,上面介绍了两种DI的方式,分别是通过构造器进行DI、通过set方法进行DI(property属性注入),其实还有其他方式可以实现DI,只不过这两种是我们最常用的方式,这个章节里就来详细说说DI的三大方式。
1.构造器实现DI
这块内容其实第二节介绍IOC时已经介绍过了,使用构造器进行DI时,我们有三种选择,使用name、type、index三种都可以完成构造器的DI,这里展示下这三种DI的代码,其余部分就不重复展示了:
方式一:name-value模式
该模式使用与属性注入完全一样,都是通过name来声明bean的属性名,通过value或者ref来声明属性值。
<bean id="studengContru" class="com.cheng.pojo.Studeng"> <constructor-arg name="name" value="mu" ></constructor-arg> <constructor-arg name="age" value="0" ></constructor-arg> <constructor-arg name="studeng" ref="studentNew" ></constructor-arg> </bean> <bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng"> <property name="name" value="jin"/> <property name="age" value="3"/> </bean>
方式二:type-value模式
该模式不推荐使用,该模式的原理是通过bean的类型与这里声明的类型进行匹配来映射的,若是多个参数是同种数据类型就会无法处理了,所以这种赋值的方式不推荐。
<bean id="studengContru2" class="com.cheng.pojo.Studeng"> <constructor-arg type="java.lang.String" value="mu1" ></constructor-arg> <constructor-arg type="java.lang.Integer" value="0" ></constructor-arg> <constructor-arg type="com.cheng.pojo.Studeng" ref="studentNew" ></constructor-arg> </bean> <bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng"> <property name="name" value="jin"/> <property name="age" value="3"/> </bean>
方式三:index-value模式
该模式是通过下标来匹配参数的,使用起来也没有什么问题,只不过相对于通过属性名来映射来说,可读性并不是太高,综合以上三种实现构造器注入的方式,建议使用与property相同的name-value模式来进行属性的注入。
<bean id="studengContru3" class="com.cheng.pojo.Studeng"> <constructor-arg index="0" value="mu2" ></constructor-arg> <constructor-arg index="1" value="0" ></constructor-arg> <constructor-arg index="2" ref="studentNew" ></constructor-arg> </bean> <bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng"> <property name="name" value="jin"/> <property name="age" value="3"/> </bean>
2.set方法实现DI
set方法进行DI才是我们的首选,也是我们开发中真正比较常用的方式,介绍依赖set方法进行DI时,笔者会较少所有常用的数据类型的注入包括基本数据类型、引用数据类型、数组、List、Map、set等集合,其实都是大同小异,这里使用Supplier和Shop两个自定义类型来演示这些数据的注入,Supplier和Shop的代码如下:
@Data @AllArgsConstructor @NoArgsConstructor public class Supplier { private int oid; private Shop shop; private String[] address; private List<String> product; private Map<String,String> detail; private Set<String> bankCode; private Properties properties; } @Data public class Shop { private String name; }
下面开始正式各种数据类型的注入方式:
基本数据类型注入
<!--基本数据类型注入--> <property name="oid" value="100001"/>
引用数据类型注入
<!--引用数据类型注入--> <property name="shop" ref="shop"/>
数组数据类型注入
<!--数组类型注入--> <property name="address"> <array> <value>北京市</value> <value>上海市</value> <value>广州市</value> </array> </property>
List数据类型注入
<!--List数据类型注入--> <property name="product"> <list> <value>商品1号</value> <value>商品二号</value> <value>商品三号</value> </list> </property>
Map数据类型注入
<!--Map数据类型注入--> <property name="detail"> <map> <entry key="keyOne" value="valueOne"></entry> <entry key="keyTwo" value="valueTwo"></entry> </map> </property>
Set数据类型注入
<!--Set数据类型注入--> <property name="bankCode"> <set> <value>1111111222222333</value> <value>2222221111133333</value> <value>4444442222222111</value> </set> </property>
Properties数据类型注入
<!--properties数据类型注入--> <property name="properties"> <props> <prop key="jdbc.driver">jdbc.mysql.Deriver</prop> <prop key="jdbc.url">mysql:3306</prop> <prop key="jdbc.user">root</prop> <prop key="jdgc.pwd">1111</prop> </props> </property>
以上展示了七种不同的数据类型的注入,不过在真正的开发环节我们大多使用的都是基本数据类型的注入和引用数据类型注入,但是其他类型的数据类型我们也是要会的,指不定啥时候会用到,下面测试下这七种数据类型依赖注入是否成功:
下面是详细输出,可以看到我们设置的所有属性都拿到了,说明以上操作都没有问题。
Supplier(oid=100001, shop=Shop(name=8号店铺), address=[北京市, 上海市, 广州市], product=[商品1号, 商品二号, 商品三号], detail={keyOne=valueOne, keyTwo=valueTwo}, bankCode=[1111111222222333, 2222221111133333, 4444442222222111], properties={jdbc.url=mysql:3306, jdgc.pwd=1111, jdbc.driver=jdbc.mysql.Deriver, jdbc.user=root})
3.c命名空间、p命名空间实现DI
除了我们常用的构造器注入和set方法注入以外,spring还提供了c命名空间和p命名空间两种依赖注入的方式,其实这两种依赖注入底层依然是构造器注入和set方法注入,只不过是简化了操作而已,下面展示下这两种操作。
c namespace
使用c命名空间需要导入其对应的约束:xmlns:c=“http://www.springframework.org/schema/c”,然后我们就可以通过如下方式在bean标签中使用即可,c命名空间的原理是使用bean的构造器来实现DI的,所以要求我们使用c命名空间时必须提供对应的有参构造器。
<bean id="shop" class="com.cheng.pojo.Shop" c:name="张三的店铺"> </bean>
p namespace
使用p命名空间需要导入其对应的约束:xmlns:p=“http://www.springframework.org/schema/p”,然后我们就可以通过如下方式在bean标签中使用即可,p命名空间的原理是使用bean的set方法来实现DI的,所以要求我们使用p命名空间时必须提供对应的set方法才行。
<bean id="shopNew" class="com.cheng.pojo.Shop" p:name="李四的店铺"> </bean>
可以发现当我们使用c命名空间和p命名空间来完成DI时,是相对来说较为简单的,无需在单独写property标签和construcotr-arg标签,其实c命名空间和p命名空间也就是省略了这两个标签的书写而已,底层还是一样的。
4.使用bean标签的autowired属性完成自动DI
bean标签提供了autowired属性,用来支持自动注入,何为自动注入呢,就是不需要我们显示的指示使用哪个引用,Spring会自动帮我们寻找。autowired属性支持两个参数:byName、byType,下面就根据这两种不同的自动注入方式来聊聊bean标签的自动装配吧。
byName:根据名称实现自动装配
根据名称实现自动装配,这里的名称是指的set方法中的名称,比如setDog,就会IOC容器中寻找id为dog的bean来实现装配,如下:
<bean id="people" class="com.cheng.pojo.People" autowire="byName"> <property name="name" value="学习ing"/> <property name="age" value="28"/> </bean> <bean id="dog" class="com.cheng.pojo.Dog"> <property name="name" value="阿花"/> </bean> <bean id="computer" class="com.cheng.pojo.Computer"> <property name="category" value="HuaWei"/> </bean>
byType:根据类型实现自动装配
根据类型实现自动装配,这里的类型指的是属性的类型,因此若是使用这种方式来实现自动装配,我们可以不声明bean的id也没有任何问题,但是这是不推荐的,不利于阅读和问题的发现,下面是根据类型的自动注入实现:
<bean id="people" class="com.cheng.pojo.People" autowire="byType"> <property name="name" value="学习ing"/> <property name="age" value="28"/> </bean> <bean class="com.cheng.pojo.Dog"> <property name="name" value="阿花"/> </bean> <bean class="com.cheng.pojo.Computer"> <property name="category" value="HuaWei"/> </bean>
这就是通过bean标签的autowired属性来实现的自动DI了,不过真正的开发中我们都是使用Autowired、Qualifier、Resource这三种注解来实现自动DI,而不是使用bean标签的autowired,以上两种比较简单,就不重复测试了。
5.使用Autowired、Qualifier或者Resource完成自动DI
这里要介绍的才是工作中真正使用的部分,工作中最为常用的就是Autowired或者Resource了,至于Qualifier必须得与Autowired配合使用,所以我们要么是使用Autowired要么是使用Resource了,这三种注解描述的DI都是针对引用数据类型来说的而不是基本数据类型这个必须要清晰,下面来介绍下他们三个。
@Autowired根据类型自动装配
Autowired注解与bean标签中的autowired属性是不同的,他们只是名称类似,Autowired模式是byType的方式去寻找对象,使用如下所示:
@Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class People { private String name; private Integer age; @Autowired private Dog dog; @Autowired private Computer computer; }
以上是java代码,下面是xml配置文件部分,这部分必须要说的是,要想使用注解,必须有两个前提
①导入context约束
②开启注解配置支持:<context:annotation-config/>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans 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:annotation-config/> <!--仅仅注入了基本属性,其他使用注解注入--> <bean id="people" class="com.cheng.pojo.People"> <property name="name" value="学习ing"/> <property name="age" value="28"/> </bean> <bean id="dog111" class="com.cheng.pojo.Dog"> <property name="name" value="阿花"/> </bean> <bean id="computer111" class="com.cheng.pojo.Computer"> <property name="category" value="HuaWei"/> </bean> </beans>
下面测试下使用Autowired注入属性是否正常,如下图可见应用类型使用Autowired注解注入正常。
@Qualifier配合Autowired实现根据名称自动装配
若是IOC容器中同一类型的bean存在多个会出现什么情况?此时我们直接使用Autowired就会报这个异常:NoUniqueBeanDefinitionException,要想解决这个问题有两种办法,要么再加一个注解Qualifier,通过Qualifier来指定使用IOC容器中的哪个bean,要么直接使用Resource直接,这里先说下Qualifier的使用。
@Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class People { private String name; private Integer age; @Autowired @Qualifier(value = "dog222") private Dog dog; @Autowired private Computer computer; }
以上是java代码,与上一个例子,相比就是多了一个Qualifier直接,该注解有一个value属性,我们通过value来执行想要使用IOC容器中的哪个bean,value对应容器中bean的id。下面是xml文件部分,与上一个例子相比只多了一个Dog类的实例。
<!--开启注解配置支持--> <context:annotation-config/> <!--仅仅注入了基本属性,其他使用注解注入--> <bean id="people" class="com.cheng.pojo.People"> <property name="name" value="学习ing"/> <property name="age" value="28"/> </bean> <bean id="dog111" class="com.cheng.pojo.Dog"> <property name="name" value="阿花"/> </bean> <bean id="dog222" class="com.cheng.pojo.Dog"> <property name="name" value="阿花"/> </bean> <bean id="computer111" class="com.cheng.pojo.Computer"> <property name="category" value="HuaWei"/> </bean>
加上Qualifier注解后就不会提示NoUniqueBeanDefinitionException这个问题了。
@Resource默认根据类型类型装配,加name则根据名称装配
Autowired和Qualifier都是Spring提供的注解,Resource则是java提供的注解,这与前面两个注解是本质的不同,那么为什么java提供的注解可以在Spring的容器中找得到对应的Bean呢,这就需要说下JavaEE的JNDI规范了,这里不深入探讨,只简略说下原委帮助理解,JNDI主要是为服务和资源提供一个间接层的模块,他是一种规范,Spring就实现了这种规范,所以我们可以使用Resource来获取IOC容器中的bean。下面是Resource的部分源码
@Target({TYPE, FIELD, METHOD}) @Retention(RUNTIME) public @interface Resource { /** * The JNDI name of the resource. For field annotations, * the default is the field name. For method annotations, * the default is the JavaBeans property name corresponding * to the method. For class annotations, there is no default * and this must be specified. */ String name() default ""; ...... ......
笔者英语也不好,我们可以借助翻译软件翻译一下,再结合原文看下就可以理解name方法的大致意思:这个值应该是资源的JNDI名称,当注解加在属性上时,那么这个默认的名称是属性名。。。。
这样是不是就理解了Resource的用法了呢?也就是说当我们只加了==Resource不指定name时,则默认根据属性的名称去IOC容器中找bean,若是找不到才会根据类型去找,若是我们显示指定了name值,则会直接根据名称去IOC容器中查找对应的bean。==这段话其实也很好验证,要是想要验证是不是默认根据属性名查找,我们只需要两步:
①不为Resource指定name,在配置文件中配置属性名对应的bean和另外两个同类型不同id的bean,测试Resource注入正常没问题。
②我们删掉与属性名相同的bean,然后再测试就会发现报了这个错:NoUniqueBeanDefinitionException,这是因为根据默认的name值找不到bean,然后根据类型查找发现了多个同类型的bean,所以报错了,这样就可以验证Resource默认的name就是属性名了,同时也可以验证名称不匹配时会根据类型查找。
上面一直都是在说Resource的原理,来看下他的使用吧,使用其实很简单:
@Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class People { private String name; private Integer age; @Resource private Dog dog; @Autowired private Computer computer; }
上面是使用Resource的代码,下面是对应的配置文件核心代码
<!--开启注解配置支持--> <context:annotation-config/> <!--仅仅注入了基本属性,其他使用注解注入--> <bean id="people" class="com.cheng.pojo.People"> <property name="name" value="学习ing"/> <property name="age" value="28"/> </bean> <bean id="dog111" class="com.cheng.pojo.Dog"> <property name="name" value="阿花"/> </bean> <bean id="dog" class="com.cheng.pojo.Dog"> <property name="name" value="阿花"/> </bean> <bean id="computer111" class="com.cheng.pojo.Computer"> <property name="category" value="HuaWei"/> </bean>
下面提供测试截图:
下面展示下以上部分的总结导图:
未完待续。。。。。。。。
四、AOP 1.静态代理 2.动态代理-jdk 3.动态代理-cglb 4.xml配置实现AOP 5.注解实现AOP 6.AOP的应用展示 五、事务 1.声明式事务 2.编程式事务 3.Spring事务与MyBatis事务整合 六、Spring的其他关键点 上面五个小节基本介绍完了Spring的核心,掌握了这些其实已经可以说熟练掌握Spring了,不过还有一些小功能可能没有阐述全面,这个小节过个补充。 1.bean的作用域 介绍IOC时其实提过bean的作用域,我们是通过bean标签的scope属性来控制bean的作用域的,在说IOC时提到过scope的值可以是singleton、prototype两种,其实除了这两种还支持另外三种值的设定request、session、application,乍一看就会发现这三个值都是与web有关的,事实上也是如此只有在web环境下才支持这三种值的设定(SpringMvc中),日常使用中基本全是singleton模式,不过其他的也是要了解的。 - singleton:单例模式,使用单例模式创建bean ~~~xml <bean id="shop2" class="com.cheng.pojo.Shop" scope="singleton"/> ~~~ - prototype:原型模式,每次请求都会创建一个bean,多线程场景中可能会用 ~~~xml <bean id="shop2" class="com.cheng.pojo.Shop" scope="prototype"/> ~~~ - request:在一次request共用一个bean ~~~xml <bean id="shop2" class="com.cheng.pojo.Shop" scope="request"/> ~~~ - session:在session中共用一个bean ~~~xml <bean id="shop2" class="com.cheng.pojo.Shop" scope="session"/> ~~~ - application:在一个上下文中共用一个bean,其实和单例有些类似了 ~~~xml <bean id="shop2" class="com.cheng.pojo.Shop" scope="application"/> ~~~ 2.Spring的缓存 七、总结Spring注解