⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7120430443173773326 「nuzzzz」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

1. 为啥要用 Spring

张三是一个编程小白,他每次在 service 层写代码都要自己 new 一堆 Dao 接口的实现类。

public class ProjectServiceImpl implements ProjectService {
UserDao userDao = new UserDaoImpl();
ProjectSectionDao projectSessionDao = new ProjectSessionDaoImpl();
ProjectDao projectDao = new ProjectDaoImpl();
SupplyDao supplyDao = new SupplyDaoImpl();
.......
}

有一天正 new 着对象,张三心想:"我这一个 service 都需要 new 好多 Dao ,那如果有一堆 service ,那我不得花费好长时间?"

"有没有一个工具类或者什么框架能帮我管理这些对象?我只需要配置一下,需要的时候它就能自动帮我 new 个对象出来?"

张三陷入了深深的沉思之中。

张三的室友李四也是一个编程小白。

李四呢想给自己的小项目增加一个功能:记录方法执行的时间。结果他脑子一热竟然给所有的方法都增加了一堆打印方法:

System.out.println("项目开始执行");
// 开始时间
long start = System.currentTimeMillis();

// 业务代码

// 结束时间
long end = System.currentTimeMillis();
// 计算执行时间
System.out.printf("执行时间:%d 毫秒.", (end - start));

过了半个小时,李四终于给项目中所有的方法都复制粘贴上了打印语句。他长舒一口气:"我真是个大聪明!"

张三看了一眼李四的代码,连连鼓掌:"妙啊!咱们宿舍的技术大神!"

旁边的王五实在忍不住了,对张三说:"妙个屁!最近的 Spring 框架课你俩是不是都没去?光顾着打游戏了?我都替你俩答了三次到了!"

李四问王五:"这个Spring 框架学了有用吗?"

王五:"不仅能解决张三说的管理对象的问题,还能帮你解决记录日志的问题。配置完 Spring ,你只需要定义一个切面类,根本不需要在一堆类上面复制粘贴一堆代码。"

张三摸摸后脑勺笑着说:"原来 Spring 框架那么好用,我以后再也不逃课了。我这就去翻课本学习 Spring 框架去。"

2. Spring 简介

Spring 是一个轻量级的 Java 开发框架。Spring 的核心是控制反转(IOC)和面向切面编程(AOP)。

Spring 主要有如下优点:

  • 1.解耦
  • 2.支持面向切面编程
  • 3.便于集成其他框架

3. 环境搭建

1.创建 Maven 项目

File -> New -> Project -> Maven

2.引入依赖

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
</dependencies>

3.创建接口和实现类

UserService

public interface UserService {
void print();
}

UserServiceImpl

public class UserServiceImpl implements  UserService{
@Override
public void print() {
System.out.println("hello world");
}
}

4.创建配置文件

applicationContext.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="userService" class="com.xxl.service.impl.UserServiceImpl"/>
</beans>

5.测试

@Test
public void testSpring(){
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
UserService userService = (UserService)act.getBean("userService");
// 3.调用方法
userService.print();
}

测试结果:

4. IOC

4.1 IOC 简介

IOC,全称 Inversion of Control,意思是控制反转。它是 Spring 框架中的一种思想。

控制反转就是将对象的控制权从程序中的代码转移到了 Spring 的工厂,通过 Spring 的工厂完成对象的创建以及赋值。

也就是说之前是我们自己 new 对象、给对象中的成员变量赋值。现在是让 Spring 来帮助我们创建对象、给成员变量赋值。

4.2 Spring 核心内容描述

1.配置文件

Spring 的配置文件可以放到项目中的任意一个地方,也可以随意命名,但是建议使用:applicationContext.xml。

你可以将这个配置文件看成一个装有一堆 bean 标签的容器。

2.bean 标签

Spring 工厂创建的对象,叫做 bean,所以一个 bean 标签代表一个对象。

<bean id="userService" class="com.xxl.service.impl.UserServiceImpl"/>

bean 标签中必须要有 class 属性,它的值是一个类的全限定名(包名+类名)。

除了 class 属性,bean 标签还可以设置 id 、name 、scope属性。

id:

(1)id 必须以字母开头,相当于这个 bean 的身份证号,是唯一的。

(2)如果这个 bean 只使用一次,id 可以省略不写。

(3)如果这个 bean 需要被其他 bean 引用,或者这个 bean 要使用很多次,则必须要有 id 属性。

(4)如果只配置 class 属性,Spring 框架会给每一个 bean 配置一个默认的 id:"全限定名#1"。

例如:

com.xxl.service.impl.UserServiceImpl#1

name:

name 相当于这个 bean 的别名,它可以配置多个,例如:

<bean id="user" name="aa,bb,cc" class="com.xxl.model.User"/>

scope:

scope 属性可以控制简单对象的创建次数,它有两个值:

1.singleton:每次只会创建唯一⼀个简单对象,默认值。

2.prototype:每⼀次都会创建新的对象。

例如:

<bean id="user" class="com.xxl.model.User" scope="singleton"/>

3.ApplicationContext

ApplicationContext 是 Spring 的工厂,主要用来创建对象。

Spring 通过读取配置文件创建工厂。

因为 Spring 的工厂会占用大量内存,所以一个程序一般只会创建一个工厂对象。

4.工厂常用方法

(1)根据 id 获取对象

UserService userService = (UserService)act.getBean("userService");

