# Spring

# 简介

  • Spring 框架 兴起于 2003 年。

  • 2002 年,首次退出了 Spring 框架的雏形:interface21 框架!

  • Rod Johnson,Spring Framework 创始人,著名作者。很难想象 Rod Johnson 的学历,真的让好多人大吃一惊,他是悉尼太学的博士,然而他的专业不是计算机,而是音乐学。

  • spring 理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架!

  • SSH:Struct2+Spring +Hibernate!

  • SSM:SpringMvc+Spring+Mybatis!

官网:https://spring.io/projects/spring-framework#overview
官方下载地址:http://repo.spring.io/release/org/springframework/spring
GitHub:https://github.com/spring-projects/spring-framework

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>

# 优点

  • Spring 是一个开源的免费的框架(容器)!
  • Spring 是一个轻量级的、非入侵式的框架!
  • 控制反转(IOC),面向切面编程(AOP)!
  • 支持事物的处理,对框架整合的支持!

总结一句话:Spring 就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!

# 组成

image.png

# 拓展

在 Spring 的官网有这个介绍:现代化的 Java 开发!就是基于 Spring 开发!
image.png

  • Spring Boot
    • 一个快速开发的脚手架。
    • 基于 SpringBoot 可以快速的开发单个微服务。
    • 约定大于配置!
  • Spring Cloud
    • SpringCloud 是基于 SpringBoot 实现的。

因为现在大多数公司都在使用 SpringBoot 进行快速开发,学习 SpringBoot 的前提,需要完全掌握 Spring 及 SpringMVC!

弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱”

# IOC 理论推导


  1. UserDao 接口
  2. UserDaoImpl 实现类
  3. UserService 业务接口
  4. UserServiceImpl 业务实现类

在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!如果程序代码量十分大,修改一次的成本代价十分昂贵!

我们使用一个 Set 接口实现。已经发生了革命性的变化!

private UserDao userDao;
// 利用 set 进行动态实现值的注入!
public void setuserpao(Userpao userDao){
	this.userDao=userDao;
}

之前,程序是主动创建对象!控制权在程序猿手上!
使用了 set 注入后,程序不再具有主动性,而是变成了被动的接受对象!

这种思想,从本质上解决了问题,我们程序猿不用再去管理对象的创建了。系统的耦合性大大降低~,可以更加专注的在业务的实现上!这是 IOC 的原型!

# IOC 本质

控制反转 loC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现 loC 的一种方法,也有人认为 DI 只是 loC 的另一种说法。没有 loC 的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

采用 XML 方式配置 Bean 的时候,Bean 的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean 的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML 或注解)并通过第三方去生产或获取特定对象的方式。在 Spring 中实现控制反转的是 loC 容器,其实现方法是依赖注入(Dependency Injection,DI)。

# HelloSpring


# 导入 Jar 包

注 : spring 需要导入 commons-logging 进行日志记录。我们利用 maven , 他会自动下载对应的依赖项 .

<dependency> 
  <groupId>org.springframework</groupId> 
  <artifactId>spring-webmvc</artifactId> 
  <version>5.1.10.RELEASE</version> 
</dependency>

# 编写代码

  1. 编写一个 Hello 实体类
public class Hello { 
    private String name; 
    public String getName() { 
        return name; 
    }
    public void setName(String name) { 
        this.name = name; 
    }
    public void show(){ 
        System.out.println("Hello,"+ name ); 
    } 
}
  1. 编写我们的 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 就是 java 对象,由 Spring 创建和管理 --> 
  <bean id="hello" class="com.kuang.pojo.Hello"> 
    <property name="name" value="Spring"/> 
  </bean> 
</beans>
  1. 我们可以去进行测试了 .
@Test
public void test(){ 
    // 解析 beans.xml 文件,生成管理相应的 Bean 对象 
    ApplicationContext context = new 
        ClassPathXmlApplicationContext("beans.xml"); 
    //getBean : 参数即为 spring 配置文件中 bean 的 id . 
    Hello hello = (Hello) context.getBean("hello"); 
    hello.show(); 
}

