Spring
介绍
Spring 全家桶
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
背景
出现在 2002 年左右,解决企业开发难度
- 减轻项目模块间的管理
- 帮助创建对象、管理对象间的关系
spring
一个框架,一个存放Java 对象的容器
- 核心技术
- IoC:控制反转
- AOP:面向切面编程
- 实现模块、类之间的解耦合
依赖:使用一个类的属性或方法叫做依赖这个类
使用
-
将项目中使用的对象放到容器中
-
容器完成对象的创建、对象间关系管理(属性赋值)
-
程序中从容器获取对象
-
-
放入容器的对象
-
dao 类、service 类、controller 类、工具类
-
spring 中对象默认都是单例的
- 容器中不存在同名对象
-
-
不放入的对象
-
实体类对象
- 数据来自数据库,可以放但没意义
- 在查询过程中创建对象
-
servlet、listener、filter 等
- 由 Tomcat 容器创建对象
-
-
简单流程
-
创建 Maven 项目
- 添加相关依赖
- 添加
spring-context
依赖
- 添加
- 添加相关依赖
-
创建类
- 接口、实现类
- 简单 Java 类
- 接口、实现类
-
创建 Spring 配置文件
-
配置文件公认名
applicationContext.xml
-
使用
<bean>
标签声明对象 -
通过
spring
语法完成属性赋值
-
-
获取对象进行使用
-
测试
-
优点
- 轻量
- Spring 框架使用的 jar 都比较小
- 核心功能所需框架 jar 总共 3M 左右
- 运行占用资源少,运行效率高,不依赖其他 jar 包
- Spring 框架使用的 jar 都比较小
- 针对接口编程、解耦合
- Spring 提供了 IoC 控制反转
- 由容器管理对象、对象的依赖关系
- 代码中的对象创建方式由容器完成,对象间依赖解耦
- Spring 提供了 IoC 控制反转
- AOP 编程支持
- 进行面向切面的编程
- 不容易通过传统 OOP 实现的功能可以通过 AOP 进行
- 开发人员可以从繁杂的事务管理代码中解脱出来,通过声明方式灵活进行事务管理,提高开发效率和质量
- 进行面向切面的编程
- 方便集成各种优秀框架
- Spring 不排斥各种开源框架,并且可以降低框架使用难度
- 提供了对各种优秀框架的直接支持,简化使用
- 如 Struct 、Hibernate、MyBatis
体系结构
- 数据访问/集成:Date Access/Integration
- Web 开发:Web
- 面向切面编程:AOP、Aspects
- JVM 代理:Instrumentation、消息发送:Messaging
- 核心容器:Core Container
- 存放 Java对象
- 测试:Test
IoC
概念
-
IoC:Inversion of Control,控制反转
-
一种理论、概念、思想
- 对象的创建、赋值、管理由代码之外的容器实现
- 对象创建由外部资源完成
-
指导开发人员在容器中,代码之外管理对象,属性赋值、管理依赖
-
-
容器:服务器软件、框架(Spring)
-
控制:创建对象、对象的属性赋值、对象间的关系管理
-
反转:创建对象的权限交给代码外的容器实现,由容器代替来创建对象、管理对象、给属性赋值
- 正转:直接在代码中 new 对象、主动管理对象
-
使用原因:减少对代码的改动也能实现不同的功能
- 解耦合:实现业务对象之间的解耦合
- 例如 service 和 dao 对象间的解耦合
- 解耦合:实现业务对象之间的解耦合
-
体现:例如 servlet
-
创建类继承 HttpServlet
-
在 web.xml 注册 servlet
<seevlet-name>myservlet</servlet-name> <servlet-class>demo.myservlet</servlet-class>
-
无需创建 Servlet 对象,由 Tomcat 服务器创建
- Tomcat 也是容器
- 存放有 Servlet、Listener、Filter 对象
- Tomcat 也是容器
-
DI
-
DI:Dependency Injection,依赖注入
- 只需要在程序中给提供要使用的对象名称,对象创建、赋值、查找由容器内部实现
-
Spring 使用 DI 实现 IoC 的功能
- Spring 底层创建对象使用反射机制
- IoC 是理论基础,DI 实现 Ioc,底层实现使用反射机制
-
实现方式
-
在 spring 配置文件中使用标签和属性实现
- 基于 XML 的实现
- 经常修改时使用配置文件更方便
- 优:与源文件完全分离,便于修改
- 劣:代码量较大,且使用不太直观
- 基于 XML 的实现
-
使用 spring 中的注解完成属性赋值
- 基于注解的 DI 实现
- 不经常改动时便于使用
- 优:代码量较少,使用便捷直观,信息
- 获取信息可读性较好
- 劣:侵入源代码,不便于修改
- 使用 注解 + 配置文件引用赋值解耦合
- 基于注解的 DI 实现
-
基于 XML
创建对象
-
在 spring 配置文件添加 bean 信息
- 声明 JavaBean 实例对象
- 自定义类或系统类
- 在 spring 底层自动创建对象
- 默认使用无参构造
- 创建的对象存在 Map 中
- key 是自定义 bean 的 id,value 是自动创建的对象
<!-- 一个 bean 标签声明一个对象 --> <bean id="demo" name="demo" class="demo.bean.Demo"/>
- id:对象名,唯一标识
- name:对象别名,相当于 id
- 声明 JavaBean 实例对象
-
获取对象
// 读取配置文件创建容器对象,此时 demo 对象已经创建 ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); // 从容器获取对象,使用 id 从 map 获取对象,获取对象类型为 Object,需要强转为目标类型 Demo demo = (Demo)context.getBean("demo")
-
创建对象的时间
- 创建容器对象时创建配置文件中所有对象
- 可多次创建同一类的对象
- 可声明 Java 系统类,同样可以创建
- 创建默认使用无参构造
- 创建容器对象时创建配置文件中所有对象
-
获取容器中定义的对象数量
int count = context.getBeanDefinitionCount();
-
获取容器中定义的所有 bean 的 id
String[] names = context.getBeanDefinitionNames();
属性赋值
-
按语法分类
-
set 注入:设值注入
- spring 调用类的 set 方法实现属性赋值
-
构造注入
- spring 调用类的有参构造创建对象
- 构造方法中完成赋值
-
set 注入
- 简单类型:基本数据类型 和 String 类型
- 语法:
<property name="demo01" value="demo01"/>
- name:属性名,value:属性值
- 语法:
- 引用类型
- 语法:
<property name="demo01" ref="demo01"/>
- ref:属性值,即对应对象的 bean 标签 id
- bean 标签顺序不影响结果
- 语法:
<bean id="demo" class="demo.bean.Demo">
<!-- 简单类型赋值 -->
<property name="name" value="二狗"/>
<property name="age" value="20"/>
<!-- 引用类型赋值 -->
<property name="demo01" ref="demo01"/>
</bean>
<bean id="demo01" class="demo.bean.Demo01" >
<property name="address" value="沙雕中心"/>
<property name="telNumber" value="110"/>
</bean>
-
调用无参构造创建对象,调用 set 方法进行赋值
-
类中必须有与 name 值相同的的 set 方法
-
无论该方法对应的属性是否存在,无论该方法如何实现
-
构造注入
- 调用有参构造方法创建对象,同时进行赋值
- 使用
<constructor-arg>
标签
<bean id="demo" class="demo.bean.Demo">
<!-- 一个标签表示一个构造方法参数 -->
<!-- 属性名赋值 -->
<constructor-arg name="name" value="二狗"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="demo01" ref="demo01"/>
<!--
<!-- 索引赋值:根据指定构造器对应参数的索引进行赋值 -->
<constructor-arg index="0" value="二狗"/>
<constructor-arg index="1" value="18"/>
<constructor-arg index="2" ref="demo01"/>
-->
<!--
<!-- 省略属性名、索引赋值,必须保证赋值顺序和参数顺序一直 -->
<constructor-arg value="二狗"/>
<constructor-arg value="18"/>
<constructor-arg ref="demo01"/>
-->
</bean>
<bean id="demo01" class="demo.bean.Demo01" >
<property name="address" value="沙雕中心"/>
<property name="telNumber" value="110"/>
</bean>
-
标签属性
-
name:构造方法中形参名
-
index:构造方法参数索引,从 0 开始
-
value:简单类型赋值
-
ref:引用类型赋值
-
自动注入
- 引用类型的自动注入,仅对于引用类型有效
- spring 框架根据某些规则给引用类型自动赋值
- 使用规则
byName
:按名称注入- Java 类中引用类型属性名和 spring 容器中
<bean>
标签 id 值相同、数据类型一致 - 在
<bean>
中添加autowire="byName"
- Java 类中引用类型属性名和 spring 容器中
byType
:按类型注入- Java 类中引用类型的数据类型和 spring 容器中
<bean>
的 class 值属于同源关系- class 类型和属性类型是相同类
- class 类型是属性类型继承类
- class 类型是属性类型接口实现类
- 同时存在多个同源类型无法使用 byType
- 会检测到多个同源类造成冲突无法赋值
- 在
<bean>
中添加autowire="byType"
- Java 类中引用类型的数据类型和 spring 容器中
<!-- byName 自动注入 -->
<bean id="demo" class="demo.bean.Demo" autowire="byName">
<!--
byType 自动注入
<bean id="demo" class="demo.bean.Demo" autowire="byType">
-->
<property name="name" value="二狗"/>
<property name="age" value="20"/>
</bean>
<!-- id 和 demo 类中属性名相同可通过 byName 赋值 -->
<!-- class 和 demo 类中属性类型同源可通过 byType 赋值-->
<bean id="demo01" class="demo.bean.Demo01" >
<property name="address" value="沙雕中心"/>
<property name="telNumber" value="110" />
</bean>
集合类型
- 简单类型、引用对象类型、赋值
null
- 数组、List集合、Set集合、Map、Properties 类型
<bean id="demo" class="demo.domain.Demo">
<!-- String 类型赋值 -->
<property name="name" value="二狗"/>
<!-- Integer 类型赋值 null -->
<property name="age"> <null/> </property>
<!-- String[] 赋值, -->
<property name="array">
<array>
<value>废物1</value>
<value>废物2</value>
</array>
</property>
<!-- 引用类型 Goods 赋值 -->
<property name="goods" ref="goods"/>
<!-- List<Goods> 赋值 -->
<property name="list">
<list>
<ref bean="goods"/>
</list>
</property>
<!-- Set<Goods> 赋值 -->
<property name="set">
<set>
<ref bean="goods"/>
</set>
</property>
<!-- Map<String, String> 赋值 -->
<property name="map">
<map>
<entry key="name" value=""/>
</map>
</property>
<!-- Properties 类型赋值,key=value -->
<property name="properties">
<props>
<prop key="name">张三</prop>
</props>
</property>
</bean>
命名空间
-
导入命名空间约束才能使用
- p:根据属性注入
- c:构造器注入
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 约束文件中添加了 p、c 命名空间 --> <!-- 命名空间p:通过 p:属性名=value 直接进行赋值,set 注入 --> <bean id="p-namespace" class="com.example.ExampleBean" p:email="someone@somewhere.com"/> <bean id="beanTwo" class="x.y.ThingTwo"/> <bean id="beanThree" class="x.y.ThingThree"/> <!-- 命名空间 c: 通过 c:参数名称=参数值 使用构造器进行赋值 --> <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo" c:thingThree-ref="beanThree" c:email="something@somewhere.com"/> </beans>
基于注解
-
使用步骤
-
加入 maven 依赖
- 加入
spring-context
依赖会自动导入spring-aop
依赖- 注解必须使用
spring-aop
依赖 jar 包
- 注解必须使用
- 加入
-
在类中加入 spring 注解
- 多个不同功能的注解
-
在 spring 配置文件中加入组件扫描器标签
-
说明注解在项目中的位置
-
<!-- 组件扫描器:声明要扫描注解的包 --> <context:component-scan base-package="demo.bean"/> <!-- 注解支持,在注解扫描器中已经包含此声明 --> <context:annotation-config/>
-
-
-
基本注解
-
@Component
:创建通用对象,下面三个作用一样,但用于特定类型@Repository
:简单java实体类对象@Service
:服务层对象@Controller
:控制层对象
-
@Scope
:作用域,声明类的声明周期 -
@Value
:简单类型赋值,对类中简单类型属性赋值 -
@Autowired
:引用类型赋值,对类中引用类型属性赋值- 基于 Spring 框架实现,不推荐直接使用在字段上
@Qualifier(value = "id")
:指定属性名赋值
-
@Resource
:引用类型赋值,基于javax
实现
-
创建对象
@Component
-
@Component
注解用来创建对象- 等同于
<bean>
标签的作用 - 在 MVC 项目分层之外的类使用;通用类型,例如工具类等
- 等同于
-
和 @Component 基本功能相同的注解
@Repository
- 用在持久层类,dao 实现类上面
- 表示创建
dao
对象,能访问数据库
@Service
- 用在业务层类,service 实现类上面
- 创建
service
对象,做业务处理,可以有事务等功能
@Controller
- 用在控制器,控制器(处理器)类上面
- 创建控制器对象,接收用户提交的参数,显示请求的处理结果
- 使用语法和
@Component
相同 - 此三个注解还有不同的额外功能:给项目对象分层
-
属性
- value:对象名,即
<bean>
的 id - value 值唯一,创建的对象在 spring 容器中只有一个
- value:对象名,即
-
位置:类定义的上面添加注解
-
定义注解
-
// @Component(value = "demo") // 最准确的写法 // @Component // 使用 spring 提供默认名:类名首字母小写;最常用 @Component("demo") // 省略 value 写法 public class Demo { /* 类内容 */ }
-
component-scan
- 组件扫描器:组件就是 Java 对象
base-package
:指定注解在项目中的包名- 内置了
<context:annotation-config/>
注解支持驱动 - 工作方式
spring
扫描base-package
指定的包及子包中所有类- 找到类中的注解
- 按注解功能创建对象或给属性赋值
<!-- 声明组件扫描器后会添加新约束文件 -->
<!--
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
-->
<!-- 定义组件扫描器:context 是约束文件的命名空间,即扫描器定义在约束文件中 -->
<!-- 三种使用方式 -->
<!-- 指定一个包 -->
<context:component-scan base-package="demo.bean"/>
<!-- 分隔符 ; 或 , 来分隔多个包名 -->
<context:component-scan base-package="demo.bean;demo.utils"/>
<!-- 指定父包,自动扫描子包 -->
<context:compontext-scan base-package="demo">
属性赋值
- 创建对象时组件扫描器包路径必须保证正确
- 否则无法扫描到类,无法根据注解进行操作
@Component
注解必须添加- 对象必须先创建才能赋值属性
@Value
-
对简单类型属性赋值
- 在属性上或在 setter 方法上使用注解
@Value
- 注解的 value 值指定要注入的值
- 在属性上或在 setter 方法上使用注解
-
使用该注解完成属性注入时类中无需 setter 方法
// @Value(value = "二狗") // 完整写法
@Value("二狗") // 直接赋值
private String name;
@Value("22")
public void setAge(int age){
this.age = age;
}
@Autowired
-
对引用类型赋值
-
使用自动注入原理:
byType
、byName
- 默认使用 byType
- 同时存在多个同源对象会发生冲突
- 默认使用 byType
-
强制使用 byName 自动注入
- 添加
@Autowired
注解 - 添加
@Qualifier(value = "id")
注解- 存在多个同源类型时必须指定使用 byName 赋值
- 否则会有冲突
- id 值错误时报异常
- 存在多个同源类型时必须指定使用 byName 赋值
- 两个注解顺序不影响结果
- 添加
-
-
需要先声明引用类型对象
-
属性
required
:通过byName
注入时产生作用- 默认 true:id 值找不到时报错
- 设为 false:id 值找不到时自动赋值 null,不报错
/*
直接声明在属性上,不建议使用
通过 byType 自动注入,有多个同源对象会产生冲突
*/
@Autowired
private Demo01 demo01;
@Autowired
public void setDemo01(Demo01 demo01) {
this.demo01 = demo01;
}
/*
通过 byName 自动注入,默认 id 值找不到时报错
*/
@Qualifier("demo01")
@Autowired
public void setDemo01(Demo01 demo01) {
this.demo01 = demo01;
}
// 赋值前需要已经声明 demo01 对象
@Component(value = "demo01")
public class Demo01 {
@Value("北京")
private String address;
@Value("110")
private long telNumber;
}
@Resource
- 由 JDK 提供,spring 提供对该注解功能的支持
- 通过自动注入原理赋值
- 使用方式类似 @Autowired
- 默认是 byName 方式找跟属性同名对象
- byName 找不到时自动使用 byType 注入
- buType 找到多个同源对象报错
- 指定 name 属性强制使用 byName 方式注入
- byName 找不到时自动使用 byType 注入
// 默认使用 byName 注入,失败后自动使用 byType 方注入
@Resource
private Demo11 demo11;
/*
指定 name 属性强制使用 byName 方式找同名对象
@Resource(name = "demo10")
private Demo11 demo11;
*/
注解 + 配置文件
<!-- 在 spring 配置文件中加载外部属性配置文件 -->
<context:property-placeholder location="classpath:filed.properties" />
# 配置文件中记录属性值
name=张三
age=18
resourceName=demo01 # 引用类型对象,使用注解生成对象指定的 id
// 引用配置文件中的内容赋值
@Value("${name}")
private String name;
@Value("${age}")
private int age;
// 引用类型,使用 @Resource 注解
@Resource(name = "${resourceName}")
private Demo11 demo11;
配置文件
- 配置文件通用公认名
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- spring 配置文件标准内容 -->
<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">
<!--
约束:对使用的标签和语法格式的限制
spring-beans.xsd:约束文件;xsd 是文件类型;beans 是根标签
-->
<!--
声明 bean,告诉 spring 要创建对象的类
class:类全限定名,不能是接口;spring使用反射机制创建对象必须使用类
id:自定义类的实例化对象名
创建好的对象放到 map 中,spring 框架有 map 存放对象:springMap.put(demo, Demo demo = new Demo())
-->
<!-- 一个 bean 标签声明一个对象 -->
<bean id="demo" class="demo.bean.Demo" autowire="byName">
<property name="name" value="二狗"/>
<property name="age" value="20"/>
<property name="demo01" ref="demo01"/>
<!-- 构造注入 -->
<!-- <constructor-arg name="name" value="二狗"/> -->
<!-- <constructor-arg name="age" value="18"/> -->
<!-- <constructor-arg type="demo.bean.Demo01" name="demo01" ref="demo01"/> -->
<!-- 缺省构造注入 -->
<!-- <constructor-arg value="二狗"/> -->
<!-- <constructor-arg value="18"/> -->
<!-- <constructor-arg ref="demo01"/> -->
</bean>
<!-- 声明另一个 bean,同时此对象被引用 -->
<bean id="demo01" class="demo.bean.Demo01" >
<property name="address" value="沙雕中心"/>
<property name="telNumber" value="110" />
</bean>
</beans>
多配置文件
-
较大规模项目使用多个配置文件
-
优势
-
多文件大小比单一文件较小,效率更高
-
避免多人开发的文件资源竞争
-
避免多模块功能配置混乱
-
-
分类
-
按功能模块:一个模块一个配置文件
-
按类的功能
- 数据库相关一个配置文件
- service 功能一个配置文件 等
-
-
使用
-
主配置文件
- 用来包含其他配置文件,一般不定义对象
-
加载包含关系的配置文件
<!-- 在主配置文件导入其他配置文件 --> <import resource="classpath:demo/demo-config.xml"/> <import resource="classpath:demo/demo01-config.xml"/> <!-- 使用通配符加载所有符合 *-config.xml 的文件 --> <import resource="classpath:demo/*-config.xml"/>
classpath
:表示类路径,即.class
文件所在目录路径spring
配置文件中要指定其他文件位置需要使用classpath
来加载文件
- 使用通配符
*
表示任意字符- 通配符加载的文件需要在同一个目录下且必须有目录
- 不能直接在
resources
下
- 不能直接在
- 通配符文件名不能将主配置文件包含
- 会造成死循环
- 通配符加载的文件需要在同一个目录下且必须有目录
-
Java Config
-
JavaConfig
是 Spring 的子项目,在 Spring 4 成为核心功能@Configuration
:注解在类上将此类作为配置类使用@Bean
:注解在方法上使方法返回的对象放入 Spring 容器
-
Spring 使用 Xml 作为容器配置文件需要写大量配置文件
- 还要配置各种对象,将使用的对象放到 Spring 容器才能使用
- 需要了解其他框架配置规则
-
在 3.0 之后加入 JavaConfig,使用 Java 类做配置文件使用
@Configuration
- Spring 提供的使用 Java 类配置容器
- 使用 Java 类作为 xml 配置文件的替代
- 在类中可创建对象,将对象注入容器
- 配置 Spring IOC 容器的纯 Java 方法
- 使用 Java 类作为 xml 配置文件的替代
- 优点
- 可使用面向对象方式:一个配置类可以继承配置类,可以重写方法
- 避免繁琐的 xml 配置
- 使用注解
@Configuration
- 注解在类上面,表示该类作为配置文件使用
@Bean
- 声明对象,把对象注入到容器
- 通过 name 或 value 属性指定对象名
- 默认对象名是 方法名
/**
* @Configuration 表示当前类作为配置文件使用,相当于 配置文件
*/
@Configuration
public class Conf {
/**
* 创建方法,返回值是对象
* @Bean 将对象注入到 Spring 容器,相当于 <bean></bean> 的作用
* 默认对象名是方法名
* 使用注解属性 name 或 value 指定对象名,即指定 <bean> 的 id
*
* @return 返回对象,注入到容器
*/
@Bean(name = "demo")
public Demo getDemo(){
Demo demo = new Demo();
demo.setName("李四");
demo.setAge(18);
demo.setBirthday(new Date());
demo.setHobby("篮球");
return demo;
}
}
@ImportResource
- 导入配置文件,等同于 xml 文件的
<import resource=""/>
- 注解在类定义上
- 导入配置文件中的 bean 对象
- 属性 value 和 location 作用一样,都是指定配置文件位置
- 属性为数组类型,多个配置文件使用数组添加
@Configuration
@ImportResource({"classpath:applicationContext.xml", "classpath:bean.xml"})
public class Conf {}
@PropertyResource
- 读取
properties
类型属性配置文件- 例:数据路连接信息的配置文件
- 实现外部化配置,在程序代码之外提供数据
@PropertySource
- 属性 value 指定配置文件路径,数组类型,可以指定多个文件
// 配置类
@Configuration
@PropertySource(value = "classpath:pro.properties", encoding = "UTF-8") // 以 UTF-8 编码加载外部配置文件
@ComponentScan(basePackages = "demo.domain") // 组件扫描,扫描通过注解创建的对象
public class Conf {}
@Import
- 导入其他配置类
- 注解在类定义上
- 属性值:要导入的配置类的 Class 对象
@Configuration
@ImportResource("classpath:applicationContext.xml")
@PropertySource(value = "classpath:filed.properties", encoding = "utf-8")
@Import(BeanConf2.class) // 通过类的 Class 对象查找
public class BeanConf {
@Bean
public Dmeo getDemo(){
return new Demo();
}
}
@ComponentScan
- 组件扫描驱动
@ComponentScan
- 该注解类似组件扫描器
- 扫描通过注解创建的对象
- 属性 value、basePackages 指定注解所在包
- 找到注解创建对象并放到 Spring 容器中
- 该注解类似组件扫描器
@Value
- 给类中属性赋值
${}
:占位符,通过配置文件赋值
@Configuration
@ImportResource("classpath:applicationContext.xml")
@PropertySource(value = "classpath:filed.properties", encoding = "utf-8")
@Import(BeanConf2.class)
@ComponentScan("demo.bean")
public class BeanConf {}
// 普通Java类,注解创建对象并赋值
@Component("demo03")
public class Demo03 {
@Value("${name}") // ${} 占位符,引用配置文件中值进行赋值
private String name;
@Value("${age}")
private int age;
@Autowired
private Date birthday;
@Value("${hobby}")
private String hobby;
手动获取对象
- Spring 中手动获取容器对象
@Test
public void demo(){
// 通过 AnnotationConfigApplicationContext 类获取容器中的对象
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConf.class);
Demo demo = (Demo)context.getBean("getDemo");
}
简单使用
@Configuration // 表示当前类作为配置文件使用
@ImportResource("classpath:applicationContext.xml") // 导入外部 xml 配置文件中的 bean 对象
@PropertySource(value = "classpath:pro.properties", encoding = "UTF-8") // 导入属性配置文件,指定配置文件路径
@ComponentScan(basePackages = "demo.domain") // 组件扫描器,扫描注解,创建对象;指定注解所在包
public class Conf {
/**
* 创建方法,返回值是对象
* @Bean 将对象注入到 Spring 容器,相当于 <bean></bean> 作用
* 默认对象名是方法名
* 使用注解属性 name 或 value 指定对象名:指定 <bean> 的 id
*
* @return 返回对象,注入到容器
*/
@Bean(name = "demo02")
public Demo02 getDemo(){
Demo02 demo = new Demo02();
demo.setName("张二");
demo.setAge(19);
demo.setHobby("rap");
demo.setBirthday(new Date());
return demo;
}
}
Bean 作用域
分类
singleton
:单例模式,默认机制- 将单个 bean 定义的范围限定为每个 Spring IoC 容器的单个对象实例
prototype
:原型模式- 每次获取都产生新的实例
- 创建容器的时候不进行实例化,获取对象的时候才实例化
request
:请求作用域- 每个 HTTP request 请求都有自己的 bean 实例
- 该实例是在单个 bean 定义后创建的
- 仅适用于 web 的 Spring WebApplicationContext 环境
- 每个 HTTP request 请求都有自己的 bean 实例
session
:会话作用域- 同一个 HTTP Session 中的请求共享一个 Bean
- 不同 Session 使用不同的 Bean
- 仅适用于 web 的 Spring WebApplicationContext 环境
- 同一个 HTTP Session 中的请求共享一个 Bean
application
:全局作用域- 限定一个 Bean 的作用域为
ServletContext
的生命周期 - 仅适用于 web 的 Spring WebApplicationContext 环境
- 限定一个 Bean 的作用域为
websocket
:全局作用域
设置
<bean id="" class="" scope="作用域">
AOP
AOP
Aspect Oriented Programming
,面向切面编程- 通过运行期动态代理实现程序功能的统一维护的一种技术
- Spring 框架的重要内容
- 利用 AOP 可以对业务逻辑各部分进行隔离
- 降低业务逻辑各部分耦合度,提高程序可重用性
- 提高开发效率
- 利用 AOP 可以对业务逻辑各部分进行隔离
- 底层通过动态代理实现
- 使用场景
- 给原本的类修改完善功能
- 给项目中多个类增加相同功能
- 给业务方法增加事务、日志输出等
面向切面
- 将交叉业务逻辑封装成切面;利用 AOP 容器将切面织入到主业务逻辑中
- 交叉业务逻辑:通用的、与主业务逻辑无关的代码
- 例如:安全检查、事务、日志等
- 不使用 AOP 则会出现代码纠缠
- 交叉业务逻辑和主业务逻辑混合在一起,使主业务逻辑混杂不清
- 交叉业务逻辑:通用的、与主业务逻辑无关的代码
- 理解面向切面编程
- 分析项目功能时找出切面
- 合理安排切面执行时间
- 在目标方法之前、之后
- 合理安排切面执行的位置
- 在哪个类、哪个方法增加增强功能
动态代理
- 面向切面编程基于动态代理
- 可使用 JDK、CGLIB 两种代理方式
- AOP 就是动态代理的规范化
- 定义动态代理的实现步骤、方式
- 开发使用统一的方式,即动态代理
- 动态代理两种方式
- 基于接口实现
- 利用
Proxy
、InovcationHandler
、Methon
等系统类创建代理对象执行业务方法 - 基于接口的实现机制
- 利用
- 无需接口:
CGLIB
实现动态代理- 基于类的继承机制,所以相关类和方法不能是
final
- 基于类的继承机制,所以相关类和方法不能是
- 基于接口实现
- 作用
- 不改变业务实现类源码情况下增加新功能能
- 减少代码重复
- 专注业务逻辑实现
- 解耦合,使业务功能和非业务功能分离
术语
-
Aspect
:切面,表示增强的功能:非业务功能- 通知方法所在的类
- 一个 Aspect 类可有多个通知方法
- 常见切面:日志、事务、统计、参数检查、权限验证等
- 通知方法所在的类
-
JoinPoint
:连接点- 连接业务功能和切面的位置,即某类的业务方法
-
PointCut
:切入点JoinPoint
的集合- 为要添加
advice
的方法划定范围
-
target
:目标对象- 要增加功能的类、对象 即 目标对象
JoinPoint
方法所在对象
-
Advice
:通知- 通知方法,表示切面功能执行的时间
- 已存在业务方法前、后、异常、最终处加入的通知方法
-
Weave
:织入- 把
advice
方法放在JoinPoint
的前、后、异常、最终处 - 只有植入后
advice
才会有效
- 把
-
切面关键三要素
-
功能代码:切面能执行的功能;Aspect
-
执行位置:PointCut 表示切面执行的位置;连接点集合
-
执行时间: Advice 表示时间;在目标方法之前、之后
-
实现框架
-
AOP 是规范,动态代理的规范化标准
- AOP 技术实现框架
-
Spring
- Spring 内部实现了 AOP 规范,可以使用
- 主要在事务处理时使用 AOP
- 实际很少使用 Spring 的 AOP 实现,使用过于笨重
-
aspectj:开源的专门做 AOP 的框架
-
Spring 集成了 aspectj 框架,通过 Spring 可以使用
-
实现 AOP 方式
- 使用 xml 配置文件
- 配置全局事务
- 使用注解
- 通常使用注解
- 5 个注解
- 使用 xml 配置文件
-
aspectj
执行时间
- 切面执行时间:Advice(通知、增强)
- 在
aspectj
中使用注解表示@Before
@AfterReturning
@Around
@AfterThorwing
@After
- 或使用 xml 配置文件标签表示
执行位置
-
切面执行位置使用切入点表达式
execution()
execution( modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern? )
- 参数说明
- modifiers-pattern:访问类型权限
- ret-type-pattern:返回值类型
- declaring-type-pattern:包名类名
- name-pattern(param-pattern):方法名(参数类型和个数)
- throws-pattern:抛出异常类型
?
:可选部分
- 参数说明
-
切入点表达式要匹配的对象是目标方法的方法名
- 表达式中就是方法签名
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
- 访问权限、异常类型 可忽略
- 各部分用空格分开,可使用通配符
*
:0 ~ 任意多个字符..
- 在方法参数中表示任意多参数
- 在包名后表示当前包及子包路径
- 在类名中出现时后面必须跟
*
- 表示包、子包下所有类
+
- 类名后表示当前类及子类
- 接口后表示当前接口及实现类
-
示例
execution(public * *(..))
- 指定切入点:所有公共方法
execution(* set*(..))
- 指定切入点:所有以 set 开始的方法
execution(* com.xyz.service.*.*(..))
- 指定切入点:com.xyz.service 包中所有类的所有方法
execution(* com.xyz.service..*.*(..))
- 指定切入点:com.xyz.service 包或子包中所有类的所有方法
execution(* *..service.*.*(..))
- 指定切入点:所有包下的 service 子包中所有类、接口的所有方法
基本流程
-
使用 aspectj 实现 AOP 的基本流程
- 创建 Maven 项目
- 添加依赖
- spring 依赖
- aspectj 依赖
- 创建目标类:接口和实现类
- 创建切面类:普通 Java 类
- 类上面添加
@Aspect
注解 - 类中定义方法
- 方法上添加 aspectj 中的中的通知注解
- 例如:
@Before
- 例如:
- 指定执行位置(
execution()
)
- 方法上添加 aspectj 中的中的通知注解
- 类上面添加
- 创建 Spring 配置文件
- 声明对象,并把对象交给容器统一管理
- 使用 xml 配置文件或注解
- 声明目标对象
- 声明切面类对象
- 声明 aspectj 框架中的自动代理生成器标签
- 自动代理生成器
- 完成代理对象的自动创建功能
- 自动代理生成器
- 声明对象,并把对象交给容器统一管理
- 创建测试类
- 从 spring 容器中获取目标对象(即 代理对象)
- 通过代理执行方法实现 AOP 的功能增强
- 从 spring 容器中获取目标对象(即 代理对象)
切面类
@Aspect
- aspectj 框架中的注解
- 表示当前类是切面类
- 用来给业务方法增加功能的类,类中有切面的功能代码
- 写在类定义上面
通知方法
要求
-
切面类的功能方法
- 方法用来实现切面功能
- 添加注解:执行时间
- 注解属性 value:执行位置
-
定义要求
- 公共方法:public 访问权限
- 无返回值
- 环绕通知必须有一个返回值
- 名称自定义
- 参数可有可无
- 有参数时不能是自定义类型
/* @Before:执行时间 execution():执行位置 */ @Before("execution(void send())") public void startTime() { // 功能代码 }
方法参数
-
指定通知方法中的参数:
JoinPoint
JoinPoint
:要加入切面功能的业务方法
-
作用:可以在通知方法中获取方法执行时的信息
- 例如:方法名、方法实参
-
切面功能需要用到目标方法方法信息时使用参数
- 参数由框架赋值,必须是第一个位置
@Before(void send()) public void start(JoinPoint jp){ /* 获取目标方法信息 */ // 完整方法定义 jp.getSignature(); // 方法名 jp.getSignature().getName(); // 获取方法所有参数 Object[] obj = jp.getArgs(); // 目标对象 jp.getTarget(); // 代理对象 jp.getThis(); // 完整切入点表达式 jp.toLongString(); // 简单切入点表达式 jp.toShortString(); }
@Before
- 前置通知,写在方法上面;目标方法之前执行
- 属性 value:切入点表达式,表示功能执行位置
- 特点
- 不会改变目标方法执行结果
- 不会影响目标方法执行
@AfterReturning
-
后置通知,目标方法返回后调用
-
属性
- value:切入点表达式
- returning:自定义变量名,表示目标方法的返回值
- 必须和通知方法形参名一致
-
特点
- 能获取目标方法返回值,根据不同返回值做不同处理
- 可修改返回值
- 类似传参,将目标方法返回值传到后置通知方法
- 引用类型返回值被修改后影响原结果
// 自定义变量名必须和通知方法形参名一致,jp必须是第一个参数
@AfterReturning(value = "execution(int[] send())", returning = "obj")
public void endTime(JoinPoint jp, Object obj) {
// 参数类型为引用类型的数组
// 修改数组数据会影响原返回值结果
int[] re = (int[])obj;
}
@After
- 最终通知,最终总会执行,即使目标方法异常
- 属性 value:切入点表达式
- 特点:总是会执行
- 在目标方法之后执行
- 一般进行资源清除工作
@AfterThrowing
- 异常通知,目标方法抛出异常后调用
- 属性
- value:切入点表达式
- throwing:自定义变量
- 表示目标方法抛出的异常对象
- 变量名必须和方法形参名保持一致
- 特点
- 可以做异常的监控程序
- 监控目标方法执行时是否有异常
- 有异常时可以发送邮件、短信进行通知
- 可以做异常的监控程序
@Around
- 环绕通知,将目标方法封装
- 属性 value:切入点表达式
- 特点:功能最强的通知
- 在目标方法前后都能增强功能
- 可以控制目标方法是否被调用
- 修改目标方法执行结果,影响最后调用结果
- 类似 JDK 动态代理中的 invoke 方法
- 方法定义格式
- public,方法名自定义
- 必须有返回值,推荐使用 Object
- 目标方法返回结果,可被修改
- 固定方法参数:
ProceedingJoinPoint
- 用来执行目标方法
- 获取目标方法信息
- 继承了 JoinPoint 类
@Around("execution(int[] send())") // 加强返回值 int[] 类型的 所有 send() 方法
public Object time(ProceedingJoinPoint point){
int[] obj;
try {
//获取目标方法实参集合
Objec[] args = point.getArgs();
if(args != null && argds.length > 1){
// 可以根据参数结果决定如何执行方法
Object name = args[0];
}
System.out.println("在目标方法之前执行方法");
obj = (int[]) point.proceed(); // 执行目标方法,获取方法返回值
System.out.println("在目标方法之后执行方法");
// 修改返回值
obj[0] = 0;
obj[1] = 0;
obj[2] = 0;
} catch (Throwable e) {
throw new RuntimeException(e);
}
return obj;
}
@Pointcut
-
@Pointcut
:定义切入点- 当较多通知方法使用相同的
execution()
表达式时编写维护比较麻烦
- 当较多通知方法使用相同的
-
@Pointcut
注解用于定义execution()
切入点表达式 -
注解在方法之上,之后所有的
execution()
的 value 属性值都可以使用该方法名作为切入点-
包括括号
-
代表 @Pointcut 定义的切入点
-
@Pointcut 注解的方法一般使用
private
方法- 该方法没有实际作用,仅作为通用切入点表达式存在
-
示例
切面类
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
// @Aspect 注解表示这是切面类
@Aspect
public class AspectDemo {
/*
* 在目标方法之前执行,输出执行时时间
* 目标方法:public void demo.bean.Demo01.send()
*/
@Before("execution(public void demo.bean.Demo01.send())")
public void startTime() {
System.out.println("开始执行:" + System.currentTimeMillis());
}
/*
* 在目标方法后执行,输出执行时时间
* 目标方法:所有无返回值、无参数的 send 方法
*/
@After("execution(void send())")
public void endTime() {
System.out.println("执行完毕:" + System.currentTimeMillis());
}
}
spring 配置文件
- 声明自动代理生成器
<!-- 声明自动代理生成器需要添加新约束文件 -->
<!--
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
-->
<!-- 使用注解生成了目标类对象 -->
<!-- 声明切面类对象,或者直接使用 @Component 注解声明 -->
<bean id="aspectDemo" class="demo.utils.AspectDemo"/>
<!--
声明自动代理生成器:使用 aspectj 框架的内部功能
aop 是新约束文件命名空间
-->
<!-- 默认使用 JDK 动态代理,当代理对象没有接口时使用 CGLIB -->
<aop:aspectj-autoproxy/>
autoproxy
<aop:aspectj-autoproxy/>
- 声明自动代理生成器,创建目标对象的代理对象
- 在内存中实现创建代理对象
- 先创建了目标对象和切面类对象
- 修改目标对象在的内存中结构,创建为代理对象
- 即 目标对象就是修改后的 代理对象
- 目标类有接口时默认使用 JDK 动态代理
- 没有接口时使用 CGLIB 动态代理
- 有接口时也可以选择使用 CGLIB 动态代理
<!-- 有接口时默认使用 JDK 动态代理,无接口使用 CGLIB -->
<aop:aspectj-autoproxy/>
<!-- 声明使用 CGLIB 动态代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>