(2)根据 id 和类名获取对象

UserService userService = (UserService)act.getBean("userService",UserService.class);

(3)只根据类名获取对象

UserService userService = (UserService)act.getBean(UserService.class);

(4)获取配置文件中所有 bean 标签的 id 值

String[] beanDefinitionNames = act.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}

结果:

(5)判断是否存在指定 id 或者 name 的 bean

act.containsBean("userService")

(6)判断是否存在指定 id 的 bean,只能用来判断 id

act.containsBeanDefinition("userService")

5.创建对象

Spring 是如何创建对象的呢?

工厂和反射

首先说下反射,我们可以通过一个类的全限定名获取 Class 对象,然后再通过 Class 实例化一个对象:

Class serviceClass = Class.forName("com.xxl.service.impl.UserServiceImpl");
UserService userService = (UserService)serviceClass.newInstance();

Spring 配置文件中 bean 标签的 id 和类的全限定名一一对应,所以 Spring 工厂的 getBean 方法其实就是先根据 bean 的 id 获取该类的全限定名,然后再利用反射根据类的全限定名创建对象并返回。

4.3 IOC 优点

解耦

说起解耦之前先说下耦合:耦合是指代码之间的关联性太强,我如果改了这一段代码,可能会影响到一堆代码。

那创建对象哪里有耦合了?其实就是new关键字带来的耦合。

如果你发现一个接口的实现类需要修改,你需要手动改动程序中的代码,比如修改 new 关键字后面的实现类,这样可能会影响到其他的代码。

但是使用了 Spring 之后,我们只需要修改配置文件中 bean 标签的 class 属性对应的类的全限定名,不用修改程序中的代码,这样就做到了解耦。

解耦就是解除不同代码之间的关联性、依赖性。

5. DI

DI 全称 Dependency Injection,意思是依赖注入,它是 IOC 的具体实现。

依赖就是说我需要你,比如 Service 层依赖 Dao 层,注入就是赋值

依赖注入:使用 Spring 的工厂和配置文件为一个类的成员变量赋值。

没有使用 Spring 的依赖注入我们是这样赋值的:

User user = new User();
user.setName("张三");

如果设置有误,就需要手动修改代码,代码耦合度较高,而依赖注入的出现就是为了解耦。

Spring 的依赖注入包含两种方式:

5.1 set 注入

set 注入:Spring 调用 Set 方法通过配置文件为成员变量赋值。

1.创建对象,为属性添加 set/get 方法