# 思考

  • Hello 对象是谁创建的?【 hello 对象是由 Spring 创建的 】
  • Hello 对象的属性是怎么设置的?【hello 对象的属性是由 Spring 容器设置的 】
  • 这个过程就叫控制反转 :
  • 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用 Spring 后,对象是由 Spring 来创建的
  • 反转:程序本身不创建对象,而变成被动的接收对象 .

依赖注入:就是利用 set 方法来进行注入的.
IOC 是一种编程思想,由主动的编程变成被动的接收
可以通过 newClassPathXmlApplicationContext 去浏览一下底层源码 .

# 修改案例一

我们在案例一中, 新增一个 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="MysqlImpl" class="com.kuang.dao.impl.UserDaoMySqlImpl"/> 
  <bean id="OracleImpl" class="com.kuang.dao.impl.UserDaoOracleImpl"/> 
  <bean id="ServiceImpl" class="com.kuang.service.impl.UserServiceImpl"> 
    <!-- 注意:这里的 name 并不是属性,而是 set 方法后面的那部分,首字母小写 --> 
    <!-- 引用另外一个 bean , 不是用 value 而是用 ref--> 
    <property name="userDao" ref="OracleImpl"/> 
  </bean> 
</beans>

测试!

@Test
public void test2(){ 
    ApplicationContext context = new 
        ClassPathXmlApplicationContext("beans.xml"); 
    UserServiceImpl serviceImpl = (UserServiceImpl) 
                                   context.getBean("ServiceImpl"); 
    serviceImpl.getUser(); 
}

OK , 到了现在,我们彻底不用再程序中去改动了,要实现不同的操作,只需要在 xml 配置文件中进行修改
,所谓的 IoC, 一句话搞定:对象由 Spring 来创建,管理,装配!

# IOC 创建对象方式


# 通过无参构造方法来创建

  1. User.java
public class User { 
    private String name; 
    public User() { 
        System.out.println("user无参构造方法"); 
    }
    public void setName(String name) { 
        this.name = name; 
    }
    public void show(){ 
        System.out.println("name="+ name ); 
    } 
}
  1. 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="user" class="com.kuang.pojo.User"> 
    <property name="name" value="kuangshen"/> 
  </bean> 
</beans>
  1. 测试类
@Test
public void test(){ 
    ApplicationContext context = new 
        ClassPathXmlApplicationContext("beans.xml"); 
    // 在执行 getBean 的时候,user 已经创建好了,通过无参构造 
    User user = (User) context.getBean("user"); 
    // 调用对象的方法 . 
    user.show(); 
}

结果可以发现,在调用 show 方法之前,User 对象已经通过无参构造初始化了!

# 通过有参构造方法来创建

  1. UserT.java
public class UserT { 
    private String name; 
    public UserT(String name) { 
        this.name = name; 
    }
    public void setName(String name) { 
        this.name = name; 
    }
    public void show(){ 
        System.out.println("name="+ name ); 
    } 
}
  1. beans.xml 有三种方式编写
<!-- 第一种根据 index 参数下标设置 --> 
<bean id="userT" class="com.kuang.pojo.UserT"> 
  <!-- index 指构造方法,下标从 0 开始 --> 
  <constructor-arg index="0" value="kuangshen2"/> 
</bean>
<!-- 第二种根据参数名字设置 --> 
<bean id="userT" class="com.kuang.pojo.UserT"> 
  <!-- name 指参数名 --> 
  <constructor-arg name="name" value="kuangshen2"/> 
</bean>
<!-- 第三种根据参数类型设置 --> 
<bean id="userT" class="com.kuang.pojo.UserT"> 
  <constructor-arg type="java.lang.String" value="kuangshen2"/> 
</bean>
  1. 测试
@Test
public void testT(){ 
    ApplicationContext context = new 
        ClassPathXmlApplicationContext("beans.xml"); 
    UserT user = (UserT) context.getBean("userT"); 
    user.show(); 
}

结论:在配置文件加载的时候。其中管理的对象都已经初始化了!

# Spring 配置

# 别名

alias 设置别名,为 bean 设置别名,可以设置多个别名

<!-- 设置别名:在获取 Bean 的时候可以使用别名获取 --> 
<alias name="userT" alias="userNew"/>

# Bean 的配置

