文章目录
一、概述
Spring
官网:https://spring.io/
(一)Spring家族体系
Spring
框架自2003
年由Rod Johnson
设计并实现以来,经历了多个重大版本的发展和演进,已经形成了一个庞大的家族式技术生态圈。目前,Spring
已经是Java EE
领域最流行的开发框架,在全球各大企业中都得到了广泛应用。
①简介
Spring
是一个生态体系(也可以说是技术体系),由许多开源框架和组件组成,是一个统称,其中包含了Spring Framework
、Spring Boot
、Spring Cloud
等等。
七大核心技术体系:
分别是微服务架构、响应式编程、云原生、Web
应用、Serverless
架构、事件驱动以及批处理。
这些技术体系各自独立但也有一定交集,例如,微服务架构往往会与基于Spring Cloud
的云原生技术结合在一起使用,而微服务架构的构建过程也需要依赖于能够提供RESTful
风格的Web
应用程序等。
②Spring与SpringFramework
Spring
是一个生态体系,其中包含了Spring Framework
、Spring Boot
、Spring Cloud
等等,而Spring Framework
是整个spring
生态的基石,是一个一站式的轻量级的java
开发框架,核心技术为:依赖注入、事务管理、WEB
应用、数据访问等。
Spring
和SpringFramework
并非同一概念,前者是一种生态体系,而后者是该生态体系的基石框架,我们一般常说的Spring
框架指的就是Spring Framework
,故而,本文对Spring
的介绍实质上就是SpringFramework
的基本使用。
(二)SpringFramework
早在2002
年的11
月份,Rod Johnson
发表了一本名为《Expert One-on-One J2EE Design and Development》
的书。这本书中就包含了Spring
框架代码,最初这个框架叫做Interface 21 framework
,后来Yann
提议命名为Spring
(春天),寓意 :Spring
代表了一个新的开始,结束了传统J2EE
开发的寒冬。
所有我们现在能看到的Spring
家族技术体系都是在Spring Framework
基础上逐步演进而来的,SpringFramework
是Spring
、SpringMVC
、SpringBoot
、SpringCloud
、SpringData
、SpringSecurity
等的基础框架。
①整体架构
组成Spring
框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
功能模块 | 功能介绍 |
---|---|
Web | JavaWeb模块,提供了相关的Web功能。 |
Core Container | 核心容器,包含了spring-core,spring-beans,spring-context,spring-context-support,spring-expression这五个模块,是其他模块建立的基础。 |
AOP | 提供了面向切面编程,提供了比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态把这些功能添加到需要的代码中。 |
Aspects | 提供与AspectJ的集成,是一个功能强大且成熟的面向切面编程框架。 |
InStrumentation | 提供了类工具的支持和类加载器的实现。 |
Messaging | 为集成messaging api和消息协议提供支持。同样也提供了一些映射消息到方法的注解,类似spring mvc注解。 |
Testing | spring-test模块使用JUnit或者TestNG为Spring各个组件提供单元测试与集成测试。 |
Data Access/Integration | 提供了对数据访问/集成功能的支持,包含JDBC, ORM,OXM, JMS 和 Transaction 模块。 |
SpringMVC | 提供了面向Web应用程序的集成功能。 |
- Data Access/Integration:提供数据访问/集成等功能。
- JDBC:提供了一个JDBC样例模板,消除冗长JDBC编码和必须的事务控制。
- ORM:提供了与"对象-关系"映射(在本专栏
MyBatis
一文中提过)框架集成的API
,包括JPA
、JDO
、Hibernate
、MyBatis
等。 - OXM:提供了
Object/XML
映射的抽象层实现,如JAXB
、Castor
、XMLBean
、JiBX
、XStream
。将java
对象映射成为XML
数据,或者将XML
数据映射成java
对象。 - JMS:
java
消息服务,提供了一套“消息生产者、消息消费者”模板,JMS
用于两个应用程序之间,或者分布式系统中发送消息,进行异步通信。 - Transaction:支持编程和声明式事务管理。
- Web:包含
Web
应用开发时,用到Spring
框架时所需的核心类,包括自动载入WebApplicationContext
特性的类、Struts
与JSF
集成类、文件上传的支持类、Filter
类和大量工具辅助类。- Web:提供了基本
web
集成特性,例如:多文件上传、使用Servlet
监听器的IoC
容器初始化以及web
应用上下文。 - servlet:提供了
SpringMVC Web
框架实现,SpringMVC
提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等以及一套非常简易的JSP
标签。 - WebSocket:提供了简单的接口,用户只需要实现接口就可以快速搭建
websocket server
,从而实现双向通讯。 - Portlet:提供了
Portlet
环境中MVC
实现。
- Web:提供了基本
- Core Container:
SpringFramework
核心容器,是SpringFramework
框架的基础,所有模块都构建于核心模块之上。- Beans模块:提供了框架的基础部分,包含访问配置文件、创建、管理
bean
以及进行控制反转和依赖注入等相关操作的支持。 - Core核心模块:
Spring
的其他组件要都要使用到这个包里的类,是其他组件的基本核心,封装了Spring
框架的底层部分,包括资源访问、类型转换及一些常用工具类。 - Context上下文模块:建立在
Core
和Beans
模块的基础之上,提供了一种类似于JNDI
注册器的框架式的对象访问方法,通过上下文可以获得容器中的Bean
,ApplicationContext
接口是Context
模块的关键。 - SpEL(表达式语言模块):提供了强大的表达式语言支持,用于在运行时查询和操纵对象,支持访问和修改属性值,方法调用,等功能。
- Beans模块:提供了框架的基础部分,包含访问配置文件、创建、管理
②核心jar包
Spring4
中官方给出的结构图:
图中将Spring
分为了五个部分,分别是core
、aop
、data access
、web
、test
,图中每个圆角矩形都对应一个jar
,如果在maven
中配置,所有这些jar的groupId
都是org.springframework
,每个jar有一个不同的artifactId
,另外,instrumentation
有两个jar,还有一个spring-context-support
图中没有列出,所以spring4
的jar包一共是20
个。
下面介绍这五个部分的jar
包之间的依赖关系(groupId
都是springframework
,以下均为artifactId
),使用的是Maven
工具和Maven Helper
插件。
1.Data Access
Data Access
(数据访问)部分包含5
个模块:
- spring-jdbc:
jdbc
支持。 - spring-tx:事务控制。
- spring-orm:对象关系映射,集成
orm
框架。 - spring-oxm:对象
XML
映射。 - spring-jms:
java
消息服务。
包 | 所含jar包 |
---|---|
spring-jdbc | spring-beans——>spring-core |
spring-core——>spring-jcl | |
spring-tx | |
spring-tx | spring-core |
spring-beans | |
spring-orm | spring-core |
spring-beans | |
spring-jdbc | |
spring-tx | |
spring-oxm | spring-core |
spring-beans | |
spring-jms | spring-core |
spring-beans | |
spring-messaging——>spring-beans | |
spring-messaging——>spring-core | |
spring-tx |
2.Web
- spring-web:基础
web
功能,如文件上传。 - spring-webmvc:
mvc
实现。 - spring-webmvc-portlet:基于
portlet
的mvc实现。 - spring-websocket:为
web
应用提供的高效通信工具。 - spring-messaging:用于构建基于消息的应用程序。
包 | 所含jar包 |
---|---|
spring-web | micrometer-obsercation——>micrometer-commons |
spring-beans——>spring-core | |
spring-core——>spring-jcl | |
spring-webmvc | spring-beans |
spring-aop——>spring-beans | |
spring-aop——>spring-core | |
spring-context——>spring-aop | |
spring-context——>spring-beans | |
spring-context——>spring-core | |
spring-core | |
spring-web | |
spring-expression——>spring-core | |
spring-websocket | spring-context |
spring-core | |
spring-web | |
spring-webmvc-portlet | spring-beans |
spring-context | |
spring-core | |
spring-web | |
spring-webmvc |
3.Aop
- spring-aop:面向切面编程。
- spring-aspects:集成
AspectJ
。 - spring-instrument:提供一些类级的工具支持和
ClassLoader
级的实现,用于服务器。 - spring-instrument-tomcat:针对
Tomcat
的instrument
实现
包 | 所含jar包 |
---|---|
spring-aop | spring-core——>spring-jcl |
spring-beans——>spring-core | |
spring-aspects | aspectjweaver |
spring-instrument | |
spring-instrument-tomcat |
4.Test
- spring-test:
spring
测试,提供junit
与mock
测试功能。
包 | 所含jar包 |
---|---|
spring-test | spring-core——>spring-jcl |
5.core
-
spring-core:包含
Spring
框架基本的核心工具类,Spring
其他组件都要用到这个包中的类,依赖注入IoC
与DI
的最基本实现。 -
spring-beans:所有应用都要用到的,它包含访问配置文件、创建和管理
Bean
以及进行Inversion of Control(IoC)
或者Dependency Injection(DI)
操作相关的所有类。 -
spring-context:
Spring
提供在基础IoC
功能上的扩展服务,此外还提供许多企业级服务的支持,如邮件服务、任务调度、JNDI
定位、EJB
集成、远程访问、缓存以及各种视图层框架的封装等。 -
spring-expression:定义了
Spring
的表达式语言。需要注意的是,在使用Spring
开发时,除了Spring
自带的JAR
包以外,还需要一个第三方JAR
包commons.logging
处理日志信息。
包 | 所含jar包 |
---|---|
spring-core | spring-jcl |
spring-beans | spring-core |
spring-context | spring-aop——>spring-beans |
spring-aop——>spring-core | |
spring-beans | |
spring-core | |
spring-expressions | |
spring-expressions | spring-core |
③SpringFramework特性
- 非侵入式:使用
Spring Framework
开发应用程序时,Spring
对应用程序本身的结果影响非常小,对领域模型可做到零污染,对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化,这就使得基于Spring Framework
开发应用程序时结构清晰、优雅简洁。 - 控制反转:
IOC
——Inversion Of Control
,反转资源获取方向,把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。 - 面向切面编程:
AOP
——Aspect Oriented Programming
,在不修改源代码的基础上增强代码功能。 - 容器:
Spring IOC
是一个容器,因为其包含并且管理组件对象的生命周期,组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大降低了使用门槛,大幅度提高了开发效率。 - 组件化:
Spring
实现了使用简单的组件配置组合成一个复杂的应用。在Spring
中可使用XML
和Java
注解组合这些对象,这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊地搭建超大型复杂应用系统。 - 声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
④简单的SpringFramework程序
要想使用SpringFramework
中的IOC
、AOP
功能,只需要使用core
部分的四个jar
包即可。
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>org.example</groupId>
<artifactId>SpringDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>19</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>6.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>6.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>6.0.4</version>
</dependency>
</dependencies>
</project>
Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="HelloWorld" class="com.example.HelloWorld">
<property name="message" value="Hello World!"/>
</bean>
</beans>
HelloWorld.java
package com.example;
public class HelloWorld {
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage(){
System.out.println("message : "+message);
}
}
MyTest.java
import com.example.HelloWorld;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
ApplicationContext context=new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld helloWorld=(HelloWorld) context.getBean("HelloWorld");
helloWorld.getMessage();
}
}
二、IOC模块
(一)三层架构思想
三层架构源于后端开发的一种分层思想,通常意义上的三层架构将整个业务应用划分为:界面层(User Interface Layer
)、业务逻辑层(Business Logic Layer
)、数据访问层(Data Access Layer
)。
分层的目的就是为了"高内聚,低耦合"的思想。
- 表示层:又称控制层(
Controller
),主要用于与前端进行交互,接受用户的请求,以及数据的返回,为客户端提供应用程序的访问。 - 业务逻辑层:又称服务层(
Service
),主要负责对数据层的操作。也就是说把一些数据层的操作进行组合。 - 数据访问层:又称持久化层(
Dao
、repository
、Mapper
),用于与数据库进行交互,执行SQL
语句。
在后端代码中,除了三层架构对应的类与包外,还包括但不限于以下部分:
- properties/yml文件:全局变量一般以资源文件的形式存放在
resource
目录下,文件名一般为application.properties
或者application.yml
。如,包含数据库连接信息的jdbc.properties
配置文件。 - config:项目设置类,用于指定当前
SpringBoot
项目的配置。 - common:基础信息类,规定
API
返回的基础信息,如规定返回格式为{操作时间,错误信息,数据对象}
。 - entity:实体类,对应数据库中的表,便于读取和写入,一般地,数据库中的一张表对应一个实体类,一个列名(字段)对应实体类的一个属性,一行对应该类的一个对象。
- dto:数据传输类,是为了迎合业务需求而创建的类,实体类用于与数据库交互,而
dto
用于作为参数在不同层级间传输,或者传输到前端, dto`中的属性可能由多个表列名(字段)对应。 - util:工具类,由程序员编写,用于数据处理。
- exception:异常处理类,由程序员编写,用于对异常进行处理。
(二)IOC思想概述
①基本概念
参考文章:https://blog.csdn.net/qq_36537546/article/details/90599137
IOC
(Inversion Of Control
,控制倒转),是spring
的核心,贯穿始终,所谓IOC
,对于spring
框架来说,就是由spring
来负责控制对象的生命周期和对象间的关系。所有的类都会在spring
容器中登记,告诉spring
你是个什么,你需要什么,然后spring
会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由spring
来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring
。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring
控制,所以这叫控制反转。
- 控制:谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用
Spring
后 , 对象是由Spring
来创建的。 - 反转:程序本身不再创建对象 , 而变成被动的接收对象。
②案例分析
以下案例参考:https://www.bilibili.com/video/BV1WE411d7Dv
1.不基于IOC思想的案例实现
Dao层
UserDao.java
接口
public interface UserDao {
public void getUser();
}
UserDao.java
实现类
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("获取用户数据");
}
}
Service层
UserService.java
接口
public interface UserService {
public void getUser();
}
UserService.java实现类
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
public void setUserDao(UserDaoImpl userDao){
this.userDao=userDao;
}
}
测试代码
当需要获取User
数据时,就需要先创建UserService
实现类对象,通过对象来调用UserDao
实现类的方法。
@Test
public void test(){
UserService service = new UserServiceImpl();
service.getUser();
}
分析:
以上代码的耦合度是很高的,假如想要实现在MySQL
中的查询User
功能,则需新建一个UserDao
实现类UserMySQLDao
,并修改UserServiceImpl
中对象的调用,每次变动都需要修改大量代码,当有大量需求时,会严重影响开发效率。
2.基于IOC思想的案例实现
创建UserDao
的新功能实现类UserDaoMySqlImpl
。
public class UserDaoMySqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("MySql获取用户数据");
}
}
在上述案例中创建Spring
配置文件beans.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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="UserDaoMySqlImpl " class="com.kuang.dao.impl.UserDaoMySqlImpl"/>
<bean id="ServiceImpl" class="com.kuang.service.impl.UserServiceImpl">
<!--将UserDaoMySqlImpl对象赋值给ServiceImpl对象的userDao属性-->
<property name="userDao" ref="UserDaoMySqlImpl "/>
</bean>
</beans>
测试代码
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//使用ClassPathXmlApplicationContext获取配置文件内容
UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
//获取UserServiceImpl对象
serviceImpl.getUser();
}
在IOC
思想下,程序的流程,从上文中传统的由程序本身创建对象并使用,变成了由Spring
创建对象,程序被动接收,程序员只需要实现新的功能模块,而不同模块之间的协同,完全由Spring
来控制。
总之,在IOC
思想下,组件不负责创建自己依赖的组件,而是让程序员来创建依赖组件,最后通过Spring
将依赖组件自动注入原组件。
(三)IOC容器与Bean
在前文中,我们提到过Spring
能完成组件自动注入的功能,从而实现控制反转的思想,我们将被管理的对象称之为Bean
(意为咖啡豆,咖啡的制作原料是咖啡豆,而java
编程语言的最小组成是对象,所以我们在Spring
中采用Bean
这个名称),而将Spring
中管理对象之间依赖关系的管理者,称之为IOC
容器。
前言:工厂模式
参考文章:https://blog.csdn.net/a745233700/article/details/120253639
软件设计模式有23种,按照功能和使用场景可以分为三大类:
- 创建型模式:处理的是对象的创建过程(通过各种方式创建对象,使对象创建和管理变得简单)
- 结构型模式:处理的是对象/类的组合。
- 行为型模式:处理类和对象间的交互方式和任务分布。
工厂设计模式(Factory Pattern
),是一种很常见的设计模式,属于创建型模式的,主要作用就是来创建对象,其将目的将创建对象的具体过程屏蔽隔离起来,从而达到更高的灵活性,工厂模式可以分为三类:
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
a.简单工厂模式
简单工厂模式的核心是定义一个创建对象的接口,将对象的创建和本身的业务逻辑分离,降低系统的耦合度,使得两个修改起来相对容易些,当以后实现改变时,只需要修改工厂类即可。
组成
- 工厂
Factory
:被应用程序调用以创建具体产品的对象,含有和具体业务逻辑有关的代码。 - 抽象产品
AbstractProduct
:是具体产品继承的父类或实现的接口,在Java
中一般有抽象类或者接口来实现。 - 具体产品
Product
:工厂角色所创建的对象就是此角色的实例。
手机公共接口:Phone.java
public interface Phone {
String getPhoneBrand();
}
华为手机实现类:HuaWeiPhone.java
public class HuaWeiPhone implements Phone {
@Override
public String getPhoneBrand(){
return "HuaWei";
}
}
苹果手机实现类:MacPhone.java
public class MacPhone implements Phone {
@Override
public String getPhoneBrand(){
return "Mac";
}
}
手机工厂类:PhoneFactory.java
public class PhoneFactory {
public Phone getPhone(String brand){
switch (brand){
case "HuaWei":
return new HuaWeiPhone();
case "Mac":
return new MacPhone();
default:
return null;
}
}
}
测试类:MyTest.java
import org.junit.Test;
public class MyTest {
@Test
public void test(){
//创建工程对象
PhoneFactory phoneFactory=new PhoneFactory();
//通过工厂来创建特定对象
Phone MyPhone=phoneFactory.getPhone("Mac");
System.out.println("手机的牌子是:"+MyPhone.getPhoneBrand());
}
}
优点
简单工厂模式提供专门的工厂类用于创建对象,实现了对象创建和使用的职责分离,客户端不需知道所创建的具体产品类的类名以及创建过程(否则,则可能需针对HuaWei
和Mac
各写一个实现类,且,客户端需要手动创建所需的手机对象。当有新的牌子出现时,客户端需要手动创建新的对象,而引入工厂之后,只需要将新牌子的实现类加入到工厂当中,客户端只需要提供参数就可获取到相应牌子的手机对象。),只需知道具体产品类所对应的参数即可,通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
缺点
不符合"开闭原则"(一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节,且,当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。),每次添加新产品就需要修改工厂类。在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展维护,并且工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
b.工厂方法模式
工厂方法模式将工厂抽象化,并定义一个创建对象的接口。每增加新产品,只需增加该产品以及对应的具体实现工厂类,由具体工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,这样工厂的设计就符合“开闭原则”了,扩展时不必去修改原来的代码。
组成
- 抽象工厂
AbstractFactory
: 工厂方法模式的核心,是具体工厂角色必须实现的接口或者必须继承的父类,在Java
中它由抽象类或者接口来实现。 - 具体工厂
Factory
:被应用程序调用以创建具体产品的对象,含有和具体业务逻辑有关的代码。 - 抽象产品
AbstractProduct
:是具体产品继承的父类或实现的接口,在Java
中一般有抽象类或者接口来实现。 - 具体产品
Product
:具体工厂角色所创建的对象就是此角色的实例。
手机的公共接口:Phone.java
public interface Phone {
String getPhoneBrand();
}
华为手机实现类:HuaWeiPhone.java
public class HuaWeiPhone implements Phone {
@Override
public String getPhoneBrand(){
return "HuaWei";
}
}
苹果手机实现类:MacPhone.java
public class MacPhone implements Phone {
@Override
public String getPhoneBrand(){
return "Mac";
}
}
手机工厂公共接口:PhoneFactory.java
public interface PhoneFactory {
Phone getPhone();
}
华为手机工厂实现类:HuaWeiPhoneFactory.java
public class HuaWeiPhoneFactory implements PhoneFactory{
@Override
public HuaWeiPhone getPhone(){
return new HuaWeiPhone();
}
}
苹果手机工厂实现类:MacPhoneFactory.java
public class MacPhoneFactory implements PhoneFactory{
@Override
public MacPhone getPhone(){
return new MacPhone();
}
}
测试类:MyTest.java
import org.junit.Test;
public class MyTest {
@Test
public void test(){
//1.获取所需工厂(工厂此时相当于是一个可调用的方法)
MacPhoneFactory macPhoneFactory=new MacPhoneFactory();
//2.通过工厂获取对象
MacPhone macPhone= macPhoneFactory.getPhone();
System.out.println("手机的牌子为:"+macPhone.getPhoneBrand());
}
}
优点
在使用时,用于只需知道产品对应的具体工厂,关注具体的创建过程,甚至不需要知道具体产品类的类名,当我们选择哪个具体工厂时,就已经决定了实际创建的产品是哪个了,且,符合“开闭原则”了,扩展时不必去修改原来的代码。
缺点
每增加一个产品都需要增加一个具体产品类和实现工厂类,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
c.抽象工厂模式
在工厂方法模式中,我们使用一个工厂创建一个产品,一个具体工厂对应一个具体产品,但有时候我们需要一个工厂能够提供多个产品对象,而不是单一的对象,这个时候我们就需要使用抽象工厂模式。
两个概念
- 产品等级结构:产品等级结构指的是产品的继承结构,例如一个空调抽象类,它有海尔空调、格力空调、美的空调等一系列的子类,那么这个空调抽象类和他的子类就构成了一个产品等级结构。
- 产品族:产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。比如,海尔工厂生产海尔空调、海尔冰箱,那么海尔空调则位于空调产品族中。
什么是抽象工厂模式?
抽象工厂模式主要用于创建相关对象的家族。当一个产品族中需要被设计在一起工作时,通过抽象工厂模式,能够保证客户端始终只使用同一个产品族中的对象;并且通过隔离具体类的生成,使得客户端不需要明确指定具体生成类;所有的具体工厂都实现了抽象工厂中定义的公共接口,因此只需要改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
但该模式的缺点在于添加新的行为时比较麻烦,如果需要添加一个新产品族对象时,需要更改接口及其下所有子类,这必然会带来很大的麻烦。
组成
- 抽象工厂
AbstractFactory
:定义了一个接口,这个接口包含了一组方法用来生产产品,所有的具体工厂都必须实现此接口。 - 抽象工厂
AbstractFactory
:定义了一个接口,这个接口包含了一组方法用来生产产品,所有的具体工厂都必须实现此接口。 - 抽象产品
AbstractProduct
:这是一个产品家族,每一个具体工厂都能够生产一整组产品。 - 具体产品
Product
:抽象产品的具体实现。
手机公共接口:Phone.java
public interface Phone {
String getPhoneBrand();
}
华为手机实现类:HuaWeiPhone.java
public class HuaWeiPhone implements Phone {
@Override
public String getPhoneBrand(){
return "HuaWei";
}
}
苹果手机实现类:MacPhone.java
public class MacPhone implements Phone {
@Override
public String getPhoneBrand(){
return "Mac";
}
}
手机壳公共接口:PhoneCase.java
public interface PhoneCase {
String getPhoneCase();
}
华为手机壳实现类:HuaWeiPhoneCase.java
public class HuaWeiPhoneCase implements PhoneCase{
@Override
public String getPhoneCase(){
return "华为手机壳";
}
}
苹果手机壳实现类:MacPhoneCase.java
public class MacPhoneCase implements PhoneCase{
@Override
public String getPhoneCase(){
return "Mac手机壳";
}
}
公共工厂接口:AbstractFactory.java
public interface AbstractFactory {
//获取手机壳对象
public PhoneCase getPhoneCase();
//获取手机对象
public Phone getPhone();
}
华为工厂实现类:HuaWeiFactory.java
public class HuaWeiFactory implements AbstractFactory{
@Override
public HuaWeiPhoneCase getPhoneCase(){
return new HuaWeiPhoneCase();
}
@Override
public HuaWeiPhone getPhone(){
return new HuaWeiPhone();
}
}
苹果工厂实现类:MacFactory.java
public class MacFactory implements AbstractFactory{
@Override
public MacPhone getPhone(){
return new MacPhone();
}
@Override
public MacPhoneCase getPhoneCase(){
return new MacPhoneCase();
}
}
测试类:MyTest.java
import org.junit.Test;
public class MyTest {
@Test
public void test(){
//1.创建工厂对象
MacFactory macFactory=new MacFactory();
HuaWeiFactory huaWeiFactory=new HuaWeiFactory();
//2.通过工厂对象可获取任意产品对象
MacPhone macPhone= macFactory.getPhone();
HuaWeiPhoneCase huaWeiPhoneCase=huaWeiFactory.getPhoneCase();
System.out.println("手机牌子是:"+macPhone.getPhoneBrand()+",手机壳牌子是:"+huaWeiPhoneCase.getPhoneCase());
}
}
工厂方法模式与抽象工厂模式的区别在于:
- 工厂方法只有一个抽象产品类和一个抽象工厂类,但可以派生出多个具体产品类和具体工厂类,每个具体工厂类只能创建一个具体产品类的实例。
- 抽象工厂模式拥有多个抽象产品类(产品族)和一个抽象工厂类,每个抽象产品类可以派生出多个具体产品类;抽象工厂类也可以派生出多个具体工厂类,同时每个具体工厂类可以创建多个具体产品类的实例。
d.BeanFactory与FactoryBean
工厂模式的实现是Spring
管理Bean
对象的基础,也是Spring
框架解耦和重要的扩展点,其中,BeanFactory与FactoryBean
是Spring
当中最重要的两个工厂模式的实现。
在Spring
当中,BeanFactory
是IOC
容器设计方式的顶层接口,也是一种工厂方法模式的典型案例,它的实现类DefaultListableBeanFactory
、SimpleJndiBeanFactory
、StaticListableBeanFactory
等等,通过getBean
重载方法,为我们创建不同的Bean
对象,这样开发过程中,我们可以将需要创建的对象通过配置等方式交给Bean
工厂去完成,使用时直接获取便可,从而大大降低耦合度,并增强可扩展性,提高了代码的可维护性,符合设计模式的原则。
FactoryBean
是Spring
当中另一个典型的工厂方法模式的实现,它和BeanFactory
一样都是用于创建对象,但是,BeanFactory
相当于是Spring
的一个大工厂,创建着Spring
框架运行过程中所需要的Bean
,而FactoryBean
是一个定制化工厂,其会存在于BeanFactory
创建对象的过程中,通过getObject()
方法来创建并返回对象,且,当有需要时,会通过FactoryBean
去自定制个性化的Bean
,从而Spring
框架提高扩展能力。
1. 三大核心接口
Spring
当中IOC
容器有三大核心接口,分别是BeanFactory
、ApplicationContext
、BeanDefinition
,前两者用于IOC
容器的实现,是IOC
核心容器接口,而BeanDefinition
接口则用于描述Bean
对象。
事实上,BeanFactory
是Spring
内部使用的接口,面向Spring
本身,不提供给开发人员使用,而ApplicationContext
是BeanFactory
的子接口,提供了更多高级功能。
1.1 BeanFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
Spring
框架给我们提供BeanFactory
功能,它是IOC
容器的最基本形式,给具体的IOC
容器的实现提供了规范,是最顶层的接口。
BeanFactory
可用来解决Bean
之间的依赖问题,达到了松耦合的效果,且,BeanFactory
会在Bean
的生命周期的各个阶段中对Bean
进行各种管理,并且Spring
将这些阶段通过各种接口暴露给我们,让我们可以对Bean
进行各种处理,我们只要让Bean
实现对应的接口,那么Spring
就会在Bean
的生命周期调用我们实现的接口来处理该Bean
。
功能:
IOC
容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象的依赖。BeanFactory
定义了Spring
管理Bean
的通用方法,是负责生产和管理Spring
中bean
的一个工厂,也是Spring
当中一个典型的工厂方法模式的案例。- 多种实现:如
DefaultListableBeanFactory
、XmlBeanFactory
、ApplicationContext
等,其中XmlBeanFactory
就是常用的一个,该实现将以XML
方式描述组成应用的对象及对象间的依赖关系。
方法声明 | 说明 |
---|---|
String FACTORY_BEAN_PREFIX = “&”; | 在获取Bean时,若在Bean的name前加上& ,则此时获取的是FactoryBean本身对象,否则获取的是由工厂生成的Bean的代理对象。 |
Object getBean(String name) throws BeansException; | 根据Bean的名称在IOC容器中获取Bean的实例对象。 |
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException; | 根据Bean的名字和Class类型来得到Bean实例,增加了类型安全验证机制。 |
Object getBean(String name, Object… args) throws BeansException; | 根据Bean的名称和参数获取Bean实例。 |
<T> T getBean(Class<T> requiredType) throws BeansException; | 根据Class类型来获取Bean实例。 |
<T> T getBean(Class<T> requiredType, Object… args) throws BeansException; | 根据Class类型和参数获取Bean实例。 |
boolean containsBean(String name); | 查看IOC容器中是否包含名称为name的Bean。 |
boolean isSingleton(String name) throws NoSuchBeanDefinitionException; | 判断名称为name的Bean是否是单例模式。 |
boolean isPrototype(String name) throws NoSuchBeanDefinitionException; | 判断名称为name的Bean是否是Prototype类型。 |
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; | 判断名称为name的Bean是否匹配typeToMatch泛型。 |
Class getType(String name) throws NoSuchBeanDefinitionException; | 获取名称为name的Bean实例的Class对象。 |
String[] getAliases(String name); | 获取名称为name的Bean的所有别名。 |
BeanFactory
有3个重要的子接口:ListableBeanFactory
、HierarchicalBeanFactory
和 AutowireCapableBeanFactory
。这三个类最终的默认实现类是DefaultListableBeanFactory
,它实现了所有的接口。
- ListableBeanFactory:表示这些
Bean
是可列表化的。 - HierarchicalBeanFactory:表示的是这些
Bean
是有继承关系的,也就是每个Bean
有可能有父Bean
。 - AutowireCapableBeanFactory:定义
Bean
的自动装配规则。
事实上,每个接口都有它使用的场合,它主要是为了区分在Spring
内部在操作过程中对象的传递和转化过程时,对对象的数据访问所做的限制。
1.2 ApplicationContext
参考文章:https://blog.csdn.net/qq_41907991/article/details/104890350
ApplicationContext
,称为Spring
上下文,是Spring
在BeanFactory
基础容器之上,提供的另一个IoC容器实现,也是一个核心IOC
容器,在ApplicationContext
中通过配置的方式实现了BeanFactory
的许多功能。
功能:是spring
继BeanFactory
之外的另一个核心IOC
容器接口,允许容器通过应用程序上下文环境创建、获取、管理Bean
(BeanFactory
),还有国际化(MessageSource
)、加载资源文件(ResourceLoader
)、事件发布(ApplicationEventPublisher
)、获取容器当前运行的环境(EnvironmentCapable
)等功能。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.lang.Nullable;
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
@Nullable
String getId();
String getApplicationName();
String getDisplayName();
long getStartupDate();
@Nullable
ApplicationContext getParent();
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
1.2.1 国际化
1.2.1.1 什么是国际化?
应用程序运行时,可根据客户端操作系統的国家/地区、语言的不同而显示不同的界面,比如客户端OS
的语言环境为大陆的简体中文,程序就显示为简体中文,客户端OS
的语言环境为美国——英语,程序就显示美式英语。
对于有国际化要求的应用系统,我们不能简单地采用硬编码的方式编写代码,而是为每种语言提供一套相应的资源文件,并以规范化命名的方式保存在特定的目录中,由系统自动根据客户端语言选择适合的资源文件。
1.2.1.2 JAVA
国际化
国际化信息也称为本地化信息,一般需要两个条件才可以确定一个特定类型的本地化信息,它们分别是“语言类型”和“国家/地区的类型”。如中文本地化信息既有中国大陆地区的中文,又有中国台湾、中国香港地区的中文,还有新加坡地区的中文。国际化代码:
ar_sa 阿拉伯语(沙特阿拉伯)
ar_iq 阿拉伯语(伊拉克)
eu 巴斯克语
bg 保加利亚语
zh_tw 中文(中国台湾)
zh_cn 中文(中华人民共和国)
zh_hk 中文(中国香港特别行政区)
zh_sg 中文(新加坡)
hr 克罗地亚语
en 英语
en_us 英语(美国)
en_gb 英语(英国)
en_au 英语(澳大利亚)
en_ca 英语(加拿大)
JAVA
通过java.util.Locale
类表示一个本地化对象,它允许通过语言参数和国家/地区参数创建一个确定的本地化对象:
Locale locale=new Locale("zh","cn");//中文,中国
Locale locale2=new Locale("en","us");//英文,美国
Locale locale3=new Locale("zh");//中文--不指定国家
Locale locale4=Locale.CHINA;//中文,中国
Locale locale5=Locale.CHINESE;//中文
在持有一个Locale
对象后,我们需要将同一个文字或者数字根据不同的地区/语言格式化成不同的表现形式,所以这里我们还需要一个格式化的操作,JDK
给我们提供以下几个常见的类用于国际化格式化:
a.NumberFormat:处理数字、百分数、货币等,如:
public static void main(String[] args) {
// 1.通过语言跟地区确定一个Locale对象
// 中国,中文
Locale chinaLocale = new Locale("zh", "cn");
// 美国,英文
Locale usLocale = new Locale("en", "us");
// 获取货币格式化对象
NumberFormat chinaCurrencyFormat = NumberFormat.getCurrencyInstance(chinaLocale);
NumberFormat usLocaleCurrencyFormat = NumberFormat.getCurrencyInstance(usLocale);
// 中文,中国下的货币表现形式
String chinaCurrency = chinaCurrencyFormat.format(99.9); // 输出 ¥99.90
// 美国,英文下的货币表现形式
String usCurrency = usLocaleCurrencyFormat.format(99.9); // 输出 $99.90
System.out.println(chinaCurrency);
System.out.println(usCurrency);
}
b.DateFormat:通过DateFormat.getDateInstance(int style,Locale locale)
方法按本地化的方式对日期进行格式化操作。该方法第一个入参为时间样式,第二个入参为本地化对象
public static void main(String[] args) {
// 1.通过语言跟地区确定一个Locale对象
// 中国,中文
Locale chinaLocale = new Locale("zh", "cn");
// 美国,英文
Locale usLocale = new Locale("en", "us");
DateFormat chinaDateFormat = DateFormat.getDateInstance(DateFormat.YEAR_FIELD,chinaLocale);
DateFormat usDateFormat = DateFormat.getDateInstance(DateFormat.YEAR_FIELD,usLocale);
System.out.println(chinaDateFormat.format(new Date())); // 输出 2020年1月15日
System.out.println(usDateFormat.format(new Date())); // 输出 January 15, 2020
}
c.MessageFormat:在NumberFormat
和DateFormat
的基础上提供了强大的占位符字符串的格式化功能,它支持时间、货币、数字以及对象属性的格式化操作。
public static void main(String[] args) {
// 1.通过语言跟地区确定一个Locale对象
// 中国,中文
Locale chinaLocale = new Locale("zh", "cn");
String str1 = "{0},你好!你于{1}在农业银行存入{2}。";
MessageFormat messageFormat = new MessageFormat(str1,chinaLocale);
Object[] o = {"小红", new Date(), 99.99};
System.out.println(messageFormat.format(o));
// 输出:小红,你好!你于20-1-15 下午4:05在农业银行存入99.99。
}
0
,1
,2
代表的是占位符的索引,从0
开始计数,date
,number
为格式化的类型,而long
,currency
为格式化样式。
FormatType
:格式化类型,取值如下:number
:调用NumberFormat
进行格式化。date
:调用DateFormat
进行格式化。-
time
:调用DateFormat
进行格式化。 choice
:调用ChoiceFormat
进行格式化
FormatStyle
:设置使用的格式化样式。
1.3 BeanDefinition
2.Bean
2.1 普通Bean
2.2 FactoryBean
FactoryBean
是一种特殊的Bean
,它在IOC
容器的基础上给Bean
的实现加上了一个简单工厂模式和装饰模式,是一个可以创建或修饰其他对象的工厂 bean
,可创建除自身以外其他对象。
一般情况下实例化一个Bean
对象时,Spring
通过反射机制利用<bean>
的class
属性指定实现类实例化Bean
,在某些情况下,实例化Bean
过程比较复杂,如果按照传统的方式,则需要在<bean>
中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。
而实现了FactoryBean
接口后,可在getObject()
方法中灵活配置,来定制实例化Bean
的逻辑,使得从BeanFactory
及其实现类的getBean()
方法中获取到的Bean
对象,实际上是FactoryBean
的getObject()
方法创建并返回的Bean
对象,而不是FactoryBean
本身。
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
//从Factory中获取Bean
@Nullable
T getObject() throws Exception;
// 从BeanFactory中获取类型
@Nullable
Class<?> getObjectType();
//判断是否是单例
default boolean isSingleton() {
return true;
}
}
方法声明 | 说明 |
---|---|
String OBJECT_TYPE_ATTRIBUTE = “factoryBeanObjectType”; | 声明实现接口后属于FactoryBean类型。 |
T getObject() throws Exception; | 返回Bean对象。如果一个类实现了FactoryBean 接口,则该类就变成了工厂,此时根据类名所获取的实际上是该工厂调用getObject() 返回的对象,而非工厂自身的对象,若想要获取工厂自身的对象,则需要加上& 前缀。 |
Class<?> getObjectType(); | 返回的Bean对象的类型。 |
default boolean isSingleton() | 判断是否是单例。 |
案例:
汽车类:Car.java
public class Car {
private String Brand;
private Double Price;
public Car() {
}
public Car(String brand, Double price) {
Brand = brand;
Price = price;
}
}
汽车工厂类:CarFactory.java
import org.springframework.beans.factory.FactoryBean;
public class CarFactory implements FactoryBean<Car> {
@Override
public Car getObject() throws Exception {
return new Car("宝马",32351.5);
}
@Override
public Class<?> getObjectType() {
return Car.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="CarFactory" class="CarFactory"/>
</beans>
测试类:MyTest.java
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("UserBeans.xml");
//获取Bean对象
System.out.println(IOC.getBean("CarFactory"));
//获取工厂对象
System.out.println(IOC.getBean("&CarFactory"));
}
}
使用断点调试:
可见,此时BeanFactory.getBean()
方法最终返回的是xxxFactoryBean.getObject()
方法中创建并返回的Bean
对象,而非工厂本身。
BeanFactory
与FactoryBean
的对比
BeanFactory
是Spring
中IOC
容器的核心公共接口,其提供方法支持外部程序对所管理的bean
进行访问,在程序启动时根据传入的参数产生各种类型的bean
,并添加到IOC容器
(实现BeanFactory
接口的类) 的ingletonObject
属性中。常见的DefaultListableBeanFactory
、XmlBeanFactory
、ApplicationContext
都是实现了BeanFactory
的IOC
容器。
FactoryBean
是一个bean
,但它又不仅仅是个bean
。它是一个可以 创建 或 修饰 其他对象的工厂bean
,这跟设计模式中的工厂模式或者装饰设模式很相似,它可以创建除自身以外的其他对象,其最大的应用场景是用在AOP
中,AOP
代理对象通过java
反射机制在运行时创建代理对象,并根据业务需求将相应的方法编织到代理对象的目标方法中,Spring
中的ProxyFactoryBean
就是这个功能。
2.3 Bean的作用域
Bean
的作用域是指Bean
在Spring
整个框架中的某种行为模式,是创建的Bean
对象相对于其他Bean
对象的请求可见范围。比如singleton
单例作用域,就表示Bean
在整个Spring
中只有一份,它是全局共享的,当其他人修改了这个值之后,另一个人读取到的就是被修改的值。
五种作用域:
范围 | 说明 |
---|---|
singleton (单例作用域) | 将每个Spring IoC容器的单个 bean 定义范围限定为单个对象实例。 |
prototype :原型作用域(多例作用域) | 将单个 bean 定义的作用域限定为任意数量的对象实例。 |
request (请求作用域) | 每个 HTTP 请求都有一个自己的 bean 实例,仅在含有Web应用的Spring的上下文ApplicationContext中有效。 |
session (会话作用域) | 每个session会话都有一个自己的 bean 实例,仅在含有Web应用的Spring的上下文ApplicationContext中有效。 |
application (上下文作用域) | 每个ServletContext都有一个自己的 bean 实例,仅在含有Web应用的Spring的上下文ApplicationContext中有效。 |
websocket (HTTP WebSocket 作用域) | 每个WebSocket都有一个自己的 bean 实例,仅在含有Web应用的Spring的上下文ApplicationContext中有效。 |
其中,singleton
和prototype
属于基本作用域,后三者应用在Web应用当中,在SpringMVC
当中再进行介绍。
2.3.1 singleton
单例作用域,当将Bean
的scope
设置为singleton
,Spring
的IOC
容器将仅生成和管理一个Bean
实例,且当你是用id
或name
获取Bean
实例时,IOC
容器会返回共享的Bean
实例。
<bean id="Bean名称" class="全类名" scope="singleton"/>
例:
配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="MacPhone" class="MacPhone" scope="singleton"/>
</beans>
测试类:MyTest.java
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("UserBeans.xml");
//获取单例对象
MacPhone macPhone1=(MacPhone) IOC.getBean("MacPhone");
MacPhone macPhone2=(MacPhone) IOC.getBean("MacPhone");
System.out.println("macPhone1的引用为:"+macPhone1);
System.out.println("macPhone2的引用为:"+macPhone2);
}
}
2.3.2 prototype
原型作用域(多例作用域),指每次向Spring
容器请求获取Bean
都返回一个全新的Bean
。
<bean id="Bean名称" class="全类名" scope="prototype"/>
例:
配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="MacPhone" class="MacPhone" scope="prototype"/>
</beans>
测试类:MyTest.java
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("UserBeans.xml");
//获取单例对象
MacPhone macPhone1=(MacPhone) IOC.getBean("MacPhone");
MacPhone macPhone2=(MacPhone) IOC.getBean("MacPhone");
System.out.println("macPhone1的引用为:"+macPhone1);
System.out.println("macPhone2的引用为:"+macPhone2);
}
}
2.4 Bean的生命周期
由前文可知,Bean
就是Spring
中IOC
容器实例化、组装和管理的对象。Bean
对象和普通的Java
对象类似,但是Spring
不再自己去new
对象了,而是由IOC
容器去帮助我们实例化对象并且管理它,Bean
的生命周期完全由容器控制。
对于普通的JAVA
对象,其生命周期为:
- 实例化。
- 不再使用时由垃圾回收机制回收。
对于Bean
对象,其基本生命周期为:
- 实例化(
Instantiation
)。 - 属性赋值(
Populate
)。 - 初始化(
Initialization
)。 - 销毁(
Destruction
)。
注:前文提到过,BeanFactory
管理的Bean
是在使用到Bean
的时候才会实例化Bean
,ApplicantContext
管理的Bean
在容器初始化的时候就回完成Bean
实例化,前者是提供给Spring
内部使用,后者提供给开发人员使用,故此处探讨的是后者。
2.4.1 基本生命周期
在基本生命周期中,只探讨:实例化Bean
对象->Bean
对象属性赋值 -> 初始化Bean
对象-> 销毁Bean
对象,这四个步骤,其中,实例化和属性赋值都是Spring
帮助我们做的,能够自己实现的有初始化和销毁两个生命周期阶段。
a.实例化Bean对象
Spring
会根据XML
配置和@Autowire
注解注入来获取Bean
的定义信息(BeanDefinition
),得到一份需要实例化的Bean
列表,然后才能开始实例化。事实上,Spring
提供了BeanDefinitionReader
接口,用于获取Bean
的定义信息,它有AbstractBeanDefinitionReader
、GroovyBeanDefinitionReader
、PropertiesBeanDedefintionReader
、XmlBeanDefinitionReader
四个实现类用于获取信息,其中,XmlBeanDefinitionReader
就是负责机械XML
配置文件当中的Bean
,并执行相关的方法,将XML
中的Bean
配置解析成BeanDefinition
对象。
在得到包含Bean
定义信息的BeanDefinition
对象后,并不能直接反射创建对象,因为此时还有很多信息是不全的,比如:附属Bean
的配置信息、占位符没替换、自定义的逻辑没处理等等,所以,在实例化Bean
之前还有很多需要处理,这就是BeanFactoryPostProcessor
接口的功能(事实上,在实例化Bean
之前,完成了各种复杂配置、自定义配置的处理等,放在下一节写)。
当客户向容器请求一个尚未初始化的Bean
时,容器就会调用doCreateBean()
方法进行实例化,实际上就是通过反射的方式创建出一个Bean
对象,且,过程非常复杂,且整个过程中Spring
留下了大量可扩展点。
AbstractAutowireCapableBeanFactory类:AbstractBeanFactory
的子类,实现了AutowireCapableBeanFactory
接口,用于提供Bean
创建(构造函数创建)、属性装配(population
),注入(autowiring
)的功能。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
...
// 忽略了无关代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
...
if (instanceWrapper == null) {
//实例化阶段
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//属性赋值阶段
populateBean(beanName, mbd, instanceWrapper);
//初始化阶段
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
}
...
}
进入org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance
方法,一直进入,找到org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate
方法,就能看出,Spring
采用反射机制实例化Bean
对象:
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
// Don't override the class with CGLIB if no overrides.
if (!bd.hasMethodOverrides()) {
Constructor<?> constructorToUse;
synchronized (bd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
final Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
if (System.getSecurityManager() != null) {
constructorToUse = AccessController.doPrivileged(
(PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
}
else {
constructorToUse = clazz.getDeclaredConstructor();
}
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
catch (Throwable ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
}
return BeanUtils.instantiateClass(constructorToUse);
}
else {
// Must generate CGLIB subclass.
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
b.Bean对象属性赋值
// 忽略了无关代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// 实例化阶段!
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 属性赋值阶段!
populateBean(beanName, mbd, instanceWrapper);
// 初始化阶段!
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
}
Bean
实例创建出来后,接着就是给这个Bean
对象进行属性填充(populateBean
方法),包括自定义属性赋值和容器对象属性赋值(invokeAwareMethods
方法,在initializeBean
方法中调用,就会检查 Aware
相关接口的实现和配置,执行了这些逻辑之后,才调用了invokeInitMethods
执行初始化逻辑),由于第一步的时候已经转换成BeanDefinition
对象了,直接取值即可。
private void invokeAwareMethods(String beanName, Object bean) {
if (bean instanceof Aware) {
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
ClassLoader bcl = getBeanClassLoader();
if (bcl != null) {
((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
}
}
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
}
}
}
c.初始化Bean对象
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
...
// 忽略了无关代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
...
if (instanceWrapper == null) {
//实例化阶段
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//属性赋值阶段
populateBean(beanName, mbd, instanceWrapper);
//初始化阶段
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
}
...
}
初始化时机:对象实例化完成,且,一些属性也完成赋值,那么就会调用Bean
的初始化方法。对于单实例Bean
来说,在Spring
容器创建完成后,Spring
容器会自动调用Bean
的初始化方法initializeBean
;对于多实例bean
来说,在每次获取Bean
对象的时候,调用bean
的初始化方法initializeBean
。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
...
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
this.invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
}
try {
this.invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable var6) {
throw new BeanCreationException(mbd != null ? mbd.getResourceDescription() : null, beanName, var6.getMessage(), var6);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
...
}
其中,调用了invokeAwareMethods()
方法,而在真正的初始化方法invokeInitMethods)
之前、之后,各有一个前置和后置处理器,留给开发者拓展使用。
对于invokeAwareMethods()
方法:
protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
((InitializingBean) bean).afterPropertiesSet();
return null;
}, getAccessControlContext());
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
((InitializingBean) bean).afterPropertiesSet();
}
}
if (mbd != null && bean.getClass() != NullBean.class) {
String initMethodName = mbd.getInitMethodName();
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
Spring
为Bean
提供了两种初始化的方式,实现InitializingBean
接口(也就是要实现该接口中的afterPropertiesSet
方法),或者在配置文件或@Bean
注解中通过init-method
来指定,两种方式可以同时使用。这两种方式可以同时使用,同时使用先调用afterPropertiesSet
方法,后执行init-method
指定的方法。
原理分析:
Spring
中提供了一个InitializingBean
接口,对应生命周期的初始化阶段,该接口为Bean
提供了属性初始化后的处理方法,它只包括afterPropertiesSet
方法,凡是继承该接口的类,在Bean
的属性赋值后都会执行该方法。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.beans.factory;
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
案例:
配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="carBean" class="Car"/>
</beans>
汽车类:Car.java
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class Car implements InitializingBean {
public Car(){
System.out.println("执行Car构造器");
}
@Override
public void afterPropertiesSet(){
System.out.println("执行初始化方法");
}
}
测试类:MyTest.java
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("UserBeans.xml");
//获取Car对象
System.out.println(IOC.getBean("carBean"));
}
}
(可见,是先实例化再初始化)
d.销毁Bean对象
在Spring
当中,对于单实例Bean
对象,会在容器关闭时销毁,而多实例Bean
对象,容器不会管理Bean
,只是负责创建。
2.4.2 扩展生命周期
流程图:
https://blog.csdn.net/kykangyuky/article/details/123114227
3.IOC工作流程
bean创建过程大体分为五个步骤:加载→解析→实例化→初始化→获取
1.Tomcat启动,IoC容器初始化,加载扫描包下的Class文件。
2.BeanDefinitionReader读取配置文件中的bean定义信息加载解析为BeanDefinition。
3.通过反射将BeanDefinition实例化为Bean对象。
4.然后进行初始化Bean对象中的属性信息和方法。
(1) 设置Bean的属性
(2) 检查Aware相关接口并设置相关依赖
(3) 检查BeanPostProcessor接口postProcessBeforeInitialization方法进行前置处理
(4) 检查Bean在Spring配置文件中配置的init-method属性并自动调用其配置的初始化方法
(5) 检查BeanPostProcessor接口postProcessAfterInitialization方法并进行后置处理
5.最后获取到完整的Bean对象。
(四)Bean的管理:基于XML
①配置文件
Spring
配置文件是指导Spring
工厂进行Bean
生产、依赖关系注入的图纸。例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 注解包扫描 -->
<context:component-scan base-package="com.test.fx.service"></context:component-scan>
<!-- 引入DB配置文件 -->
<context:property-placeholder location="classpath:oracleDriver.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${name}"></property>
<property name="password" value="${password}"></property>
</bean>
<!-- 创建SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="mapperLocations" value="classpath:com/baizhi/fx/dao/*DaoImpl.xml"></property>
</bean>
<!-- 扫描Mapper文件并生成dao对象 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<property name="basePackage" value="com.test.fx.dao"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务管理器注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
1.XML结构体
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" <!-- 默认bean命名空间 -->
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <!-- xml约束命名空间定义 -->
xmlns:aop="http://www.springframework.org/schema/aop"<!-- AOP命名空间的scheme约束 -->
xmlns:context="http://www.springframework.org/schema/context" <!-- context命名空间的scheme约束 -->
xsi:schemaLocation=" <!-- 上面各个scheme的location信息(网络地址) -->
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd ">
</beans>
2. bean标签
常见的Bean
标签:
<!-- 一个最基本的bean标签 -->
<bean id="bean名称" class="bean的类全名(类全限定路径)" scope="作用域">
属性 | 说明 |
---|---|
id | bean的名称,一般为类名的驼峰命名。 |
class | Bean的完全限定名,就是类的全路径名。 |
scope | bean的作用域,可选值有singleton(单例)、prototype(多例,又叫原型)、request、session、global session。 |
lazy-init | 表明了Bean是否为延迟加载,false为立即加载,表示在spring启动时,立刻进行实例化。为true时将不会在ApplicationContext启动时提前被实例化,而是第一次向容器通过getBean索取bean时实例化才会加载。 |
init-method | 在Bean实例被创建后、初始化时指定需要执行的方法。 |
destroy-method | 用于容器在销毁bean之前调用的方法。 |
abstract | 是否为抽象Bean,Spring对抽象Bean不产生实例,主要用于继承。 |
parent | 父Bean的名称,会继承父Bean的属性。 |
factory-bean | 指定创建该bean时使用的工厂类名。 |
depends-on | 依赖对象,这个Bean在初始化时依赖的对象,这个对象会在这个Bean初始化之前创建。 |
dependency-check | 依赖检查它用来确保Bean组件通过JavaBean描述的所以依赖关系都得到满足。在与自动装配功能一起使用时,它特别有用。可选值:none(不进行依赖检查),objects(只做对象间依赖的检查),simple(只做原始类型和String类型依赖的检查),all(对所有类型的依赖进行检查。它包括了前面的objects和simple) |
autowire | 自动装配,它定义了Bean的自动装载方式。 可选值:no(不使用自动装配功能),byName(通过Bean的属性名实现自动装配),byType(通过Bean的类型实现自动装配),constructor(构造函数的参数的自动组装),autodetect通过bean类的自省机制(introspection)来决定是使用constructor还是byType方式进行自动装配。如果发现默认的构造器,那么将使用byType方式 |
2.1 lazy-init属性
如果一个设置了立即加载的Bean1
,引用了一个延时加载的Bean2
,那么Bean1
在容器启动时被实例化,而Bean2
由于被Bean1
引用,所以也被实例化,这种情况也符合延时加载的Bean
在第一次调用时才被实例化的规则。
事实上,可在容器层次<beans>
来控制延时初始化:
<beans default-lazy-init="true"><!-- no beans will be eagerly pre-instantiated... --></beans>
如果一个Bean
的scope
属性为scope="pototype"
时,即使设置了lazy-init=“false”
,容器启动时不实例化Bean
,而是调用getBean()
方法实例化的
2.2 init-method属性
此属性用于替代的是InitializingBean
接口。InitializingBean
接口为bean提供了初始化方法的方式,它只有afterPropertiesSet
方法,凡是继承该接口的类,在初始化Bean
的时候会执行该方法。此接口在前文中已详细写过,故不再赘述。
2.2 destroy-method属性
此属性用于替代的是DisposableBean
接口中的destroy()
方法,但当Bean
的作用域scope = "prototype"
时,该属性将失效,因为在Spring
中,如果一个Bean
为多例的,那么该Bean
被创建后,Spring
将不再去管理了。
2.3 abstract和parent属性
abstract
属性可将Bean
声明为抽象Bean
,而parent
属性则用于指定继承的Bean
对象。
例,多个用户性别、年龄相同,就可在创建Bean
时将相同的属性抽取出来:
<beans>
<bean id="abstractBean" abstract="true">
<property name="Sex" value="Male"/>
<property name="Age" value="18"/>
</bean>
<!--其他Bean所对应的实体类中也有相对应的属性-->
<bean id="mikeBean" class="Mike" parent="abstractBean">
<property name="Id" value="001"/>
<property name="Hobby" value="Swimming"/>
</bean>
<bean id="johnBean" class="John" parent="abstractBean">
<property name="Id" value="002"/>
<property name="Hobby" value="Basketball"/>
</bean>
</beans>
2.4 factory-bean和factory-method属性
这两个属性可以同时出现,也可以只指定factory-method
。
a.同时使用factory-bean
和factory-method
汽车工厂类:CarFactory.java
public class CarFactory {
public Car createCar(){
Car car = new Car();
return car;
}
}
配置文件:Beans.xml
<!--创建汽车工厂Bean-->
<bean id="carFactory" class="com.factory.demo.CarFactory"/>
<!--指定汽车工厂Bean和创建car1的Bean对象所用方法CarFactory()-->
<bean id="car1" factory-bean="carFactory" factory-method="createCar">
</bean>
注意,此时不能指定静态方法,因为static
方法可直接通过类调用而无需实例化。
b.只使用factory-method
汽车工厂类:CarFactory.java
public class CarFactory {
// 静态方法
public static Car createStaticCar(){
Car car = new Car();
return car;
}
}
配置文件:Beans.xml
<!-- 指定该bean的class类型为该bean的工厂类 -->
<bean id="car2" class="com.bean.demo.CarFactory" factory-method="createStaticCar"></bean>
这里该Bean
的class
类型为产生该Bean
的工厂类,factory-method
指向该工厂类的静态方法。
c.factory-method
方法有参数
如果有参数,那么我们可以通过constructor-arg
进行配置,原理跟普通的构造方法注入是一样的。
汽车工厂类:CarFactory.java
public class CarFactory {
// 静态方法
public static Car createStaticCar(String brand){
Car car = new Car();
car.setBrand(brand);
return car;
}
}
配置文件:Beans.xml
<bean id="car2" class="com.bean.demo.CarFactory" factory-method="createStaticCar">
<constructor-arg name="brand" value="GTR" />
</bean>
2.5 ref属性
当Bean
的属性是其他Bean
时,就可使用ref
属性,它有local
和bean
两个基本属性。
- local:如果一个
Bean
与被参考引用的Bean
在同一个xml
文件中而且被引用参考的Bean
是用id
来命名的,那么就可以使用ref
的local
属性。 - bean:允许指向的
Bean
可以在同一个xml
,也可以不在同一个xml
中。Bean
属性的值可以与被参考引用的Bean
的id
属性相同,也可以与被参考引用的bean的属性不相同。
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/db_ssm" />
<property name="username" value="hc" />
<property name="password" value="123456" />
</bean>
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource">
<ref local="dataSource"/>
<!--
<ref bean="dataSource">
-->
</property>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
2.6 tx标签
<!-- 配置事务管理器 -->
<bean id="事务管理bean id" class="类的全限定路径">
<property name="数据源属性名称" ref="引用的数据源实例名称" />
</bean>
<!-- 配置一个事务通知 -->
<tx:advice id="事务通知名称" transaction-manager="事务管理器实例名称">
<tx:attributes>
<tx:method name="方法名" read-only="是否只读" propagation="事务类型"/>
<!-- 其他所有方法,以默认事务运行 -->
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<!-- 配置事务切入点 -->
<aop:config>
<aop:pointcut id="事务切入点id" expression="事务切入点表达式" />
<aop:advisor advice-ref="事务通知名称" ponitcut-ref="事务切入点id" />
</aop:config>
例:
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${name}"></property>
<property name="password" value="${password}"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务通知 -->
<!-- 使用<tx:advice>元素声明事务通知,需要指定id属性,以便AOP把通知和切入点关联起来 -->
<!-- 还需要指定transaction-manager属性,其值为Bean配置文件中事务管理器的id属性值 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 在<tx:advice>元素下声明<tx:attributes>元素,用于指定事务属性 -->
<!-- 在<tx:attributes>元素下可以使用多个<tx:method>元素指定不同方法的不同事务属性 -->
<tx:attributes>
<!-- 根据方法名指定事务的属性 -->
<tx:method name="BookShopXmlService" propagation="REQUIRED"/>
<!--方法名以get开头的事务属性 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务切入点,以及把事务切入点和事务属性关联起来 -->
<!-- 在<aop:config>元素下,使用<aop:advisor>元素声明一个增强器,将事务通知和切入点关联起来 -->
<!-- 使用 advice-ref属性指定事务通知,用pointcut-ref属性指定切入点 -->
<aop:config>
<aop:pointcut expression="execution(* com.sqp.spring.service.*.*(..))"
id="txPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
2.7 context标签
a.<context:annotaion-config>
注解装配
注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在Spring
配置文件中配置<context:annotation-config/>
元素。
<!-- 使用@Autowired注解,必须事先在Spring容器中声明AutowiredAnnotationBeanPostProcessor的Bean: -->
<bean class="org.springframework.beans.factory.annotation.
AutowiredAnnotationBeanPostProcessor "/>
<!-- 使用 @Required注解,就必须声明RequiredAnnotationBeanPostProcessor的Bean -->
<bean class="org.springframework.beans.factory.annotation.
RequiredAnnotationBeanPostProcessor"/>
<!-- 类似地,使用@Resource、@PostConstruct、@PreDestroy等注解就必须声明 CommonAnnotationBeanPostProcessor;使用@PersistenceContext注解,就必须声明 PersistenceAnnotationBeanPostProcessor的Bean。 -->
<!-- 这样的声明未免太不优雅,而Spring为我们提供了一种极为方便注册这些BeanPostProcessor的方式,即使用<context:annotation- config/>隐式地向 Spring容器注册AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor以及PersistenceAnnotationBeanPostProcessor这4个BeanPostProcessor。 -->
<context:annotation-config/>
b.<context:component-scan>
组件扫描
组件扫描可以在类路径底下寻找标注了@Component
、@Service
、@Controller
、@Repository
注解的类,并把这些类纳入进Spring
容器中管理。
<!-- base-package 指定了要扫描的包路径 -->
<context:component-scan base-package="org.example.Controller">
<!--<context:exclude-filter type="" expression=""/>
context:exclude-filter,排除标签:用于指出当前扫描过程中要排除掉组件.
type:排除时所依据的原则,常用选项:annotation(注解类型)、assignable(类类型).
当type为annotation时,若要排除控制层,则expression="import org.springframework.stereotype.Controller;",即指出注解的全类名.
当type为assignable时,则expression需要指定目标类的全类名,如,"org.example.Controller.UserController".
-->
<!--<context:include-filter type="" expression=""/>
注意,在使用之前需要在父级标签中指定参数:<context:component-scan base-package="org.example.Controller" use-default-filters="false">,
因为use-default-filters默认为true,含义为默认在扫描所有标签的基础上...,对于exclude而言即为在扫描所有标签的情况下排除...,自然无影响,而对于include而言,
就是使得其失效(因为default-filters中是所有,其包含了include-filter).
context:include-filter,指定标签:用于指出当前扫描过程中要扫描组件的范围.
-->
这个配置隐式注册了多个对注解进行解析处理的处理器,包括<context:annotation-config/>
该配置注册的处理器,也就是说写了<context:component-scan base-package="" />
配置,就不用写<context:annotation-config/>
配置了。另外此标签还有一个属性是use-default-filter
,默认为true
这就意味着会扫描指定包下的全部的标有@Component
,@Controller
,@Service
等等的类,并注册成Bean
。
<context:component-scan>
标签还有两个子标签,用于在包内自由选择扫描的组件:
<context:exclude-filter> 指定的不扫描
<context:include-filter> 指定的扫描
<!-- 例如,我们只想扫描指定包下面的Controller -->
<!-- 此处如果省略了use-default属性,不仅会把指定包下的Controller扫描出来,还会把带有@Service等注解的其他组件扫描出来 -->
<context:component-scan base-package="com.sparta.trans" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
c.<context:property-placeholder>
配置文件处理
用来处理用一个proerties
文件里面的内容来替换Spring
配置文件中的${}
内容,例如:
<!-- 引入DB配置文件 -->
<context:property-placeholder location="classpath:oracleDriver.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${name}"></property>
<property name="password" value="${password}"></property>
</bean>
常用属性:
属性 | 说明 |
---|---|
location | 表示引入配置文件的位置,多个之间用逗号分隔。 |
file-encoding | 指定文件编码,如GBK、UTF-8等。 |
ignore-resource-not-found | 如果属性文件找不到,是否忽略,默认false,即不忽略,找不到将抛出异常 |
ignore-unresolvable | 是否忽略解析不到的属性,如果不忽略,找不到将抛出异常。 |
properties-ref | 配置类的引用,如果在xml中声明了properties bean,那么将引用这个bean的id。 |
local-override | 是否本地覆盖模式,即如果true,那么properties-ref的属性将覆盖location加载的属性。 |
system-properties-mode | 系统属性模式:ENVIRONMENT(默认,将使用Spring 3.1提供的PropertySourcesPlaceholderConfigurer,其他情况使用Spring 3.1之前的PropertyPlaceholderConfigurer如果是本地覆盖模式:那么查找顺序是:properties-ref、location、environment,否则正好反过来;)、OVERRIDE(PropertyPlaceholderConfigurer使用,因为在spring 3.1之前版本是没有Enviroment的,所以OVERRIDE是spring 3.1之前版本的Environment如果是本地覆盖模式:那么查找顺序是:properties-ref、location、System.getProperty(),System.getenv(),否则正好反过来;)、NEVER(只查找properties-ref、location;)。 |
②获取Bean对象的常用方式
在基于XML
配置方式中,获取Bean
对象的常用方式有三种:按Class
对象来获取、按id
来获取、通过Class
对象和id
获取。
1.根据Class对象获取
配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="personBean" class="Pojo.Person">
<property name="name" value="张三"/>
<property name="age" value="18"/>
<property name="sex" value="男"/>
</bean>
</beans>
测试类:MyTest.java
import Pojo.Person;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
//获取Person对象
System.out.println(IOC.getBean(Person.class));
}
}
通过类型来获取Bean
,这种方式只适合获取单例的对象,如果有多个,则会报 NoUniqueBeanDefinitionException
。这是因为当有多个相同类型Bean
对象的时候,只通过类型获取就会有多个匹配,我们的程序不知道你到底需要获取那个对象,最终就会报错。
2.根据id获取
配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="personBean" class="Pojo.Person">
<property name="name" value="张三"/>
<property name="age" value="18"/>
<property name="sex" value="男"/>
</bean>
</beans>
测试类:MyTest.java
import Pojo.Person;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
//获取Person对象
Person person=(Person) IOC.getBean("personBean");
System.out.println(person);
}
}
此时获取到的是Object
对象(可见前文中源码解析部分),这种获取方式不会对对象有单例的那种限制,因为我们是通过id
来获取的Bean
。
3.根据Class对象和id获取
配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="personBean" class="Pojo.Person">
<property name="name" value="张三"/>
<property name="age" value="18"/>
<property name="sex" value="男"/>
</bean>
</beans>
测试类:MyTest.java
import Pojo.Person;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
//获取Person对象
Person person=(Person) IOC.getBean("personBean", Person.class);
System.out.println(person);
}
}
③依赖注入
依赖注入(DI
,Dependency Injection
),组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。事实上,IOC
和DI
是同一个概念的不容角度描述,在Spring
框架当中,前者重点强调,由Spring
来负责控制对象的生命周期和对象间的关系,后者则重点强调,在系统运行中,动态的向某个对象提供它所需要的其他对象。
- 依赖:
Bean
对象的创建依赖于IOC
容器,指Bean
对象创建时所依赖的资源。 - 注入:指
Bean
对象创建时资源的注入由容器来完成。
1.构造器注入
Person实体类:Person.java
package Pojo;
public class Person {
private String Name;
private Integer Age;
private String Sex;
private Person Father;
public Person() {
}
public Person(String name, Integer age, String sex, Person father) {
Name = name;
Age = age;
Sex = sex;
Father = father;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public Integer getAge() {
return Age;
}
public void setAge(Integer age) {
Age = age;
}
public String getSex() {
return Sex;
}
public void setSex(String sex) {
Sex = sex;
}
public Person getFather() {
return Father;
}
public void setFather(Person father) {
Father = father;
}
@Override
public String toString() {
return "Person{" +
"Name='" + Name + '\'' +
", Age=" + Age +
", Sex='" + Sex + '\'' +
", Father=" + Father +
'}';
}
}
属性列表:
private String Name;
private Integer Age;
private String Sex;
private Person Father;
1.1 无参构造注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="personBean" class="Pojo.Person" scope="prototype"/>
</beans>
1.2 有参构造注入
1.2.1 使用index指定
使用index
指定的是构造器的参数索引,从0
开始。
<?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="personBean" class="Pojo.Person" scope="prototype">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="40"/>
<constructor-arg index="2" value="男"/>
<constructor-arg index="3" ref="personFatherBean"/>
</bean>
<!--注册father属性对应的Bean-->
<bean id="personFatherBean" class="Pojo.Person" scope="prototype">
<property name="name" value="张三"/>
<property name="sex" value="男"/>
<property name="age" value="40"/>
</bean>
</beans>
1.2.2 使用name指定
使用name
指定的是构造器的参数名。
<?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="personBean" class="Pojo.Person" scope="prototype">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="40"/>
<constructor-arg name="sex" value="男"/>
<constructor-arg name="father" ref="personFatherBean"/>
</bean>
<!--注册father属性对应的Bean-->
<bean id="personFatherBean" class="Pojo.Person" scope="prototype">
<property name="name" value="张三"/>
<property name="sex" value="男"/>
<property name="age" value="40"/>
</bean>
</beans>
1.2.3 使用type指定
<?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="personBean" class="Pojo.Person" scope="prototype">
<constructor-arg type="java.lang.String" value="张三"/>
<constructor-arg type="java.lang.Integer" value="18"/>
<constructor-arg type="java.lang.String" value="男"/>
<constructor-arg type="Pojo.Person" ref="personFatherBean"/>
</bean>
<!--注册father属性对应的Bean-->
<bean id="personFatherBean" class="Pojo.Person" scope="prototype">
<property name="name" value="张三"/>
<property name="sex" value="男"/>
<property name="age" value="40"/>
</bean>
</beans>
注:建议同时使用type
和name
进行指定,因为在赋值时都是value="值"
的形式,这样便于区分。
2.set注入
在上述的构造器方法构造当中,并不能处理null
值的问题,而set
注入本质是先空参构造,再调用实体类当中的set
方法,故可解决此问题。
<?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="personBean" class="Pojo.Person" scope="prototype">
<constructor-arg type="java.lang.String" value="张三"/>
<constructor-arg type="java.lang.Integer" value="18"/>
<constructor-arg type="java.lang.String" value="男"/>
<constructor-arg type="Pojo.Person" ref="personFatherBean"/>
</bean>
<bean id="personFatherBean" class="Pojo.Person" scope="prototype">
<property name="name" value="张三"/>
<property name="sex" value="男"/>
<property name="age" value="40"/>
<!--
若使用构造器构造来处理null对象:
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="40"/>
<constructor-arg name="sex" value="男"/>
<constructor-arg name="father" value=""/>
则会报错:Cannot convert value of type 'java.lang.String' to required type 'Pojo.Person',即,实体类类型不能由String对象转换而来(基本数据类型及其包装类可转换).
而若去掉<constructor-arg name="father" value=""/>,即:
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="40"/>
<constructor-arg name="sex" value="男"/>
则会报错,找不到对应的构造方法,但是property并不会出现这些错误,因为它的本质是先无参构造,再使用内置的set方法.
-->
</bean>
</beans>
事实上,还可以写为:
<!--基本数据类型及其包装类可由空字符串直接转换-->
<property name="name" value=""/>
<!--实体类对象的写法-->
<property name="father">
<null/>
</property>
3.级联注入
前文中对于实体类属性,使用的是ref
的方式进行注入,在MyBatis
中,直接通过"对象.属性"来为引用类型的属性进行赋值,但Spring
中并不可行,因为此时该对象就是null
,自然不能为其赋值,此时,可以定义内部Bean
来为实体类属性赋值。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="Student" class="org.example.pojo.Student">
<property name="SId" value="1001"/>
<property name="SName" value="张三"/>
<property name="clazz">
<bean id="clazzInner" class="org.example.pojo.Clazz">
<property name="clazzId" value="403"/>
<property name="clazzName" value="四年三班"/>
</bean>
<!--内部bean只能在该标签内部使用,程序当中亦无法通过IOC容器获取-->
</property>
</bean>
</beans>
4.集合注入
4.1数组注入
Person.java
实体类参数列表
package Pojo;
import java.lang.reflect.Array;
public class Person {
private String Name;
private Integer Age;
private String Sex;
private Array Hobbies;
...
}
配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="personBean" class="Pojo.Person">
<property name="name" value="张三"/>
<property name="age" value="18"/>
<property name="sex" value="男"/>
<property name="hobbies">
<array>
<value>游泳</value>
<value>跑步</value>
<value>唱歌</value>
</array>
</property>
</bean>
</beans>
4.2 列表注入
Person.java
实体类参数列表
package Pojo;
import java.lang.reflect.Array;
public class Person {
private String Name;
private Integer Age;
private String Sex;
private List<String>Hobbies;
...
}
配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="personBean" class="Pojo.Person">
<property name="name" value="张三"/>
<property name="age" value="18"/>
<property name="sex" value="男"/>
<property name="hobbies">
<list>
<value>游泳</value>
<value>跑步</value>
<value>唱歌</value>
</list>
</property>
</bean>
</beans>
4.3 集合注入
Person.java
实体类参数列表
package Pojo;
import java.util.Map;
public class Person {
private String Name;
private Integer Age;
private String Sex;
private Map<String,String>BankCard;
...
}
配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="personBean" class="Pojo.Person">
<property name="name" value="张三"/>
<property name="age" value="18"/>
<property name="sex" value="男"/>
<property name="bankCard">
<map>
<entry key="中国农业银行" value="123456654321"/>
<entry key="中国建设银行" value="789654456789"/>
</map>
</property>
</bean>
</beans>
5. 配置文件注入
在(四)2.7
当中写过配置文件的引入方式,在这里只举例,关于其他属性,还请看上文。
需要先注册解析器,使用标签:
<context:property-placeholder location="配置文件路径"/>
location
表示引入配置文件的路径,多个之间用逗号分隔。
例,在Spring
当中引入数据库连接参数配置文件(事实上,数据源本身就是一个对象,也可通过Spring来进行管理):
a.引入依赖:pom.xml
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<!--数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
b.数据库连接参数配置文件:jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/dataBase?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123456
在数据源Bean
中使用${键名}
的方式可获取属性文件中的数据库连接参数。
c.配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--注册解析器-->
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
d.测试类:MyTest.java
import com.alibaba.druid.pool.DruidDataSource;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
//获取数据库源对象
DruidDataSource dataSource=IOC.getBean(DruidDataSource.class);
System.out.println(dataSource);
}
}
6. 属性值包含特殊符号
小于号等其他符号在XML
文档中有特殊含义,故而不能作为字符串正常传入,有两种解决方案:
<!--解决方案一:使用XML实体代替,如,"<"代表"<",">"代表">"-->
<property name="expression" value="a <: b"/>
<!--解决方案二:使用CDATA节代替-->
<!--CDATA代表当前是纯文本数据,XML解析器看到CDATA就知道这是纯文本从而不会当做XML标签或属性来解析-->
<value><![CDATA[a<b]]></value>
<!--注意,CDATA不能写在属性中,只能写在标签中-->
7.自动装配
自动装配就是指Spring
容器在不使用<constructor-arg>
和<property>
标签的情况下,可以自动装配(autowire
)相互协作的Bean
之间的关联关系,将一个Bean
注入其他Bean
的Property
中,即,相当于扩展了ref
的功能。
自动装配需要配置<bean>
元素的autowire
属性,其有五个值:
值 | 说明 |
---|---|
no | 默认值,表示不使用自动装配,此时Bean依赖需要通过ref进行配置。 |
byName | 根据属性名自动装配,此时将检查容器并根据名字查找与属性完全一致的Bean,并将其与属性自动装配,。 |
byType | 若容器中存在一个与指定属性类型相同的Bean,则将与该属性自动装配,若存在多个该类型的Bean,则将抛出异常,并指出不能使用byType的方式进行自动装配,若没有找到相匹配的Bean,则什么都不发生,属性也不会被设置,这一情况可通过设置dependency-check="objects"让Spring抛出异常。 |
constructor | 类似于 byType,根据构造方法参数的数据类型,进行 byType 模式的自动装配。(类中构造函数的参数必须在配置文件中有相同的类型,否则将抛出异常) |
注:自动装配只能对注册了Bean
的实体类类型起作用,而简单的数据类型,如,in
、boolean
、String
等,不起作用。
事实上,可在头文件中,一次性将IOC
容器的所有Bean
统一设置成自动装配:
<beans xmlns="http://www.springframework.org/schema/beans"
....
default-autowire="byName">
7.1 byName
将属性的属性名作为Bean
的id
来寻找相应的Bean
(IOC
中不允许Bean
的id
重复)。
实体类:Person.java
package Pojo;
public class Person {
private String Name;
private Integer Age;
private String Sex;
private Person Father;
...
}
配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="personBean" class="Pojo.Person" autowire="byName">
<property name="name" value="张三"/>
<property name="sex" value="男"/>
<property name="age" value="18"/>
</bean>
<!--不能大写成Father,原因看下面的过程-->
<bean id="father" class="Pojo.Person">
<property name="name" value="张三父亲"/>
<property name="sex" value="男"/>
<property name="age" value="40"/>
</bean>
</beans>
测试类:MyTest.java
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
//获取Bean对象
System.out.println(IOC.getBean("personBean"));
}
}
实际过程:
- 查找
personBean
其类中所有的set
方法名,找到setFather
后,获得将set
去掉并且首字母小写的字符串,即father
。 - 去
Spring
容器中寻找是否有此字符串名称id
的对象。 - 如果有,就取出注入;如果没有,就报空指针异常。
7.2 byType
使用autowire byType
首先需要保证同一类型的对象,在Spring
容器中唯一。如果不唯一,会报不唯一的异常。
NoUniqueBeanDefinitionException
实体类:Person.java
package Pojo;
public class Person {
private String Name;
private Integer Age;
private String Sex;
private Phone phone;
...
}
实体类:Phone.java
package Pojo;
public class Phone {
String Brand;
...
}
配置文件:Beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="personBean" class="Pojo.Person" autowire="byType">
<property name="name" value="张三"/>
<property name="sex" value="男"/>
<property name="age" value="18"/>
</bean>
<bean id="phoneBean" class="Pojo.Phone">
<property name="brand" value="Mac"/>
</bean>
</beans>
测试类:MyTest.java
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
//获取Bean对象
System.out.println(IOC.getBean("personBean"));
}
}
(五)Bean的管理:基于注解
Spring
本身就是一个轻代码而重配置的框架,配置比较繁重,影响开发效率,所以就在Spring
中加入了注解,使用注解代替原来的xml
配置文件,这样就可以简化配置,提升开发效率。
常用注解:
注解 | 说明 |
---|---|
@Component | 使用在类上用于实例化Bean。 |
@Controller | 使用在web层的类上实例化Bean。 |
@Service | 使用在service层类上用于实例化Bean。 |
@Repository | 使用在dao层类上用于实例化Bean。 |
@Autowired | 根据属性类型进行自动装配。 |
@Qualifier | 根据Bean名称进行自动装配。 |
@Resource | 相当于@Autowired+@Qualifier |
@Value | 用于字面量的自动装配。 |
@Bean | 标注将该方法的返回值存储到Spring容器。 |
@Scope | 标注Bean的作用域。 |
@PostConstruct | 使用在方法上标注该方法是Bean的初始化方法。 |
@PreDestroy | 使用在方法上标注该方法是Bean的销毁方法。 |
@Configuration | 用于指定当前类是一个Spring 配置类,当创建容器时会从该类上加载注解。 |
@ComponentScan | 用于指定Spring 在初始化容器时要扫描的包。 |
@PropertySource | 用于加载.properties 文件中的配置。 |
@Import | 用于导入其他配置类。 |
需要注意的是,要想使用注解功能,就需要在配置文件当中开启注解扫描:
<!--扫描组件,用于Spring扫描注解-->
<context:component-scan base-package="org.example.Controller">
<!--<context:exclude-filter type="" expression=""/>
context:exclude-filter,排除标签:用于指出当前扫描过程中要排除掉组件.
type:排除时所依据的原则,常用选项:annotation(注解类型)、assignable(类类型).
当type为annotation时,若要排除控制层,则expression="import org.springframework.stereotype.Controller;",即指出注解的全类名.
当type为assignable时,则expression需要指定目标类的全类名,如,"org.example.Controller.UserController".
-->
<!--<context:include-filter type="" expression=""/>
注意,在使用之前需要在父级标签中指定参数:<context:component-scan base-package="org.example.Controller" use-default-filters="false">,
因为use-default-filters默认为true,含义为默认在扫描所有标签的基础上...,对于exclude而言即为在扫描所有标签的情况下排除...,自然无影响,而对于include而言,
就是使得其失效(因为default-filters中是所有,其包含了include-filter).
context:include-filter,指定标签:用于指出当前扫描过程中要扫描组件的范围.
-->
①基于注解注册Bean
1.@Component
@Component
用于定义Spring
管理的Bean
,被标注后的类会被自动识别为Bean
并交给Spring
进行管理。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
@Component
只有一个属性,相当于<bean>
标签中id
的作用。
配置文件:Beans.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 http://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="Pojo"/>
</beans>
实体类:Cat.java
package Pojo;
import org.springframework.stereotype.Component;
@Component("cat")
public class Cat {
public void say(){
System.out.println("I'm a cat!");
}
}
测试类:MyTest.java
import Pojo.Cat;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
//获取Bean对象
Cat cat=(Cat) IOC.getBean("cat");
cat.say();
}
}
2.@Repository、@Service与@Controller
在Java
的web
开发中,提供3
个@Component
注解衍生注解(功能与@component
一样,对于Spring
而言三者并无区别,主要是便于开发人员区分)分别是:
注解 | 说明 |
---|---|
@Controller | 用于标注控制层,使在web层的类注册为Bean。 |
@Service | 用于标注服务层,使在service层的类注册为Bean。 |
@Repository | 用于标注数据访问层(DAO层),使用在dao层的类注册为Bean。 |
@Controller
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
@Service
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
@Repository
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
可见,本质就是@Component
标签的别称,故不再赘述。
②基于注解的自动装配
为实现基于注解的自动装配,Spring
提供了以下四个注解:
注解 | 说明 |
---|---|
@Autowired | 根据属性类型进行自动装配。 |
@Qualifier | 根据Bean名称进行自动装配。 |
@Resource | 相当于@Autowired+@Qualifier。 |
@Value | 用于字面量的自动装配。 |
1.@Autowired
@Autowired
用于根据属性类型进行自动装配,它等同于前文基于XML
配置当中的default-autowire=byType
,可以标注在属性上、方法上、注解上和构造器上,在一个类上标注@Service
或者@Controller
或@Component
或@Repository
注解之后,Spring
的组件扫描会发现并将其初始化为Spring
应用上下文中的Bean
,当需要使用这个Bean
时,可加上Autowired
注解,之后会根据无参构造函数来创建相应的Bean
对象,以玩成自动装配。
例:
手机类:Phone.java
package Pojo;
import org.springframework.stereotype.Component;
@Component("phoneBean")
public class Phone {
public Phone() {
System.out.println("Phone无参构造");
}
public void sayPhone(){
System.out.println("I have a Phone!");
}
}
实体类:Person.java
package Pojo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Person {
private String Name;
private Integer Age;
private String Sex;
@Autowired
private Phone phone;
...
}
测试类:MyTest.java
import Pojo.Person;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
}
}
可见,虽然并未获取Person
对象,但还是被Spring
通过无参构造完成了初始化。
@Autowired
只有一个required
属性:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
@Autowired
注解的required
参数默认是true
,表示开启自动装配,有些时候我们不想使用自动装配功能,可以将该参数设置成false
,此时即为null
值。
1.1属性
@Autowired
标注在属性上后,Spring
会自动寻找已注册的、与该属性类型相同的类进行注入。
配置文件:Beans.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 http://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="Pojo"/>
</beans>
实体类:Phone.java
package Pojo;
import org.springframework.stereotype.Component;
@Component("phone")
public class Phone {
public void sayPhone(){
System.out.println("I have a Phone!");
}
}
实体类:Person.java
package Pojo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("person")
public class Person {
private String Name;
private Integer Age;
private String Sex;
@Autowired
private Phone phone;
...
}
测试类:MyTest.java
import Pojo.Person;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
//获取Bean对象
Person person=(Person) IOC.getBean("person");
person.getPhone().sayPhone();
}
}
1.2 方法
可以在JavaBean
中的方法(一般写在setter
方法上)上使用@Autowired
注解,当Spring
遇到一个使用@Autowired
的setter
方法上,它会在方法中执行byType
自动装配。
配置文件:Beans.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 http://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="Pojo"/>
</beans>
文本编辑器实体类:TextEditor.java
package Pojo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("textEditorBean")
public class TextEditor {
//文本编辑器类
//包含一个拼写检查程序对象
private SpellChecker spellChecker;
@Autowired
public void setSpellChecker(SpellChecker spellChecker) {
this.spellChecker = spellChecker;
System.out.println("调用了setSpellChecker方法");
}
public SpellChecker getSpellChecker() {
return spellChecker;
}
public void checkSpelling(){
spellChecker.checkSpelling();
}
}
拼写检查程序实体类:SpellChecker.java
package Pojo;
import org.springframework.stereotype.Component;
@Component("spellCheckerBean")
public class SpellChecker {
//拼写检查程序
public SpellChecker() {
System.out.println("调用了SpellChecker无参构造");
}
public void checkSpelling(){
System.out.println("检查拼写...");
}
}
测试类:MyTest.java
import Pojo.TextEditor;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
//获取文本编辑器对象,此处已经调用了SpellChecker无参构造和setSpellChecker方法,完成了自动装配.
TextEditor editor=(TextEditor) IOC.getBean("textEditorBean");
editor.checkSpelling();
}
}
1.3 构造器
可以在JavaBean
中的构造器方法上使用@Autowired
注解,Spring
会自动寻找已注册的、与该参数类型相同的类对象进行注入。
配置文件:Beans.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 http://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="Pojo"/>
</beans>
文本编辑器实体类:TextEditor.java
package Pojo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("textEditorBean")
public class TextEditor {
//文本编辑器类
//包含一个拼写检查程序对象
private SpellChecker spellChecker;
public TextEditor() {
System.out.println("执行TextEditor无参构造");
}
@Autowired
public TextEditor(SpellChecker spellChecker) {
this.spellChecker = spellChecker;
System.out.println("执行TextEditor有参构造");
}
public void checkSpelling(){
spellChecker.checkSpelling();
}
}
拼写检查程序实体类:SpellChecker.java
package Pojo;
import org.springframework.stereotype.Component;
@Component("spellCheckerBean")
public class SpellChecker {
//拼写检查程序
public SpellChecker() {
System.out.println("调用了SpellChecker无参构造");
}
public void checkSpelling(){
System.out.println("检查拼写...");
}
}
测试类:MyTest.java
import Pojo.TextEditor;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
//获取文本编辑器对象,此处已经调用了SpellChecker无参构造和setSpellChecker方法,完成了自动装配.
TextEditor editor=(TextEditor) IOC.getBean("textEditorBean");
editor.checkSpelling();
}
}
可见,此时在初始化Bean
对象时执行的是有参构造函数,若将@Autowired
注解去掉,则会执行无参构造函数,且,会在checkSpelling
方法中报出空指针异常。
1.4 集合
@Autowired
注解可用在集合属性上,前提是集合泛型的子类对象/实现类对象都需要已经交给ioc
管理。之后ioc
容器会初始化将这些被管理的实现类对象装入集合,注入到当前类中,注入map
对象,key
是注入类的name
,value
是注入类对象,按照那么可以在map
中获取到目标对象进行业务操作,注入set/list
使用时候就需要按照字节码对象进行判断使用,这样有利于统一管理某个业务实现对象。
手机接口:Phone.java
package Pojo;
public interface Phone {
void getBrand();
}
华为手机实现类:HuaweiPhone.java
package Pojo;
import org.springframework.stereotype.Component;
@Component("huaweiPhone")
public class HuaweiPhone implements Phone{
@Override
public void getBrand() {
System.out.println("Huawei!");
}
}
苹果手机实现类:ApplePhone.java
package Pojo;
import org.springframework.stereotype.Component;
@Component("applePhone")
public class ApplePhone implements Phone{
@Override
public void getBrand() {
System.out.println("Apple!");
}
}
手机商店类:PhoneStore.java
package Pojo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Component("phoneStore")
public class PhoneStore {
@Autowired
private List<Phone>phoneList;
@Autowired
private Set<Phone>phoneSet;
@Autowired
private Map<String,Phone>phoneMap;
public PhoneStore() {
}
public List<Phone> getPhoneList() {
return phoneList;
}
public void setPhoneList(List<Phone> phoneList) {
this.phoneList = phoneList;
}
public Set<Phone> getPhoneSet() {
return phoneSet;
}
public void setPhoneSet(Set<Phone> phoneSet) {
this.phoneSet = phoneSet;
}
public Map<String, Phone> getPhoneMap() {
return phoneMap;
}
public void setPhoneMap(Map<String, Phone> phoneMap) {
this.phoneMap = phoneMap;
}
}
测试类:MyTest.java
import Pojo.PhoneStore;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
PhoneStore phoneStore=(PhoneStore) IOC.getBean("phoneStore");
System.out.println(phoneStore.getPhoneList());
System.out.println(phoneStore.getPhoneSet());
System.out.println(phoneStore.getPhoneMap());
}
}
2.@Qualifier
@Autowired
默认是根据类型进行注入的,因此如果有多个类型一样的Bean
候选者,则需要限定其中一个候选者,否则将抛出异常。
而@Qualifier
注解一般作为Autowired
的修饰使用,使得能根据当前注入到IOC
中的Bean
的唯一标识(id
)来获取Bean
并进行注入,这种组合使用使得注入更加精确、更加多元(可注册多个同类型不同id
的Bean
供选择),且不用担心是否能够匹配成功的问题。
@Qualifier
只有一个value
参数,用于指定Bean
的id
值。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
2.1 类
@Qualifier
标注在类上时,可用于指定该类所注册Bean
的唯一标识id
,但需要提前先将该类注册为Bean
(相当于修改<bean>
的id
值)。
配置文件:Beans.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 http://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="Pojo"/>
</beans>
实体类:TextEditor.java
package Pojo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Qualifier("textEditorBean")
public class TextEditor {
//文本编辑器类
//包含一个拼写检查程序对象
private SpellChecker spellChecker;
public TextEditor() {
System.out.println("执行TextEditor无参构造");
}
@Autowired
public TextEditor(SpellChecker spellChecker) {
this.spellChecker = spellChecker;
System.out.println("执行TextEditor有参构造");
}
public void checkSpelling(){
spellChecker.checkSpelling();
}
}
此时,在使用到该Bean
的地方就可以直接根据唯一标识(id
)从IOC
容器中获取。
2.2 属性
当使用在属性上的时候,需要和@Autowired
配合使用,否则,无法实现自动装配,之后,就可以直接通过Bean
的唯一标识(id
)进行自动装配了。
配置文件:Beans.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 http://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="Pojo"/>
</beans>
实体类:TextEditor.java
package Pojo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Qualifier("textEditorBean")
public class TextEditor {
//文本编辑器类
//包含一个拼写检查程序对象
@Autowired
@Qualifier("spellCheckerBean")
private SpellChecker spellChecker;
public TextEditor() {
System.out.println("执行TextEditor无参构造");
}
public TextEditor(SpellChecker spellChecker) {
this.spellChecker = spellChecker;
System.out.println("执行TextEditor有参构造");
}
public void checkSpelling(){
spellChecker.checkSpelling();
}
}
2.3 集合
当使用在集合上时,@Qualifier
起到了一个筛选的作用,只有加了@Qualifier
注解的、且符合继承关系的Bean
才会被收集注入到本集合。
//@Qualifier在方法上时,遇见自动装配中有@Qualifier不会被过滤
@Qualifier
@Bean
public User user1() {
return new User("zhangsan",18) ;
}
@Bean
public User user2() {
return new User("lisi",17);
}
//只有user1会被自动注入到users,user2因为没有@Qualifier注解而导致被过滤掉了
@Resource
@Qualifier
private List<User> users = Collections.emptyList() ;
3.@Resource
@Resource
注解是Java
自己的注解,在使用@Resource
注解之前需要先导入依赖:
<dependencies>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
@Resource
和@Autowired
都可以用来装配Bean
,都可以用于字段或setter
方法,@Autowired
默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null
值,可以设置它的required
属性为false
。
@Resource
默认按名称装配,当找不到与名称匹配的bean时才按照类型进行装配,相当于是@Autowired
+@Qualifier
。名称可以通过name
属性指定,如果没有指定name
属性,当注解写在字段上时,默认取字段名,当注解写在setter
方法上时,默认取属性名进行装配。
其相当于@Autowired
+@Qualifier
,故而此处不再举例子,看上文@Qualifier
即可。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package javax.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Resources.class)
public @interface Resource {
String name() default "";
String lookup() default "";
Class<?> type() default Object.class;
AuthenticationType authenticationType() default Resource.AuthenticationType.CONTAINER;
boolean shareable() default true;
String mappedName() default "";
String description() default "";
public static enum AuthenticationType {
CONTAINER,
APPLICATION;
private AuthenticationType() {
}
}
}
@Resource
有两个属性是比较重要的,分别是name
和type
;Spring
将@Resource
注解的name
属性解析为bean
的名字,而type
属性则解析为bean
的类型。所以如果使用name
属性,则使用byName
的自动注入策略,而使用type
属性时则使用byType
自动注入策略。默认按name
进行注入。
4.@Value
@Value
用于注入属性,包括普通属性、外部文件、表达式计算结果、URL
等。
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
String value();
}
只有value
属性,用于指定注入量的值或表达式。
4.1普通属性
@Value
可以注入一些字段的普通属性,并且会自动进行类型转换。
@Repository
public class ConnectionPool {
@Value("jdbc:mysql://localhost:3306/test")
private String url;
@Value("com.mysql.jdbc.Driver")
private String driveName;
@Value("Scott")
private String userName;
@Value("10")
private int no;
}
4.2配置文件
@Value
可以注入外部配置文件中的内容,需要先使用@PropertySource
注解引入外部配置文件,且,@PropertySource
注解有个value
属性,字符串数组类型,可以用来指定多个配置文件的路径,再使用${}
来获取属性值:
package Pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component("ConnectionPool")
@PropertySource({"classpath:jdbc.properties"})
public class ConnectionPool {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.maxConnection}")
private Integer maxConnection;
}
可在@Value
注解当中设置默认值,如:
@Value("${jdbc.username:root}")
private String username;
表示当配置文件中不含有jdbc.username
时,将username
属性赋值为root
。
4.3其他属性
在注入表达式、URL、操作系统属性与Bean
对象属性时需要使用#{}
来进行注入。
SpEl
表达式注入
SpEl(Spring Expression Language )
,是Spring
的表达式语言。
@Value("#{T(java.lang.Runtime).getRuntime().availableProcessors() * 2}")
private int maxCons;
URL
注入
@Value("https://www.baidu.com/")
private URL homePage;
注入后会被识别为URL
地址。
Bean
对象属性
//OtherBean.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//其他bean,自定义名称为 myBeans
@Component("myBeans")
public class OtherBean {
@Value("OtherBean的NAME属性")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//ValueController.java
import cn.wideth.PdaAndIpadApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest()
@ContextConfiguration(classes = PdaAndIpadApplication.class)
public class ValueController {
@Value("#{myBeans.name}")
private String fromAnotherBean;
@Test
public void getValue(){
System.out.println(fromAnotherBean);
}
}
③Bean的配置
Spring
提供了@Configuration
、@Bean
、@Profile
、@Scope
、@Lazy
、@DependsOn
、@Primary
标签用于配置Bean
。
1.@Configuration
@Configuration
用于声明一个类为配置类,用于取代bean.xml
配置文件注册bean
对象,并声明这是一个配置类,相当于<Beans>
标签。
@Configuration
注解常常一起搭配使用的注解有:
@Bean
:等价于Spring
中的bean
标签用于注册bean
对象,内部有一些初始化、销毁的属性。@Scope
:用于声明该bean
的作用域。@Lazy
:用于标记该bean
是否开启懒加载。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
boolean enforceUniqueMethods() default true;
}
2.@Bean
@Bean
是用于方法上的注解,作用与<bean>
类似,用于声明本方法会产生一个Bean
对象,然后这个Bean
对象交给Spring
管理。产生这个Bean
对象的方法Spring
只会调用一次,随后这个Spring
将会将这个Bean
对象放在自己的IOC
容器中,其常常与@Component
、@Configuration
配合使用,起到取代配置文件的作用。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
boolean autowireCandidate() default true;
String initMethod() default "";
String destroyMethod() default "(inferred)";
}
从ElementType.METHOD
, ElementType.ANNOTATION_TYPE
可见,@Bean
可用于方法、注解上。
属性 | 说明 |
---|---|
value | name属性的别名,是默认属性。 |
name | 此Bean的一个或多个别名,当未指定时,默认用方法名作为Bean的名称。 |
initMethod | bean的初始化之前的执行方法。 |
destroyMethod | 在bean销毁时会自动执行该方法. |
例:
//MyBean.java
public class MyBean {
public MyBean(){
System.out.println("MyBean Initializing");
}
public void init(){
System.out.println("Bean 初始化方法被调用");
}
public void destroy(){
System.out.println("Bean 销毁方法被调用");
}
}
//AppConfig.java
@Configuration
public class AppConfig {
@Bean(name="myBean",initMethod = "init", destroyMethod = "destroy")
public MyBean myBean(){
return new MyBean();
}
}
//测试类:MyTest.java
import Pojo.MyBean;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
MyBean myBean=(MyBean) IOC.getBean("myBean");
}
}
例:
//MyBean.java
package Pojo;
public class MyBean {
public MyBean(){
System.out.println("Bean 构造方法被调用");
}
public void init(){
System.out.println("Bean 初始化方法被调用");
}
public void destroy(){
System.out.println("Bean 销毁方法被调用");
}
}
//AppConfig.java
package Pojo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean(name="myBean",initMethod = "init", destroyMethod = "destroy")
public MyBean myBean(){
return new MyBean();
}
}
//MyTest.java
import Pojo.MyBean;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
MyBean myBean=(MyBean) IOC.getBean("myBean");
myBean.destroy();
}
}
3.@Scope
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
@AliasFor("scopeName")
String value() default "";
@AliasFor("value")
String scopeName() default "";
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
@Scope
用于配置Bean
实例的作用域对象,有如下几种作用域:
- singleton(默认):单实例模式。
- prototype:多实例模式。
- request:同一次请求模式,指每一次
HTTP
请求都会产生一个新的Bean
,同时该Bean
仅在当前HTTP
请求中有效。 - session:同一会话模式,每一次
HTTP
请求都会产生一个新的Bean
,同时该Bean
仅在当前HTTP session
内有效。
//MyBean.java
package Pojo;
public class MyBean {
public MyBean(){
System.out.println("Bean 构造方法被调用");
}
public void init(){
System.out.println("Bean 初始化方法被调用");
}
public void destroy(){
System.out.println("Bean 销毁方法被调用");
}
}
//AppConfig.java
package Pojo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class AppConfig {
@Bean(name="myBean",initMethod = "init", destroyMethod = "destroy")
@Scope(value="prototype")
public MyBean myBean(){
return new MyBean();
}
}
//MyTest.java
import Pojo.MyBean;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//获取IOC容器
ApplicationContext IOC=new ClassPathXmlApplicationContext("Beans.xml");
MyBean myBean1=(MyBean) IOC.getBean("myBean");
MyBean myBean2=(MyBean) IOC.getBean("myBean");
System.out.println(myBean1);
System.out.println(myBean2);
}
}
4.@Lazy
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
boolean value() default true;
}
Spring
在应用程序上下文启动时去创建所有的单例Bean
对象,而@Lazy
注解可以延迟加载Bean
对象,即在使用时才去初始化。
故而,@Lazy注解
, 一是可以减少Spring
的IOC
容器启动时的加载时间, 二是可以解决Bean
的循环依赖问题
三、AOP模块
前言
在面向切面编程当中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
以获取某个活动的数据、根据条件发放奖励为例,在实现这两种功能时,都需要实现以下功能:
其中,在正常的逻辑编写当中,红框内的代码是冗余的,需要在所有需要使用到该功能的地方进行代码的复制粘贴,故而此时提出一种公共方法,使得每个接口都能调用这个接口,减少代码的冗余,且当红框内的代码需要修改时,只需修改该公共方法接口即可,降低了代码的耦合度。
虽然情况得到了缓解,但是每个接口都需要调用这个方法,于是就有了切面的概念,只需要将方法注入到接口调用的某个地方(切点)即可。
此时,接口就只需要关注具体的业务,而不需要关注其他非该接口关注的逻辑处理。
(一)简介
AOP(Aspect Oriented Programming)
,意为面向切面编程,以预编译方式和运行期间动态代理方式实现在不修改源代码的情况下将程序统一添加额外功能。其是OOP(Object Oriented Programming
,面向对象编程)的一种延伸,前者是纵向(继承)编程,而AOP
是横向(前面)编程。利用AOP
,可将业务逻辑的各个部分进行隔离,从而降低各部分之间的耦合度。
优点在于,对于一个切面(对象与对象之间、方法与方法之间、模块与模块之间都是一个个切面)中的内容,其会有一个统一的接口来与其他切面进行连接,故而只需关注该切面的具体业务。