public class User {
private String name;
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

2.修改配置文件

<bean id="user" class="com.xxl.model.User">
<property name="name" value="知否君" />
<property name="age" value="18" />
</bean>

3.测试

// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
User user = (User)act.getBean("user");
System.out.println("姓名:"+user.getName());
System.out.println("性别:"+user.getAge());

测试结果:

从上面的例子可以看出 Set 注入就是在 property 标签中为属性赋值。spring 可以为 JDK 内置的数据类型进行赋值,也可以为用户自定义的数据类型进行赋值。

5.1.1 JDK 内置数据类型

1.基本类型

<property name="name" value="知否君" />
<property name="age" value="18" />

2.List 集合

<property name="phones">
<list>
<value>15799999918</value>
<value>15788888819</value>
<value>15766666620</value>
</list>
</property>

3.Set 集合

<property name="phones">
<set>
<value>15799999918</value>
<value>15788888819</value>
<value>15766666620</value>
</set>
</property>

4.Map 集合

<property name="mapInfo">
<map>
<entry>
<key><value>name</value></key>
<value>知否君</value>
</entry>
<entry>
<key><value>age</value></key>
<value>23</value>
</entry>
</map>
</property>

5.数组

<property name="phones">
<list>
<value>15799999918</value>
<value>15788888819</value>
<value>15766666620</value>
</list>
</property>

6.Properites

<property name="prop">
<props>
<prop key="key1">value1</prop>
<prop key="key2">value2</prop>
</props>
</property>

5.1.2 用户自定义数据类型

1.为成员变量添加 set/get 方法

public class UserServiceImpl implements UserService {

private UserDao userDao;

public UserDao getUserDao() {
return userDao;
}

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void print() {
userDao.print();
}
}

2.bean 标签使用 ref 属性

<bean id="userDao" class="com.xxl.dao.impl.UserDaoImpl" />
<bean id="userService" class="com.xxl.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>

3.测试

@Test
public void testSpring(){
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
UserService userService = (UserService)act.getBean("userService");
// 3.调用方法
userService.print();
}

测试结果:

解释:

上面的例子中,因为 userDao 是 userService 的一个成员变量,所以在配置文件中需要使用 property 标签,ref 指向了 userDao 这个对象,然后调用 userDao 的 set 方法为 userDao 赋值。

4.自动注入

我们还可以使用 bean 标签的 autowire 属性为自定义变量自动赋值。当类中引用类型的属性名和 bean 标签的 id 值相同时,我们可以使用 byName。例如:

<bean id="userDao" class="com.xxl.dao.impl.UserDaoImpl" />

<bean id="userService" autowire="byName" class="com.xxl.service.impl.UserServiceImpl" />

当类中引用类型的全限定名和 bean 标签的 class 属性的值相同,或者是子类、实现类,我们可以使用 byType。例如:

<bean id="userDao" class="com.xxl.dao.impl.UserDaoImpl" />

<bean id="userService" autowire="byType" class="com.xxl.service.impl.UserServiceImpl" />

5.2 构造注入

构造注入:Spring 调用构造方法通过配置文件为成员变量赋值。

1.为类添加构造方法

public class User {
private String name;
private int age;

public User(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

2.修改配置文件

在 bean 标签中使用 constructor-arg 标签。

<bean class="com.xxl.model.User">
<constructor-arg value="张三"/>
<constructor-arg value="18"/>
</bean>

3.测试

@Test
public void testSpring(){
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
User user= (User)act.getBean(User.class);
System.out.println("姓名:"+user.getName());
System.out.println("年龄:"+user.getAge());
}

测试结果:

5.3 注入总结

注入就是通过 Spring 的配置文件为类的成员变量赋值。在实际开发中,我们一般采用 Set 方式为成员变量赋值。

6. Bean 的生命周期

Bean 生命周期指的就是由 Spring 管理的对象从创建到销毁的过程,和人生老病死的过程一样。

它主要分为三个阶段:创建 --> 初始化 --> 销毁

6.1 创建阶段

Spring 工厂创建对象的方式分两类:

  1. singleton 模式

当 scope 属性为 singleton ,创建 Spring 工厂的同时创建所有单例对象。

例如:

新建 User 类:

public class User {
String name;
int age;

public User() {
System.out.println("调用User的构造方法");
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}

spring 配置文件注册 bean :

<bean id="user" class="com.xxl.model.User">
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>

测试:

@Test
public void testSpring(){
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
}

执行结果:

我们发现当创建 Spring 工厂的同时就会调用对象的构造方法。因为 spring 中 bean 默认的 scope 就是 singleton ,所以创建工厂的同时默认就会创建多个单例对象。

如果想修改创建单例对象的方式为获取的时候才创建,只需要在 bean 标签上面添加如下属性:

lazy-init="true"

例如:

<bean id="user" class="com.xxl.model.User" lazy-init="true">
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>

  1. prototype 模式

只有获取对象的时候才会创建对象。

修改 bean 标签的 scope 属性:

<bean id="user" class="com.xxl.model.User" scope="prototype">
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>

测试:

@Test
public void testSpring(){
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
Object user = act.getBean("user");
System.out.println(user);
}

执行结果:

通过上面的例子我们发现只有当执行 getBean() 方法的时候才会调用该类的构造方法。

6.2 初始化阶段

spring 中 bean 的初始化操作指的是在创建对象的时候完成一些附加的功能。bean 的初始化操作有两种实现方式:

1.实现 InitializingBean 接口

public class 类名 implements InitializingBean {
public void afterPropertiesSet(){
// 初始化方法操作
}
}

例如:

public class User implements InitializingBean {
String name;
int age;
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
'}';
}

// 初始化操作
@Override
public void afterPropertiesSet(){
this.name = "张无忌";
this.age = 30;
}
}

测试:

@Test
public void testSpring(){
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
Object user = act.getBean("user");
System.out.println(user);
}

执行结果:

2.通过创建普通方法完成初始化

在 User 类中创建一个方法

// 初始化方法
public void initMethod() {
this.name = "张无忌";
}

在配置文件中配置 init-method 属性

<bean id="user" class="com.xxl.model.User" init-method="initMethod" >
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>

测试:

@Test
public void testSpring(){
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
Object user = act.getBean("user");
System.out.println(user);
}

执行结果:

我们发现该初始化方法在创建对象之后修改了 user 对象的名字。

总结

初始化方法修改了注入的值,所以初始化方法一定在注入之后执行。

6.3 销毁阶段

Spring 销毁对象前,会调用对象的销毁方法,完成销毁操作。

Spring 什么时候销毁所创建的对象?当 Spring 工厂关闭时,Spring 工厂会调用我们自定义的销毁方法。

销毁方法的定义有两种方式:

1.实现DisposableBean接口

public class 类名 implements DisposableBean {
// 销毁操作
@Override
public void destroy(){
// 销毁操作业务
}
}

2.创建普通方法

在 User 类中创建一个方法

// 销毁方法
public void destroyMethod() {
// 销毁操作业务
}

在配置文件中配置 destroy-method 属性

<bean id="user" class="com.xxl.model.User" destroy-method="destroyMethod">
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>

7. Bean 的后置处理

Spring 工厂创建完对象后如果还想对对象干点别的事情,除了初始化阶段,还可以采用Bean的后置处理。

Bean 的后置处理:对 Spring 工厂创建的对象进行二次加工处理,就是创建完对象后再干点别的事。

Bean 后置处理的流程:

1.实现 BeanPostProcessor 接口

public class BeanProcessor implements BeanPostProcessor {

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置bean:before 方法");
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置bean:after 方法");
if (bean instanceof User) {
User user = (User) bean;
user.setName("亚里士多德");
return user;
}
return bean;
}
}

2.配置文件添加 bean

<bean id="beanProcessor" class="com.xxl.config.BeanProcessor"/>

3.测试

@Test
public void testSpring(){
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
Object user = act.getBean("user");
System.out.println(user);
}

执行结果:

前面我们学习了对象的初始化方法,那么初始化方法和 Bean 的后置处理的执行顺序是什么?

我们来修改一下 User 类,测试一下:

public class User implements InitializingBean {
String name;
int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
'}';
}

@Override
public void afterPropertiesSet(){
System.out.println("初始化方法");
}
}

测试执行顺序:

其实在实际开发中,我们很少对 Spring 工厂创建的对象进行初始化操作,一般是采用 Bean 的后置处理的方式来加工对象。

BeanPostProcessor 接口有两个方法,这里简称 before 和 after 方法。

这两个方法都是先获取 Spring 创建的对象,然后再对其加工,加工完成后再交给 Spring。

因为这两个方法的作用一样,所以我们一般采用其中的一个方法,这里建议采用 after 方法。

从上面的例子中我们得到了Spring 操作 bean 的顺序:

8. 代理设计模式

8.1 为啥要用代理设计模式?

咱们先来看一个需求:在所有方法的执行前后输出一段日志。

程序小白可能会这样写:

接口:

public interface CalculateService {
// 加法
int add(int a,int b);
// 减法
int sub(int a,int b);
}

实现类:

public class CalculateServiceImpl implements CalculateService {
@Override
public int add(int a, int b) {
System.out.println("方法执行前打印");
int result = a + b;
System.out.println("方法执行后打印");
return result;
}

@Override
public int sub(int a, int b) {
System.out.println("方法执行前打印");
int result = a - b;
System.out.println("方法执行后打印");
return result;
}
}

但是这样写有 3 个问题:

  • 1.代码混乱:业务代码和非业务代码混在一起,看着太乱了

  • 2.代码重复:如果有多个方法,那就要写一堆输出日志的代码片段,吃力不讨好。

  • 3.代码耦合:如果非业务代码(日志打印)要做修改,那所有相关的业务方法都要改一遍,代码耦合度太高。

那有什么解决办法呢?使用代理

生活中有关代理的例子无处不在,例如:一些大学可以面向全球招生,所以会衍生很多留学中介,这些中介可以帮学校招生。

所以中介的作用就是帮助雇主做事,有了中介,雇主变得很轻松。而在 java 开发中,也存在这样的代理关系,它的专业术语是代理设计模式。

代理设计模式可以很好解决上面开发中遇到的三个问题,帮助我们简化代码、提高工作效率。

8.2 代理设计模式

代理设计模式:通过代理类为目标类做一些额外(非业务)的功能。

专业名词解释:

  • 1.目标类(原始类):指的是完成业务的核心类,一般指的是 service 层的各种实现类。

  • 2.目标方法(原始方法):目标类中的方法是目标方法(原始方法)。

  • 3.额外功能(附加功能):打印日志等非业务功能。

代理设计模式开发步骤:

    1. 代理类和目标类实现相同的接口
    1. 代理类中除了要调用目标类的方法实现业务功能,还要实现额外功能。

例如:

// 接口
public interface CalculateService {
业务方法
}

// 目标类
public CalculateServiceImpl implements CalculateService {
业务方法
}

// 代理类:要实现目标类相同的接口
public CalculateServiceProxy implements CalculateService {
// 业务方法
// 额外功能
}

8.3 静态代理

静态代理:给每一个目标类手动开发一个代理类。

例如:

public interface CalculateService {
// 加法
int add(int a,int b);
// 减法
int sub(int a,int b);
}
// 目标类
public CalculateServiceImpl implements CalculateService {
@Override
public int add(int a, int b) {
int result = a + b;
return result;
}
@Override
public int sub(int a, int b) {
int result = a - b;
return result;
}
}
// 代理类:要实现目标类相同的接口
public CalculateServiceProxy implements CalculateService {
private CalculateService calculateService = new CalculateServiceImpl();
@Override
public int add(int a, int b) {
System.out.println("方法执行前打印");
int result = calculateService.add(a,b);
System.out.println("方法执行后打印");
return result;
}
@Override
public int sub(int a, int b) {
System.out.println("方法执行前打印");
int result = calculateService.sub(a,b);
System.out.println("方法执行后打印");
return result;
}
}

通过上面的例子我们发现静态代理也存在很多问题:

  • 1.如果存在很多目标类,我们就要手动创建一堆代理类,太繁琐。

  • 2.代理类中混杂着目标类方法和额外功能,代码耦合度高。

那有没有这样一种代理模式?

  • 1.目标类和代理类互不干扰

  • 2.代码耦合度低,便于维护

有的,动态代理闪亮登场!

8.4 动态代理

动态代理:也是通过代理类为目标类做一些额外的功能,但是不用手动写一堆代理类,而是动态地为目标类创建代理类。

开发流程:

  1. 引入依赖

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>

这里我们主要是引入了 aspectj 这个技术,aspectj 是 spring 社区中非常流行的基于动态代理技术的框架。

  1. 创建目标类和目标方法

接口:

public interface CalculateService {
// 加法
int add(int a,int b);
// 减法
int sub(int a,int b);
}

实现类(目标类):

public class CalculateServiceImpl implements CalculateService {
@Override
public int add(int a, int b) {
int result = a + b;
System.out.println("加法操作。。。");
return result;
}

@Override
public int sub(int a, int b) {
int result = a - b;
System.out.println("减法操作。。。");
return result;
}
}

  1. 在 spring 配置文件中注册 bean

<bean id="calculateService"  class="com.xxl.service.impl.CalculateServiceImpl" />