<!--bean 就是 java 对象,由 Spring 创建和管理 --> 
<!--
id 是 bean 的标识符,要唯一,如果没有配置 id,name 就是默认标识符 
如果配置 id, 又配置了 name, 那么 name 是别名 
name 可以设置多个别名,可以用逗号,分号,空格隔开 
如果不配置 id 和 name, 可以根据 applicationContext.getBean (.class) 获取对象;
class 是 bean 的全限定名 = 包名 + 类名 
--> 
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello"> 
  <property name="name" value="Spring"/> 
</bean>

# import

团队的合作通过 import 来实现 .

<import resource="{path}/beans.xml"/>

# 依赖注入(DI)

  • 依赖注入(Dependency Injection,DI)。
  • 依赖:指 Bean 对象的创建依赖于容器 . Bean 对象的依赖资源 .
  • 注入:指 Bean 对象所依赖的资源,由容器来设置和装配 .

# 构造器注入

我们在之前的案例 4 已经详细讲过了

# set 注入 (重点)

要求被注入的属性,必须有 set 方法,set 方法的方法名由 set + 属性首字母大写,如果属性是 boolean 类型,没有 set 方法,是 is .
测试 pojo 类 :
Address.java

public class Address { 
    private String address; 
    public String getAddress() { 
        return address; 
    }
    public void setAddress(String address) { 
        this.address = address; 
    } 
}

Student.java

package com.kuang.pojo; 
import java.util.List; 
import java.util.Map; 
import java.util.Properties; 
import java.util.Set; 
public class Student { 
    private String name; 
    private Address address; 
    private String[] books; 
    private List<String> hobbys; 
    private Map<String,String> card; 
    private Set<String> games; 
    private String wife; 
    private Properties info; 
    public void setName(String name) { 
        this.name = name; 
    }
    public void setAddress(Address address) { 
        this.address = address; 
    }
    public void setBooks(String[] books) { 
        this.books = books; 
    }
    public void setHobbys(List<String> hobbys) { 
        this.hobbys = hobbys; 
    }
    public void setCard(Map<String, String> card) { 
        this.card = card; 
    }
    public void setGames(Set<String> games) { 
        this.games = games; 
    }
    public void setWife(String wife) { 
        this.wife = wife; 
    }
    public void setInfo(Properties info) { 
        this.info = info; 
    }
    public void show(){ 
        System.out.println("name="+ name 
                           + ",address="+ address.getAddress() 
                           + ",books=" 
                          );
        for (String book:books){ 
            System.out.print("<<"+book+">>\t"); 
        }
        System.out.println("\n爱好:"+hobbys); 
        System.out.println("card:"+card); 
        System.out.println("games:"+games); 
        System.out.println("wife:"+wife); 
        System.out.println("info:"+info); 
    } 
}

# 常量注入

<bean id="student" class="com.kuang.pojo.Student"> 
  <property name="name" value="小明"/> 
</bean>

测试:

@Test
public void test01(){ 
    ApplicationContext context = new 
        ClassPathXmlApplicationContext("applicationContext.xml"); 
    Student student = (Student) context.getBean("student"); 
    System.out.println(student.getName()); 
}

# Bean 注入

注意点:这里的值是一个引用,ref

<bean id="addr" class="com.kuang.pojo.Address"> 
  <property name="address" value="重庆"/> 
</bean> 
<bean id="student" class="com.kuang.pojo.Student"> 
  <property name="name" value="小明"/> 
  <property name="address" ref="addr"/> 
</bean>

# 数组注入

<bean id="student" class="com.kuang.pojo.Student"> 
  <property name="name" value="小明"/> 
  <property name="address" ref="addr"/> 
  <property name="books"> 
    <array> 
      <value>西游记</value> 
      <value>红楼梦</value> 
      <value>水浒传</value> 
    </array> 
  </property> 
</bean>

# List 注入

<property name="hobbys"> 
  <list>
    <value>听歌</value> 
    <value>看电影</value> 
    <value>爬山</value> 
  </list> 
</property>

# Map 注入

<property name="card"> 
  <map>
    <entry key="中国邮政" value="456456456465456"/> 
    <entry key="建设" value="1456682255511"/> 
  </map> 
</property>

# set 注入

