Spring基本使用及核心技术

Spring

介绍

Spring 全家桶

  • Spring
  • Spring MVC
  • Spring Boot
  • Spring Cloud
背景

出现在 2002 年左右,解决企业开发难度

  • 减轻项目模块间的管理
  • 帮助创建对象、管理对象间的关系
spring

一个框架,一个存放Java 对象的容器

  • 核心技术
    • IoC:控制反转
    • AOP:面向切面编程
  • 实现模块、类之间的解耦合

依赖:使用一个类的属性或方法叫做依赖这个类

使用
  • 将项目中使用的对象放到容器中

    • 容器完成对象的创建、对象间关系管理(属性赋值)

    • 程序中从容器获取对象

  • 放入容器的对象

    • dao 类、service 类、controller 类、工具类

    • spring 中对象默认都是单例的

      • 容器中不存在同名对象
  • 不放入的对象

    • 实体类对象

      • 数据来自数据库,可以放但没意义
      • 在查询过程中创建对象
    • servlet、listener、filter 等

      • 由 Tomcat 容器创建对象
  • 简单流程

    1. 创建 Maven 项目

      • 添加相关依赖
        • 添加 spring-context 依赖
    2. 创建类

      • 接口、实现类
        • 简单 Java 类
    3. 创建 Spring 配置文件

      • 配置文件公认名 applicationContext.xml

      • 使用 <bean> 标签声明对象

      • 通过 spring 语法完成属性赋值

    4. 获取对象进行使用

    5. 测试

优点
  1. 轻量
    • Spring 框架使用的 jar 都比较小
      • 核心功能所需框架 jar 总共 3M 左右
    • 运行占用资源少,运行效率高,不依赖其他 jar 包
  2. 针对接口编程、解耦合
    • Spring 提供了 IoC 控制反转
      • 由容器管理对象、对象的依赖关系
      • 代码中的对象创建方式由容器完成,对象间依赖解耦
  3. AOP 编程支持
    • 进行面向切面的编程
      • 不容易通过传统 OOP 实现的功能可以通过 AOP 进行
    • 开发人员可以从繁杂的事务管理代码中解脱出来,通过声明方式灵活进行事务管理,提高开发效率和质量
  4. 方便集成各种优秀框架
    • 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

    1. 创建类继承 HttpServlet

    2. 在 web.xml 注册 servlet

      <seevlet-name>myservlet</servlet-name>
      <servlet-class>demo.myservlet</servlet-class>
      
    3. 无需创建 Servlet 对象,由 Tomcat 服务器创建

      • Tomcat 也是容器
        • 存放有 Servlet、Listener、Filter 对象

DI

  • DI:Dependency Injection,依赖注入

    • 只需要在程序中给提供要使用的对象名称,对象创建、赋值、查找由容器内部实现
  • Spring 使用 DI 实现 IoC 的功能

    • Spring 底层创建对象使用反射机制
    • IoC 是理论基础,DI 实现 Ioc,底层实现使用反射机制
  • 实现方式

    1. 在 spring 配置文件中使用标签和属性实现

      • 基于 XML 的实现
        • 经常修改时使用配置文件更方便
        • 优:与源文件完全分离,便于修改
        • 劣:代码量较大,且使用不太直观
    2. 使用 spring 中的注解完成属性赋值

      • 基于注解的 DI 实现
        • 不经常改动时便于使用
        • 优:代码量较少,使用便捷直观,信息
          • 获取信息可读性较好
        • 劣:侵入源代码,不便于修改
          • 使用 注解 + 配置文件引用赋值解耦合

基于 XML

创建对象
  • 在 spring 配置文件添加 bean 信息

    • 声明 JavaBean 实例对象
      • 自定义类或系统类
    • 在 spring 底层自动创建对象
      • 默认使用无参构造
      • 创建的对象存在 Map 中
        • key 是自定义 bean 的 id,value 是自动创建的对象
    <!-- 一个 bean 标签声明一个对象 -->
    <bean id="demo" name="demo" class="demo.bean.Demo"/>
    
    • id:对象名,唯一标识
    • name:对象别名,相当于 id
  • 获取对象

    // 读取配置文件创建容器对象,此时 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"
    • byType:按类型注入
      • Java 类中引用类型的数据类型和 spring 容器中 <bean> 的 class 值属于同源关系
        • class 类型和属性类型是相同类
        • class 类型是属性类型继承类
        • class 类型是属性类型接口实现类
      • 同时存在多个同源类型无法使用 byType
        • 会检测到多个同源类造成冲突无法赋值
      • <bean> 中添加 autowire="byType"
<!-- 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>
    

基于注解

  • 使用步骤

    1. 加入 maven 依赖

      • 加入 spring-context 依赖会自动导入 spring-aop 依赖
        • 注解必须使用 spring-aop 依赖 jar 包
    2. 在类中加入 spring 注解

      • 多个不同功能的注解
    3. 在 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 容器中只有一个
  • 位置:类定义的上面添加注解

  • 定义注解

    • // @Component(value = "demo") 	// 最准确的写法
      // @Component					// 使用 spring 提供默认名:类名首字母小写;最常用
      @Component("demo")  			// 省略 value 写法
      public class Demo { /* 类内容 */ }
      