  1. 实现额外功能

这里我们需要创建一个类实现 MethodInterceptor 接口:

/**
* @Desc: 动态代理完成非业务功能
* @Author: 知否技术
* @date: 下午8:49 2022/5/4
*/
public class PrintLog implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("在目标方法执行之前打印。。。。。");
// 执行目标方法
Object object = methodInvocation.proceed();
System.out.println("在目标方法执行之后打印。。。。。");
return object;
}
}

  1. 注册完成额外功能的 bean

<bean id="printLog" class="com.xxl.aop.PrintLog" />

  1. 定义切入点

<!--切入点:给哪些方法加入额外功能-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* * (..))"/>
</aop:config>

  1. 组装切入点和额外功能

<!--切入点:给哪些方法加入额外功能-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* * (..))"/>
<aop:advisor advice-ref="printLog" pointcut-ref="pc"/>
</aop:config>

  1. 测试

@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
CalculateService calculateService = (CalculateService) act.getBean("calculateService");
// 3.调用方法
int result = calculateService.add(1, 2);
System.out.println("result:" + result);
}

讲解:

1.上面的例子中我们定义了一个 PrintLog 打印日志的类,并实现了 MethodInterceptor 接口的 invoke 方法。invoke 方法里面实现了在目标方法执行前后打印日志的功能。

2.invoke 方法的返回值就是原始方法的返回值,上个例子中的原始方法就是 add 方法。

3.aop:config 这个标签用来配置切入点和额外功能。上面例子中额外功能就是在要执行的方法前后打印日志,而切入点就是额外功能要作用的位置:比如某些类上或者某些方法上。

4.execution(* * (..)) 是切入点表达式,表示作用在所有类的所有方法上,这个后面会讲。

5.上面的例子表示:你无论执行哪个方法,这个方法的前面和后面都会打印一段日志。

8.5 动态代理实现原理

我们通过 spring 的工厂获取的对象,其实是通过动态代理技术创建的代理类。那这个代理类在哪里?

当程序运行的时候,spring 框架通过动态字节码技术在 JVM 内存中为目标类创建代理类。当程序运行结束的时候,这个代理类就会随之消亡。

所以使用动态代理不需要手动创建多个代理类。

9. AOP

9.1 AOP 概念

AOP: 全称 Producer Oriented Programing,即面向切面编程。

那啥是面向切面编程?其实说白了还是 Spring 的动态代理,通过代理类为原始类增加一些 额外功能(例如打印等)。

那啥是切面?

切面 = 切入点 + 额外功能。

切入点:额外功能作用的位置,在哪些类哪些方法上。

额外功能作用在不同的类上面,我们都知道点连接起来构成面,所以不同的切入点连接起来构成了切面,这个切面就像刀切西瓜一样切在不同的类上面,所以额外功能就对这些类中的方法起了作用。

9.2 AOP 底层实现原理

AOP 的底层还是使用 Spring 的动态代理技术创建代理类对象。

动态代理的方式分为两种:

  • 基于接口实现动态代理:JDK 动态代理
  • 基于继承实现动态代理:Cglib 动态代理
9.2.1 JDK 动态代理

创建代理对象的三个元素:

  • 1.原始对象

  • 2.额外功能

  • 3.原始对象实现的接口

代码格式:

Proxy.newPorxyInstance(classloader,interfaces,invocationHandler)

讲解:

(1)classloader:叫做类加载器,它可以用来创建代理对象。

创建方式:

类.class.getClassLOader()

(2)interfaces:原始对象实现的接口

创建方式

接口.getClass().getInterfaces()

(3)invocationHandler:额外功能

创建方式:

InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---- 方法执行前打印 ----");
// 执行原始方法
Object ret = method.invoke(caculateService, args);
System.out.println("---- 方法执行后打印 ----");
return ret;
}
};

完整代码:

@Test
public void testJDKProxy() {
// 1. 原始对象
CalculateService calculateService = new CalculateServiceImpl();

// 2. JDK 动态代理:包含额外功能
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---- 方法执行前打印 ----");
// 执行原始方法
Object result = method.invoke(calculateService, args);
System.out.println("---- 方法执行后打印 ----");
return result;
}
};
// 3. 代理类
CalculateService calService = (CalculateService) Proxy.
newProxyInstance(CalculateService.class.getClassLoader(),
calculateService.getClass().getInterfaces(),
handler);
// 4. 执行方法
int result = calService.add(1, 2);
System.out.println("result:" + result);
}

测试结果:

9.2.2 Cglib 动态代理

CGlib 创建动态代理的原理:原始类作为父类,代理类作为子类,通过继承关系创建代理类。

代码格式:

Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(classLoader);
enhancer.setSuperclass(calculateService);
enhancer.setCallback(interceptor);

讲解:

(1)classLoader:类加载器(了解即可)

(2)Superclass:父类,就是原始类

(3)interceptor:额外功能

MethodInterceptor interceptor = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("---- 方法执行前打印 ----");
// 执行原始方法
Object result = method.invoke(calculateService, args);
System.out.println("---- 方法执行后打印 ----");
return result;
}
};

完整代码:

@Test
public void testCglibProxy() {
// 1. 原始对象
CalculateService calculateService = new CalculateServiceImpl();

// 2. Cglib 动态代理:包含额外功能
MethodInterceptor interceptor = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("---- 方法执行前打印 ----");
// 执行原始方法
Object result = method.invoke(calculateService, args);
System.out.println("---- 方法执行后打印 ----");
return result;
}
};

Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(CalculateService.class.getClassLoader());
enhancer.setSuperclass(calculateService.getClass());
enhancer.setCallback(interceptor);