<property name="games"> 
  <set>
    <value>LOL</value> 
    <value>BOB</value> 
    <value>COC</value> 
  </set> 
</property>

# Null 注入

<property name="wife">
  <null/>
</property>

# Properties 注入

<property name="info"> 
  <props> 
    <prop key="学号">20190604</prop> 
    <prop key="性别"></prop> 
    <prop key="姓名">小明</prop> 
  </props> 
</property>

测试结果:
image.png

# 拓展注入实现

User.java : 【注意:这里没有有参构造器!】

public class User { 
    private String name; 
    private int age; 
    public void setName(String name) { 
        this.name = name; 
    }
    public void setAge(int age) { 
        this.age = age; 
    }
    @Override 
    public String toString() { 
        return "User{" + 
            "name='" + name + '\'' + 
            ", age=" + age + 
            '}'; 
    } 
}

# P 命名空间注入:需要在头文件中假如约束文件

导入约束 : xmlns:p="http://www.springframework.org/schema/p" 
<!--P (属性: properties) 命名空间,属性依然要设置 set 方法 --> 
<bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>

# c 命名空间注入:需要在头文件中假如约束文件

导入约束 : xmlns:c="http://www.springframework.org/schema/c" 
<!--C (构造: Constructor) 命名空间,属性依然要设置 set 方法 --> 
<bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>

发现问题:爆红了,刚才我们没有写有参构造!
解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入!
测试代码:

@Test
public void test02(){ 
    ApplicationContext context = new 
        ClassPathXmlApplicationContext("applicationContext.xml"); 
    User user = (User) context.getBean("user"); 
    System.out.println(user); 
}

# Bean 的作用域

在 Spring 中,那些组成应用程序的主体及由 Spring IoC 容器所管理的对象,被称之为 bean。简单地讲,
bean 就是由 IoC 容器初始化、装配及管理的对象 .
image.png
几种作用域中,request、session 作用域仅在基于 web 的应用中使用(不必关心你所采用的是什么 web
应用框架),只能用在基于 web 的 Spring ApplicationContext 环境。

# Singleton

当一个 bean 的作用域为 Singleton,那么 Spring IoC 容器中只会存在一个共享的 bean 实例,并且所有对
bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回 bean 的同一实例。Singleton 是单例类型,就是
在创建起容器时就同时自动创建了一个 bean 的对象,不管你是否使用,他都存在了,每次获取到的对象
都是同一个对象。注意,Singleton 作用域是 Spring 中的缺省作用域。要在 XML 中将 bean 定义成
singleton,可以这样配置:

<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">

测试:

@Test
public void test03(){ 
    ApplicationContext context = new 
        ClassPathXmlApplicationContext("applicationContext.xml"); 
    User user = (User) context.getBean("user"); 
    User user2 = (User) context.getBean("user"); 
    System.out.println(user==user2); 
}

# Prototype

当一个 bean 的作用域为 Prototype,表示一个 bean 定义对应多个对象实例。Prototype 作用域的 bean 会
导致在每次对该 bean 请求(将其注入到另一个 bean 中,或者以程序的方式调用容器的 getBean () 方法)
时都会创建一个新的 bean 实例。Prototype 是原型类型,它在我们创建容器的时候并没有实例化,而是
当我们获取 bean 的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经
验,对有状态的 bean 应该使用 prototype 作用域,而对无状态的 bean 则应该使用 singleton 作用域。在
XML 中将 bean 定义成 prototype,可以这样配置:

<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/> 
或者 
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>

# Request

当一个 bean 的作用域为 Request,表示在一次 HTTP 请求中,一个 bean 定义对应一个实例;即每个 HTTP
请求都会有各自的 bean 实例,它们依据某个 bean 定义创建而成。该作用域仅在基于 web 的 Spring
ApplicationContext 情形下有效。考虑下面 bean 定义:

<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>

针对每次 HTTP 请求,Spring 容器会根据 loginAction bean 的定义创建一个全新的 LoginAction bean 实
例,且该 loginAction bean 实例仅在当前 HTTP request 内有效,因此可以根据需要放心的更改所建实例
的内部状态,而其他请求中根据 loginAction bean 定义创建的实例,将不会看到这些特定于某个请求的
状态变化。当处理请求结束,request 作用域的 bean 实例将被销毁。