component-scan
  • 组件扫描器:组件就是 Java 对象
  • base-package:指定注解在项目中的包名
  • 内置了 <context:annotation-config/> 注解支持驱动
  • 工作方式
    1. spring 扫描 base-package 指定的包及子包中所有类
    2. 找到类中的注解
    3. 按注解功能创建对象或给属性赋值
<!-- 声明组件扫描器后会添加新约束文件 -->
<!--
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 方法

// @Value(value = "二狗")  	// 完整写法
@Value("二狗")  				// 直接赋值
private String name;

@Value("22")
public void setAge(int age){
    this.age = age;
}
@Autowired
  • 对引用类型赋值

    • 使用自动注入原理:byTypebyName

      • 默认使用 byType
        • 同时存在多个同源对象会发生冲突
    • 强制使用 byName 自动注入

      • 添加 @Autowired 注解
      • 添加 @Qualifier(value = "id") 注解
        • 存在多个同源类型时必须指定使用 byName 赋值
          • 否则会有冲突
        • id 值错误时报异常
      • 两个注解顺序不影响结果
  • 需要先声明引用类型对象

  • 属性 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 方注入
@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 方法
  • 优点
    • 可使用面向对象方式:一个配置类可以继承配置类,可以重写方法
    • 避免繁琐的 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 环境
  • session:会话作用域
    • 同一个 HTTP Session 中的请求共享一个 Bean
      • 不同 Session 使用不同的 Bean
    • 仅适用于 web 的 Spring WebApplicationContext 环境
  • application:全局作用域
    • 限定一个 Bean 的作用域为 ServletContext 的生命周期
    • 仅适用于 web 的 Spring WebApplicationContext 环境
  • websocket:全局作用域
设置
<bean id="" class="" scope="作用域">

AOP

AOP

  • Aspect Oriented Programming,面向切面编程
    • 通过运行期动态代理实现程序功能的统一维护的一种技术
  • Spring 框架的重要内容
    • 利用 AOP 可以对业务逻辑各部分进行隔离
      • 降低业务逻辑各部分耦合度,提高程序可重用性
      • 提高开发效率
  • 底层通过动态代理实现
  • 使用场景
    1. 给原本的类修改完善功能
    2. 给项目中多个类增加相同功能
    3. 给业务方法增加事务、日志输出等
面向切面
  • 将交叉业务逻辑封装成切面;利用 AOP 容器将切面织入到主业务逻辑中
    • 交叉业务逻辑:通用的、与主业务逻辑无关的代码
      • 例如:安全检查、事务、日志等
    • 不使用 AOP 则会出现代码纠缠
      • 交叉业务逻辑和主业务逻辑混合在一起,使主业务逻辑混杂不清
  • 理解面向切面编程
    • 分析项目功能时找出切面
    • 合理安排切面执行时间
      • 在目标方法之前、之后
    • 合理安排切面执行的位置
      • 在哪个类、哪个方法增加增强功能
动态代理
  • 面向切面编程基于动态代理
    • 可使用 JDKCGLIB 两种代理方式
  • AOP 就是动态代理的规范化
    • 定义动态代理的实现步骤、方式
    • 开发使用统一的方式,即动态代理
  • 动态代理两种方式
    1. 基于接口实现
      • 利用 ProxyInovcationHandlerMethon 等系统类创建代理对象执行业务方法
      • 基于接口的实现机制
    2. 无需接口: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 方式

      1. 使用 xml 配置文件
        • 配置全局事务
      2. 使用注解
        • 通常使用注解
        • 5 个注解

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 ~ 任意多个字符
      • ..
        • 在方法参数中表示任意多参数
        • 在包名后表示当前包及子包路径
        • 在类名中出现时后面必须跟 *
          • 表示包、子包下所有类
      • +
        • 类名后表示当前类及子类
        • 接口后表示当前接口及实现类
  • 示例

    1. execution(public * *(..))
      • 指定切入点:所有公共方法
    2. execution(* set*(..))
      • 指定切入点:所有以 set 开始的方法
    3. execution(* com.xyz.service.*.*(..))
      • 指定切入点:com.xyz.service 包中所有类的所有方法
    4. execution(* com.xyz.service..*.*(..))
      • 指定切入点:com.xyz.service 包或子包中所有类的所有方法
    5. execution(* *..service.*.*(..))
      • 指定切入点:所有包下的 service 子包中所有类、接口的所有方法
基本流程
  • 使用 aspectj 实现 AOP 的基本流程

    1. 创建 Maven 项目
    2. 添加依赖
      • spring 依赖
      • aspectj 依赖
    3. 创建目标类:接口和实现类
    4. 创建切面类:普通 Java 类
      • 类上面添加 @Aspect 注解
      • 类中定义方法
        • 方法上添加 aspectj 中的中的通知注解
          • 例如:@Before
        • 指定执行位置(execution()
    5. 创建 Spring 配置文件
      • 声明对象,并把对象交给容器统一管理
        • 使用 xml 配置文件或注解
      • 声明目标对象
      • 声明切面类对象
      • 声明 aspectj 框架中的自动代理生成器标签
        • 自动代理生成器
          • 完成代理对象的自动创建功能
    6. 创建测试类
      • 从 spring 容器中获取目标对象(即 代理对象)
        • 通过代理执行方法实现 AOP 的功能增强
切面类
@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"/>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值