// 3. 创建代理类
CalculateService calService = (CalculateService)enhancer.create();
// 4. 执行方法
int result = calService.add(3, 4);
System.out.println("result:" + result);
}

执行结果:

9.2.3 Spring 如何创建代理对象?

Spring 是如何为原始对象创建目标对象的呢?是通过 BeanPostProcessor。

前面我们讲过 BeanPostProcessor 可以对对象进行二次加工,所以可以用来创建代理对象。

Spring 创建代理对象的流程:

  1. 实现 BeanPostProcessor 接口

/**
* @Desc: 后置bean创建代理对象
* @Author: 知否技术
* @date: 上午11:59 2022/5/5
*/
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

InvocationHandler handler = (proxy, method, args) -> {
System.out.println("--- 方法执行前打印6666666---");
Object ret = method.invoke(bean, args);
System.out.println("--- 方法执行后打印7777777---");
return ret;
};
return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler);
}
}

  1. 注册 bean

<bean id="calculateService"  class="com.xxl.service.impl.CalculateServiceImpl" />
<bean id="proxyBeanPostProcessor" class="com.xxl.aop.ProxyBeanPostProcessor"/>

  1. 测试

@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
CalculateService calculateService = (CalculateService) act.getBean("calculateService");
// 3.调用方法
int result = calculateService.sub(7, 2);
System.out.println("result:" + result);
}

9.3 基于注解开发 AOP

开发流程:

  1. 开发切面类

@Aspect
public class TestAspect {

// 前置通知:方法执行前添加额外功能
@Before("execution(* *(..))")
public void beforePrint(){
System.out.println("------before: 方法执行前打印~");
}

//后置通知:方法执行后添加额外功能
@After("execution(* *(..))")
public void afterPrint(){
System.out.println("------after: 方法执行前打印~");
}

// 环绕通知:方法执行前后添加额外功能
@Around("execution(* *(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("方法执行前打印~");
Object result = joinPoint.proceed();
System.out.println("方法执行后打印~");
return result;
}
}

  1. 配置切面类和扫描注解

<bean id="testMyAspect"  class="com.xxl.aop.TestAspect" />
<!-- 扫描 aop 相关注解-->
<aop:aspectj-autoproxy/>

  1. 测试

@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
calculateService calculateService = (CalculateService) act.getBean("calculateService");
// 3.调用方法
int result = calculateService.add(100, 1);
System.out.println("result:" + result);
}

讲解:

1.我们新建了一个 TestMyAspect 类,然后添加 @Aspect 注解,表示这是一个切面类,专门用来完成非业务功能的。

2.在这个类中,我们创建了三个方法,其中 @Before 注解标注的方法表示在目标方法操作前执行。@After 注解标注的方法表示在目标方法操作后执行。@Around 注解标注的方法表示在目标方法操作前后执行。

3.在实际开发中一般使用 @Around 注解标注的方法完成非业务功能。

4.我们新建了这个切面类,但是 spring 不知道啊,所以需要在 Spring 的配置文件中注册一下 bean。

5.现在 Spring 工厂能够管理这个类了,但是 Spring 不知道他是切面类啊!所以需要配置一下扫描注解的标签。

6.然后通过 Spring 获取创建的类,我们获取的其实是 Spring 通过后置 Bean 加工后的代理类。

切入点复用

我们可以在切面类中定义⼀个方法,方法上面标注 @Pointcut 注解。然后就可以重复使用切入点表达式了:

@Aspect
public class TestAspect {

@Pointcut("execution(* *(..))")
public void myPointcut() {}

// 环绕通知:方法执行前后添加额外功能
@Around(value = "myPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("方法执行前打印~");
Object result = joinPoint.proceed();
System.out.println("方法执行后打印~");
return result;
}
}

9.4 切入点表达式

切入点:额外功能加入的位置。

<aop:pointcut id="pc" expression="execution(* * (..))"/>

  • execution():切入点函数
  • (* * (..)):切入点表达式

public int add(int a, int b)
* * (..)

第一个 * 表示方法的修饰符和返回值 第二个 * 是方法名 .. 表示方法中的参数

1.(包.类.方法)切入点:

修饰符-返回值 包.类.方法(参数)

expression="execution(* com.xxl.service.caculateServiceImpl.add(..))"

2.指定切入点为某个包下的所有类中的所有方法:

修饰符-返回值 包.类.方法(参数)

expression="execution(* com.xxl.service.*.*(..))"

3.@annotation

作用:用于匹配当前执行方法持有指定注解的方法,并为之加入额外的功能。

例如我们自定义了一个注解:NoToken

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoToken {
}

方法中添加自定义注解:

@Override
@NoToken
public int add(int a, int b) {
int result = a + b;
System.out.println("加法操作。。。");
return result;
}

然后我们要为包含 NoToken 注解的方法加入额外功能:

@Aspect
public class TestAspect {
// 环绕通知:方法执行前后添加额外功能
@Around("@annotation(com.xxl.annotion.NoToken)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("包含 NoToken 注解--------------");
Object result = joinPoint.proceed();
return result;
}
}

测试:

@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
CalculateService calculateService = (CalculateService) act.getBean("calculateService");
// 3.调用方法
int result1 = calculateService.add(99, 1);
System.out.println("-----------------------");
int result2 = calculateService.sub(99, 1);
System.out.println("result1:" + result1);
System.out.println("result2:" + result2);
}