# Session

当一个 bean 的作用域为 Session,表示在一个 HTTP Session 中,一个 bean 定义对应一个实例。该作用域
仅在基于 web 的 Spring ApplicationContext 情形下有效。考虑下面 bean 定义:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

针对某个 HTTP Session,Spring 容器会根据 userPreferences bean 定义创建一个全新的
userPreferences bean 实例,且该 userPreferences bean 仅在当前 HTTP Session 内有效。与 request 作
用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的 HTTP Session 中根据
userPreferences 创建的实例,将不会看到这些特定于某个 HTTP Session 的状态变化。当 HTTP Session
最终被废弃的时候,在该 HTTP Session 作用域内的 bean 也会被废弃掉。

# Bean 自动装配

  • 自动装配是 Spring 满足 bean 依赖一种方式!
  • Spring 会在上下文中自动寻找,并自动给 bean 装配属性!

在 Spring 中有三种自动装配的方式

  1. 在 xml 中显示配置
  2. 在 java 中显示配置
  3. 隐式 的自动装配 bean 【重要】

# 测试

环境搭建:一个人有两个宠物

# ByName 自动装配

<!--
byName: 会自动在容器上下文中查找,和自己对象 set 方法的值对应的 bean id!
-->
<bean id="person" class="com.pretend.entity.Person" autowire="byName">
  <property name="name" value="小明"/>
</bean>

# ByType 自动装配

<bean class="com.pretend.entity.Cat"/>
<bean class="com.pretend.entity.Dog"/>
<!--
byType: 会自动在容器上下文中查找,和自己对象属性类型对应的 bean!
-->
<bean id="person" class="com.pretend.entity.Person" autowire="byType">
  <property name="name" value="小明"/>
</bean>

小结:

  • byname 的时候,需要保证所有 bean 的 id 唯一,并且这个 bean 需要和自动注入的属性的 set 方法的值一致!
  • bytype 的时候,需要保证所有 bean 的 class 唯一,并且这个 bean 需要和自动注入的属性的类型一致!

# 使用注解实现自动装配

jdk1.5 支持的注解,Spring2.5 就支持注解了!
The introduction of annotation-based configuration raised the question of whether this approach is "better" than XML.
要使用注解须知:

  1. 导入约束:context 约束
  2. 配置注解的支持:context:annotation-config
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
  <context:annotation-config/>
  
</beans>

@Autowired
直接在属性上使用即可!也可在 set 方法上使用!
使用 Autowired 我们可以不用编写 set 方法了,前提是你这个自动装配的属性在 IOC(Spring)容器中存在,且符合名字 byName!
科普:

@Nullable 字段标记了这个注解,说明这个字段可以为null
public @interface Autowired {
    boolean required() default true;
}

测试代码:

public class Person {
    // 如果显示定义了 Autowired 的 required 为 false,说明这个值可以为 null,否则不允许为空
    @Autowired(required = false)
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;
}

如果 @Autowired 自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候、我们可以使用 @Qualifier(value="xxx")去配置 @Autowired 的使用,指定一个唯一的 bean 对象注入!|

public class Person {
    @Autowired
    @Qualifier(value = "cat11")
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog222")
    private Dog dog;
    private String name;
}

@Resource 注解

public class Person {
    @Resource(name = "cat1")
    private Cat cat;
    @Resource
    private Dog dog;
    private String name;
}

小结:
@Resource 和 @Autowired 的区别:
都是用来自动装配的,都可以放在属性字段上
@Autowired 通过 byType 的方式实现,而且必须要求这个对象存在!【常用】
@Resource 默认通过 byname 的方式实现,如果找不到名字,则通过 byType 实现!如果两个都找不到的情况下,就报错!【常用】
执行顺序不同:@Autowired 通过 byType 的方式实现。

# 使用注解开发

在 Spring4 之后,要使用注解开发,必须要保证 aop 的包导入了
image.png
使用注解需要导入 context 约束,增加注解的支持

  1. bean
  2. 属性如何注入
@Component
public class User {
    