10. Spring 相关注解

在讲注解之前我们先看一下啥是注解。

代码格式:@英文单词,例如:

作用位置:常用在类上或者方法上

用处:简化代码、完成某些功能

所以 Spring 引入注解也是为了简化我们的代码,通过使用简单的注解完成一些功能。

10.1 创建对象相关注解

我们前面在学 IOC 的时候知道如果想让 Spring 创建对象,必须要在配置文件中写 bean 标签。

<bean id="calculateService"  class="com.xxl.service.impl.CalculateServiceImpl" />
<bean id="proxyBeanPostProcessor" class="com.xxl.aop.ProxyBeanPostProcessor"/>
<bean id="testMyAspect" class="com.xxl.aop.TestAspect" />
......

可是如果想让 Spring 管理一堆对象,我们就要写一堆 bean 标签。所以 Spring 为了简化代码,提供了一些与创建对象相关的注解。

10.1.1 @Component

作用:替换 bean 标签,用来创建对象。就是在类上面加了这个注解,就不用在配置文件上写 bean 标签了。

位置:类上面

id 属性:默认首单词首字母小写。

// id 属性:默认首单词首字母小写。
@Component("user")
public class User{

}

10.1.2 @Component 衍生注解

我们在开发程序的时候一般会将程序分层,例如分为控制层(controller),业务层(service),持久层(dao)。

但是 @Component 注解并不能区分这些类属于那些层,所以 Spring 提供了以下衍生注解:

1.@Controller:表示创建控制器对象

@Controller
public class UserController {

}

2.@Service:表示创建业务层对象

@Service
public class UserServiceImpl implements UserService {

}

3.@Repository:表示创建持久层对象

@Repository
public class UserDaoImpl implements UserDao {

}

这三个注解的作用和 @Component 的作用一样,都是用来创建对象。

10.1.3 @Scope

我们知道 Spring 工厂创建的对象默认都是单例的,也就是 bean 标签中 scope 属性默认是 singleton。

@Scope 注解可以用来修改创建对象的 scope 属性。

默认:也就是说你不写 @Scope 注解,默认就是 singleton,所以可以省略。

@Component
// 可以省略不写
@Scope("singleton")
public class User {

}

修改多例:

@Component
@Scope("prototype")
public class User {

}

10.1.4 生命周期相关注解

1.@PostConstruct

初始化方法注解,作用在方法上。用来替换 bean 标签的 init-method 属性。

例如:

@Component
public class User {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
'}';
}
// 初始化方法
@PostConstruct
public void init(){
this.name = "王小波";
}
}

测试:

@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
User user = (User) act.getBean("user");
System.out.println(user);
}

报错了!这是为什么?

我们先看报错内容:

No bean named 'user'

也就是说找不到这个对象。我们虽然加了 @Component 注解,但是 Spring 不知道啊,所以需要在 Spring 的配置文件中配置注解扫描:

<context:component-scan base-package="com.xxl"/>

base-package: 添加注解的类所在的包位置。

配置了注解扫描,当程序启动的时候 Spring 会先扫描一下相关的注解,这些注解才会生效。

就比如你去快递驿站拿快递,你看到了自己的快递然后对快递小哥说:"这我的快递我拿走了啊。"但是人家小哥无法确认是你的啊!所以他需要用扫码枪扫一下才能出货!

我们配置注解扫描之后再次测试:

2.@PreDestory(了解即可)

销毁方法注解,作用在方法上。用来替换 bean 标签的 destory-method 属性。

10.2 注入相关注解

10.2.1 @Autowired

我们之前学 DI 的时候知道:注入就是赋值。

@Autowired 主要是为自定义的类型赋值,例如 service、dao 层的各种类。

@Controller
public class UserController {

@Autowired
private UserService userService;
}
@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserDao userDao;
}

@Autowired 是基于类型进行注入,所注入对象的类型必须和目标 变量类型相同或者是他的子类、实现类。

如果想基于名字注入,可以和 @Qualifier 注解连用:

@Autowired
@Qualifier("orderDAOImpl")
private OrderDAO orderDAO;

10.2.2 @Resource

@Resource 注解是 JAVAEE 规范中提供的注解,他和 @Autowired 注解的作用一样, 但是他是基于名字进行注入:

@Resource("orderDAOImpl")
private OrderDAO orderDAO;

在实际开发中,用 @Autowired 注解比较多一点。

10.2.3 案例

Product 类

@Component
public class Product {

private String productName;

public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}