    public String name;
    // 相当于 <property name="name"value="小明"/>
    @Value("小明")
    public void setName(String name) {
        this.name = name;
    }
}
  1. 衍生的注解
  • @Component 有几个衍生注解,我们在 web 开发中,会按照 mvc 三层架构分层!
    • dao【@Repository】
    • service【@Service】
    • controller 【@Controller】
      这四个注解功能都是一样的,都是代表将某个类注册到 Spring 中,装配 Bean
  1. 自动装配置
- @Autowired:自动装配通过类型。名字如果Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value="xxx")
- @Nullable字段标记了这个注解,说明这个字段可以为null;
- @Resource:自动装配通过名字。类型。
  1. 作用域
@Component
@Scope("prototype")
public class User {
    public String name;
    @Value("小明")
    public void setName(String name) {
        this.name = name;
    }
}
  1. 小结

xml 与注解

  • xml 更加万能,适用于任何场合!维护简单方便
  • 注解不是自己类使用不了,维护相对复杂!

xml 与注解最佳实践:

  • xml 用来管理 bean;
  • 注解只负责完成属性的注入;
  • 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持
<!-- 指定要扫描的包,这个包下的注解就会生效 -->
<context:component-scan base-package="com.pretend"/>
<context:annotation-config/>

# 注解说明

  • @Autowired:自动装配通过类型。名字如果 Autowired 不能唯一自动装配上属性,则需要通过 @Qualifier(value="xxx")
  • @Nullable 字段标记了这个注解,说明这个字段可以为 null;
  • @Resource:自动装配通过名字。类型。

  • @Component:组件,放在类上,说明这个类被 Spring 管理了,就是 bean!

# 使用 Java 的方式配置 Spring

我们现在要完全不使用 Spring 的 xml 配置了,全权交给 Java 来做!
JavaConfig 是 Spring 的一个子项自,在 Spring 4 之后,它成为了一个核心功能!
image.png

实体类

public class User {
    private String name;
    public String getName() {
        return name;
    }
    @Value("小明") // 属性注入值
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

配置文件

package com.pretend.config;
import com.pretend.entity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
 * @author Pretend
 * @date 2022-09-13 17:30
 */
// 这个也会 Spring 容器托管,注册到容器中,因为他本来就是一个 @Component
// @Configuration 代表这是一个配置类,就和我们之前看的 beans.xml 一样
@Configuration
@ComponentScan("com.pretend.entity")
@Import(MyConfig2.class)
public class MyConfig {
    // 注册一个 bean,就相当于我们之前写的一个 bean 标签
    // 这个方法的名字,就相当于 bean 标签中的 id 属性
    // 这个方法的返回值,就相当于 bean 标签中的 class 属性
    @Bean
    public User user() {
        return new User(); // 就是返回要注入到 bean 的对象!
    }
}

测试类

public class MyTest {
    public static void main(String[] args) {
        // 如果完全使用了配置类方式去做,我们就只能通过 AnnotationConfig 上下文来获取容器,通过配置类的 class 对象加载!
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        User user = context.getBean("user", User.class);
        System.out.println(user.getName());
    }
}

这种纯 Java 的配置方式,在 SpringBoot 中随处可见!

# 代理模式

为什么要学习代理模式?因为这就是 SpringAOP 的底层!【SpringAOP 和 SpringMVC】
代理模式的分类:

  • 静态代理
  • 动态代理

image.png

# 静态代理

角色分析:

  • 抽象角色:一般会使用接口或抽象类来解决
  • 真实角色:被代理角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理对象的人!

代码步骤:

  1. 接口
/**
 * 租房
 * @author Pretend
 * @date 2022-09-13 19:22
 */
public interface Rent {
    void rent();
}
  1. 真实角色
/**
 * 房东
 * @author Pretend
 * @date 2022-09-13 19:23
 */
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东要出租房子!");
    }
}
  1. 代理角色
/**
 * 代理角色
 * @author Pretend
 * @date 2022-09-13 19:25
 */
public class Proxy implements Rent {
    private Host host;
    public Proxy() {
    }
    public Proxy(Host host) {
        this.host = host;
    }
    @Override
    public void rent() {
        seeHouse();
        host.rent();
        hetong();
        fare();
    }
    // 看房
    public void seeHouse() {
        System.out.println("中介带你看房!");
    }
    // 签合同
    public void hetong() {
        System.out.println("签租赁合同!");
    }
    // 收中介费
    public void fare() {
        System.out.println("收中介费!");
    }
}
  1. 客户端访问代理角色
/**
 * @author Pretend
 * @date 2022-09-13 19:24
 */
public class Client {
    public static void main(String[] args) {
        // 房东要租房子
        Host host = new Host();
        // 代理,中介帮房东租房子,但是呢?代理角一般会有一些附属操作!
        Proxy proxy = new Proxy(host);
        // 你不用面对房东,直接找中介租房即可!
        proxy.rent();
    }
}

代理模式的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共也就就交给代理角色!实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!

缺点:

  • 一个真实角色就会产生一个代理角色;代码量会翻倍开发效率会变低

# 加深理解

代码 08-demo2
image.png

# 动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的!
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口 ---JDK 动态代理
    • 基于类:cglib
    • java 字节码实现:javasist

需要了解两个类:Proxy:代理、InvocationHandler:调用处理程序

动态代理的好处:

  • 可以使真实角色的操作更加纯粹!
  • 不用去关注一些公共的业务公共也就就交给代理角色!实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可!|

# Spring AOP

# 概述

Spring 框架的关键组件之一是面向方面编程 (AOP)。 面向方面的编程需要将程序逻辑分解成不同的部分。 跨越应用程序的多个点的功能被称为交叉切割问题,这些交叉关切在概念上与应用程序的业务逻辑分开。有如:日志记录,审计,声明式事务,安全性和缓存等方面的各种常见的的例子。

AOP 是依赖动态代理实现的,具体步骤:先把业务代码中的共性内容抽离出来,然后通过动态代理的方式把抽离出来的共性代码组织入业务代码中来实现对原有代码的增强处理。

image.png

# AOP 里面的几个概念

1. 切面(Aspect)
官方的抽象定义为 “一个关注点的模块化,这个关注点可能会横切多个对象”。“切面” 在 ApplicationContext 中 aop:aspect 来配置。连接点(Joinpoint) :程序执行过程中的某一行为,例如,MemberService.get 的调用或者 MemberService.delete 抛出异常等行为。
2、通知(Advice)
“切面” 对于某个 “连接点” 所产生的动作。其中,一个 “切面” 可以包含多个 “Advice”。
3、切入点(Pointcut)
匹配连接点的断言,在 AOP 中通知和一个切入点表达式关联。切面中的所有通知所关注的连接点,都由切入点表达式来决定。
4、目标对象(Target Object)
被一个或者多个切面所通知的对象。当然在实际运行时,SpringAOP 采用代理实现,实际 AOP 操作的是 TargetObject 的代理对象。
5、AOP 代理(AOP Proxy)
在 Spring AOP 中有两种代理方式,JDK 动态代理和 CGLib 代理。默认情况下,TargetObject 实现了接口时,则采用 JDK 动态代理,反之,采用 CGLib 代理。强制使用 CGLib 代理需要将 aop:config 的 proxy-target-class 属性设为 true。
通知类型:
6、前置通知(Before Advice)
在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext 中在 aop:aspect 里面使用 aop:before 元素进行声明。
7、后置通知(After Advice)
当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext 中在 aop:aspect 里面使用 aop:after 元素进行声明。
8、返回后通知(After Return Advice)
在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext 中在 aop:aspect 里面使用 <after-returning> 元素进行声明。
9、环绕通知(Around Advice)
包围一个连接点的通知,类似 Web 中 Servlet 规范中的 Filter 的 doFilter 方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext 中在 aop:aspect 里面使用 aop:around 元素进行声明。例如,ServiceAspect 中的 around 方法。
10、异常通知(After Throwing Advice)
在 方 法 抛 出 异 常 退 出 时 执 行 的 通 知 。 ApplicationContext 中 在 aop:aspect 里 面 使 用 aop:after-throwing 元素进行声明。

# AOP 相关术语

Aspect(切面)
Advice(增强处理)
Join Point (连接点)
Pointcut(切入点)
Target Object(目标对象)
AOP proxy(AOP 代理)
Weaving(织入)

未完待续...

更新于 阅读次数