@Override
public String toString() {
return "Product{" +
"productName='" + productName + ''' +
'}';
}

@PostConstruct
public void init(){
this.productName = "西瓜";
}
}

User 类:

@Component
public class User {
private String name;

@Autowired
private Product product;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Product getProduct() {
return product;
}

public void setProduct(Product product) {
this.product = product;
}

@PostConstruct
public void init(){
this.name = "小明";
}

@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", product=" + product +
'}';
}
}

测试:

@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类获得对象
User user = (User) act.getBean("user");
System.out.println(user);
}

10.3 Spring 配置文件相关注解

10.3.1 @Configuration

@Configuration 注解用于替换 xml 配置文件。

@Configuration
public class SpringConfig {

}

意思就是说你在一个类上面加一个 @Configuration 注解,这个类就可以看成 Spring 的配置类,你就不用再写 xml 文件了。

我们之前是根据 xml 文件创建 Spring 的工厂,那怎样根据配置类创建工厂呢?

有两种方式:

方式一:根据类.class

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

方式二:根据配置类所在的路径

ApplicationContext ctx = new AnnotationConfigApplicationContext("com.xxl");

10.3.2 @Bean

@Bean 注解也是用来创建对象的,相当于spring 配置文件中的 bean 标签。

@Configuration
public class SpringConfig {

@Bean
public Product getProduct(){
return new Product();
}
}

自定义 id 值:

@Configuration
public class SpringConfig {

@Bean("product")
public Product getProduct(){
return new Product();
}
}

不过在实际开发中我们一般会用 @Bean 注解创建一些复杂的对象,例如 Redis、MQ 等一些组件对象。

@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

10.3.3 @ComponentScan

@ComponentScan 注解相当于 xml 配置文件中的注解扫描标签:

<context:component-scan base-package="com.xxl"/>

作用:用来扫描@Component 等相关注解

属性:

basePackages:注解所在的包路径

例如:

@Configuration
@ComponentScan(basePackages = "com.xxl")
public class SpringConfig {
}

11. 注解小案例

1.User 类

@Component
public class User {
private String name;

@Autowired
private Product product;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}

@PostConstruct
public void init(){
this.name = "渣渣辉";
}

@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", product=" + product +
'}';
}
}

2.Product 类

@Component
public class Product {
private String productName;
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
@Override
public String toString() {
return "Product{" +
"productName='" + productName + ''' +
'}';
}
}

3.配置类:

@Configuration
@ComponentScan(basePackages = "com.xxl")
public class SpringConfig {

@Bean
public Product product() {
Product product = new Product();
product.setProductName("草莓味的番茄");
return product;
}
}

4.测试

@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new AnnotationConfigApplicationContext(SpringConfig.class);
// 2、通过工厂类获得对象
User user = (User) act.getBean("user");
System.out.println(user);
}

文章目录
  1. 1. 1. 为啥要用 Spring
  2. 2. 2. Spring 简介
  3. 3. 3. 环境搭建
    1. 3.0.1. 1.创建 Maven 项目
    2. 3.0.2. 2.引入依赖
    3. 3.0.3. 3.创建接口和实现类
    4. 3.0.4. 4.创建配置文件
    5. 3.0.5. 5.测试
  • 4. 4. IOC
    1. 4.0.1. 4.1 IOC 简介
    2. 4.0.2. 4.2 Spring 核心内容描述
      1. 4.0.2.0.1. id:
      2. 4.0.2.0.2. name:
      3. 4.0.2.0.3. scope:
      4. 4.0.2.0.4. Spring 是如何创建对象的呢?
  • 4.0.3. 4.3 IOC 优点
  • 5. 5. DI
    1. 5.0.1. 5.1 set 注入
      1. 5.0.1.0.1. 5.1.1 JDK 内置数据类型
      2. 5.0.1.0.2. 5.1.2 用户自定义数据类型
  • 5.0.2. 5.2 构造注入
  • 5.0.3. 5.3 注入总结
  • 6. 6. Bean 的生命周期
    1. 6.0.1. 6.1 创建阶段
    2. 6.0.2. 6.2 初始化阶段
    3. 6.0.3. 6.3 销毁阶段
      1. 6.0.3.0.1. 销毁方法的定义有两种方式:
  • 7. 7. Bean 的后置处理
    1. 7.0.0.0.1. Bean 后置处理的流程:
  • 8. 8. 代理设计模式
    1. 8.0.1. 8.1 为啥要用代理设计模式?
    2. 8.0.2. 8.2 代理设计模式
    3. 8.0.3. 8.3 静态代理
    4. 8.0.4. 8.4 动态代理
    5. 8.0.5. 8.5 动态代理实现原理
  • 9. 9. AOP
    1. 9.0.1. 9.1 AOP 概念
    2. 9.0.2. 9.2 AOP 底层实现原理
      1. 9.0.2.0.1. 9.2.1 JDK 动态代理
      2. 9.0.2.0.2. 9.2.2 Cglib 动态代理
      3. 9.0.2.0.3. 9.2.3 Spring 如何创建代理对象?
  • 9.0.3. 9.3 基于注解开发 AOP
    1. 9.0.3.1. 切入点复用
  • 9.0.4. 9.4 切入点表达式
  • 10. 10. Spring 相关注解
    1. 10.0.1. 10.1 创建对象相关注解
      1. 10.0.1.0.1. 10.1.1 @Component
      2. 10.0.1.0.2. 10.1.2 @Component 衍生注解
      3. 10.0.1.0.3. 10.1.3 @Scope
      4. 10.0.1.0.4. 10.1.4 生命周期相关注解
  • 10.0.2. 10.2 注入相关注解
    1. 10.0.2.0.1. 10.2.1 @Autowired
    2. 10.0.2.0.2. 10.2.2 @Resource
    3. 10.0.2.0.3. 10.2.3 案例
  • 10.0.3. 10.3 Spring 配置文件相关注解
    1. 10.0.3.0.1. 10.3.1 @Configuration
    2. 10.0.3.0.2. 10.3.2 @Bean
    3. 10.0.3.0.3. 10.3.3 @ComponentScan
  • 11. 11. 注解小案例