Spring学习记录

一、Spring启示录

1.1 OCP开闭原则

开闭原则(Open/Closed Principle,OCP)是面向对象设计的核心原则之一,由Bertrand Meyer在1988年提出。它指出软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着在不修改现有代码的情况下,应该能够扩展软件实体的功能。
在Spring中的应用:

  • 接口编程:Spring鼓励开发者使用接口而不是具体的类来编程。这样做的好处是,当需要添加新功能时,可以通过实现新的接口来扩展功能,而不需要修改现有的代码。
  • 插件架构:Spring框架本身提供了插件架构的支持,允许开发者在不修改核心代码的情况下,通过添加新的插件来扩展应用的功能。

1.2 依赖倒置原则DIP

依赖倒置原则(Dependency Inversion Principle,DIP)是SOLID原则中的一个,它建议高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。
在Spring中的应用:

  • 依赖注入:Spring通过依赖注入(Dependency Injection,DI)机制来实现依赖倒置。通过DI,Spring容器负责创建对象及其依赖关系,而不是让对象自己创建或查找依赖对象。这样,对象的实现可以被替换,而不影响使用它的代码。
  • 接口依赖:Spring鼓励开发者定义接口或抽象类,并通过这些抽象来注入依赖,而不是直接依赖具体的实现类。

1.3 控制反转IoC

控制反转(Inversion of Control,IoC)是一种设计原则,用来减少软件系统中各部分之间的耦合度。在传统的编程模式中,组件之间的依赖关系由组件自身在内部创建或查找其依赖项来实现。而在IoC模式中,这些依赖关系由外部容器在运行时动态注入。
在Spring中的应用:

  • 容器管理:Spring框架提供了一个容器(ApplicationContext),负责管理应用中的对象及其依赖关系。开发者只需要定义对象和依赖关系,容器会在运行时自动注入这些依赖。
  • 配置元数据:Spring支持通过XML、注解或Java配置类来配置对象及其依赖关系。这些配置元数据告诉Spring容器如何创建对象以及如何注入依赖。

总结:
Spring框架通过实现开闭原则、依赖倒置原则和控制反转原则,提供了一个灵活、可扩展的编程模型。这些原则使得Spring应用易于维护和扩展,同时降低了组件之间的耦合度。

二、Spring概述

2.1 Spring简介

Spring 是一个开源的Java平台,用于简化企业级应用的开发。它提供了一系列的解决方案来解决Java EE应用开发中的复杂性。Spring的核心是控制反转(IoC)容器,它允许开发者以更松散耦合的方式设计应用。

2.2 Spring 8大模块

Spring框架由多个模块组成,每个模块都提供了特定的功能。以下是Spring的8大核心模块:

  • Spring Core:提供了IoC容器的基本功能。
  • Spring Context:构建于Core模块之上,提供了应用生命周期管理、资源访问等。
  • Spring AOP:提供了面向切面编程的支持。
  • Spring DAO:提供了数据访问对象的支持,简化了JDBC操作。
  • Spring ORM:提供了对各种对象关系映射技术的集成。
  • Spring Web:提供了Web应用开发的基础设施。
  • Spring MVC:提供了一个实现了MVC设计模式的Web应用框架。
  • Spring Test:提供了对单元测试和集成测试的支持。

2.3 Spring特点

Spring框架的特点包括:

  • 轻量级:Spring框架的核心非常轻量,只有核心的几个模块依赖于其他库。
  • 控制反转(IoC):Spring通过IoC容器管理对象的创建和依赖关系,减少了代码的耦合度。
  • 面向切面编程(AOP):Spring提供了强大的AOP支持,可以轻松实现诸如日志、事务等功能。
  • 事务管理:Spring提供了声明式和编程式的事务管理。
  • 集成多种数据访问技术:Spring支持JDBC、Hibernate、JPA等多种数据访问技术。
  • MVC框架:Spring MVC提供了一个灵活的Web应用开发框架。
  • 模块化:Spring的模块化设计使得开发者可以根据需要选择使用哪些模块。
  • 广泛的社区支持:Spring拥有一个庞大的社区,提供了大量的文档、教程和工具。
  • 与现有技术的集成:Spring可以很容易地与现有的Java EE技术集成,如JMS、EJB等。
  • 测试支持:Spring提供了对测试的支持,使得单元测试和集成测试变得更加容易。

三、Spring对IoC的实现

3.1 IoC控制反转

控制反转(Inversion of Control,IoC)是一种设计原则,它将对象的控制权从对象本身转移到外部容器,如Spring框架。在IoC容器中,对象的创建、生命周期、依赖关系的管理都由容器来控制,从而实现对象之间的松耦合。
在Spring中的实现:

  • IoC容器:Spring框架中的ApplicationContext是IoC容器的实现,负责管理应用中的对象生命周期和依赖关系。
  • 依赖注入:Spring通过依赖注入(DI)实现IoC,其中对象的依赖关系由容器在运行时动态注入,而不是在代码中静态定义。

3.2 依赖注入

依赖注入(Dependency Injection,DI)是实现IoC的一种方式,它将对象的依赖关系传递给对象,而不是让对象自己创建或查找依赖。

依赖注入常见的实现方式包括两种:
● 第一种:set注入
● 第二种:构造注入

3.2.1 set注入

Setter方法注入是通过调用对象的Setter方法来注入依赖。这种方式在Spring 3.x版本中被推荐使用,因为它允许对象在创建后重新配置或注入,提供了更大的灵活性。

set注入的过程:

  1. 配置文件定义: 在Spring的XML配置文件中,通过标签定义需要注入的bean,并使用标签指定需要注入的属性和对应的bean。
<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
    <property name="userDao" ref="userDaoBean"/>
</bean>
  1. 容器启动: 当Spring容器启动时,它会读取XML配置文件并创建定义的bean。
  2. 解析属性: 对于标签,Spring会获取属性名(例如userDao)并推断出相应的set方法名(即setUserDao)。
  3. 反射调用set方法: 使用Java的反射机制,Spring会调用setUserDao方法,将对应的bean(userDaoBean)作为参数传入,从而完成依赖注入。
  4. 依赖关系建立: UserService类中的userDao属性被赋值为UserDao的实例,建立了两个对象之间的关系。

set注入的原理:

  • 反射机制: set注入依赖于Java反射机制。Spring通过反射获取类的属性信息并调用对应的set方法。
  • 方法名约定: set注入遵循一定的命名约定,通过属性名推导出set方法名。例如:
    • 属性名为userDao,则set方法为setUserDao。
    • 属性名为username,则set方法为setUsername。
  • 灵活性和可维护性: set注入允许在运行时动态修改依赖关系,因此对于配置和测试场景更为灵活。相对于构造注入,set注入的代码可读性和可维护性也较高。

总结:set注入的核心实现原理:通过反射机制调用set方法来给属性赋值,让两个对象之间产生关系。

3.2.2 构造注入

构造器注入是通过对象的构造器来注入依赖,这种方式在Spring 4.x及以后的版本中被推荐使用,因为它可以保证对象在创建时就完全初始化,并且依赖不可变。

构造注入的过程:

  1. 配置文件定义: 在Spring的XML配置文件中,通过标签定义需要注入的bean,并在构造函数中指定所需的依赖。
<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>

<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
    <constructor-arg ref="userDaoBean"/>
</bean>
  1. 容器启动: 当Spring容器启动时,它会读取XML配置文件,并创建所有定义的bean。
  2. 构造函数调用: 对于每个bean,Spring会根据其构造函数的参数列表,找到对应的依赖bean(如userDaoBean),并使用这些依赖创建该bean的实例。
  3. 依赖关系建立: UserService类的构造函数接受UserDao的实例,构造完成后,UserService就拥有了UserDao的引用,完成依赖关系的建立。

构造注入的原理:

  • 构造函数参数: 构造注入依赖于构造函数的参数,Spring通过XML配置中的标签来指定依赖bean。
  • 强制依赖性: 构造注入要求在创建对象时提供所有的依赖,因此能够确保对象在构造完成后是完全可用的。这样可以避免空指针异常的问题。
  • 不可变性: 通过构造函数注入,所有依赖在对象创建时就被初始化,这使得对象在创建后不可被更改(除非提供了其他的修改方法),从而提高了对象的不可变性。

总结:构造注入是通过构造函数直接传入依赖对象实现依赖注入的方式。这种方式的优点在于确保对象在创建时具备必要的依赖关系,能够有效地提高代码的可维护性和健壮性。

依赖注入对比:

注入方式 优点 缺点
set注入 灵活性:可以动态修改依赖。 不够安全:可能导致空指针异常。
可选依赖:允许不必在创建时提供所有依赖。 依赖关系不明确:可能在使用时未完成所有依赖注入。
简单易懂:配置简单,代码可读性高。 初始化时状态不一致:对象可能处于不完整状态。
构造注入 强制依赖性:确保在对象创建时提供所有必要依赖。 不灵活:无法在对象创建后更改依赖。
不可变性:对象状态在创建后不会变化。 参数较多:构造函数参数多时可读性降低。
依赖关系明确:使依赖关系清晰,便于维护。 不适合可选依赖:所有依赖需在构造时提供。

总结:
Spring通过IoC容器和DI机制来管理应用中的对象和它们的依赖关系,从而实现了解耦和灵活性。Setter注入和构造器注入是两种常用的DI方式,各有优缺点,开发者可以根据具体需求选择适合的注入方式。

四、Bean的作用域

Spring框架支持多种Bean作用域,作用域定义了Spring容器如何创建Bean实例。以下是几种常见的作用域:

4.1 singleton

singleton是默认的作用域。在Spring IoC容器中,每个被定义为singleton的Bean将只创建一个实例。无论多少次请求,总是返回同一个Bean实例。

示例代码:

<bean id="mySingletonBean" class="com.example.MyClass" scope="singleton"/>

4.2 prototype

prototype作用域表示每次请求(通过容器的getBean方法)都会创建一个新的Bean实例。

示例代码:

<bean id="myPrototypeBean" class="com.example.MyClass" scope="prototype"/>

4.3 其他作用域

除了singleton和prototype之外,Spring还提供了其他几种作用域:

  • request: 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于基于web的Spring应用程序上下文。
  • session: 每次HTTP会话都会创建一个新的Bean,该作用域仅适用于基于web的Spring应用程序上下文。
  • application: 在web应用中,每个ServletContext都会创建一个Bean实例,对于普通的Java应用程序,它等同于singleton。
  • websocket: 在web应用中,每个WebSocket都会创建一个Bean实例,该作用域仅适用于基于web的Spring应用程序上下文。

五、Bean的实例化方式

Spring提供了多种方式来实例化Beans,以下是几种常见的实例化方式:

5.1 通过构造方法实例化

这是最常见的实例化方式。Spring通过调用Bean的构造方法来创建实例。这种方式可以通过构造方法的参数传递复杂的依赖。

示例代码:

<bean id="myBean" class="com.example.MyClass">
    <constructor-arg value="someValue"/>
</bean>

5.2 通过简单工厂模式实例化

在这种方式中,Spring调用一个工厂方法来创建Bean的实例。工厂方法可以返回不同的Bean实例,或者在创建实例时使用复杂的逻辑。

示例代码:

<bean id="myBean" class="com.example.MyFactory" factory-method="createInstance"/>

5.3 通过factory-bean实例化

这种方式与简单工厂模式类似,但是factory-bean是定义在Spring容器中的一个Bean,通过指定的工厂方法来创建目标Bean。

示例代码:

<bean id="myFactoryBean" class="com.example.MyFactoryBean"/>
<bean id="myBean" factory-bean="myFactoryBean" factory-method="getObject"/>

5.4 通过FactoryBean接口实例化

FactoryBean是一个特殊的Bean,它可以创建并返回另一个Bean。当Spring容器创建FactoryBean时,它不会返回FactoryBean实例本身,而是返回FactoryBean实现的getObject()方法的返回值。

示例代码:

public class MyFactoryBean implements FactoryBean<MyClass> {
    @Override
    public MyClass getObject() throws Exception {
        return new MyClass("someValue");
    }

    @Override
    public Class<?> getObjectType() {
        return MyClass.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

<bean id="myFactoryBean" class="com.example.MyFactoryBean"/>

使用FactoryBean可以隐藏对象创建的复杂性,使得应用代码只需要关心Bean的引用,而不需要知道具体的创建过程。

5.5 BeanFactory和FactoryBean的区别

BeanFactory和FactoryBean是Spring框架中的两个重要概念,但它们的目的和作用是不同的。

5.5.1 BeanFactory

BeanFactory是Spring框架中最基本的接口,它定义了IoC容器的基本功能。BeanFactory负责实例化、配置和组装Beans。它是Spring框架中所有IoC容器的鼻祖,提供了最基本的依赖注入功能。

特点:

  • BeanFactory是IoC容器的顶层接口。
  • 它提供了配置和解析Bean定义的功能。
  • BeanFactory是单实例的,一旦创建,配置信息就固定了。

示例代码:

BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
MyBean myBean = (MyBean) factory.getBean("myBean");

5.5.2 FactoryBean

FactoryBean是一个接口,它定义了一个工厂方法getObject(),用于创建并返回一个对象。当Spring容器创建一个FactoryBean类型的Bean时,它不会返回FactoryBean实例本身,而是返回getObject()方法返回的对象。

特点:

  • FactoryBean是一个创建Bean的工厂。
  • 它允许你创建复杂的对象。
  • FactoryBean可以返回任何类型的对象,包括其他Beans。
  • FactoryBean可以控制Bean的生命周期。

示例代码:

public class MyFactoryBean implements FactoryBean<MyBean> {
    @Override
    public MyBean getObject() throws Exception {
        // 创建并返回一个复杂的对象
        return new MyBean();
    }

    @Override
    public Class<?> getObjectType() {
        return MyBean.class;
    }

    @Override
    public boolean isSingleton() {
        // 定义Bean的作用域
        return true;
    }
}

在Spring配置中:

<bean id="myBean" class="com.example.MyFactoryBean"/>

或者在Java配置中:

@Bean
public FactoryBean<MyBean> myBean() {
    return new MyFactoryBean();
}

总结:

  • BeanFactory是Spring IoC容器的基石,负责管理Bean的生命周期。
  • FactoryBean是一个创建复杂对象的工厂,它允许你通过工厂方法来创建Bean。

两者的主要区别在于,BeanFactory是容器本身,而FactoryBean是一个Bean的定义方式,用于创建其他Beans。

六、Bean的生命周期

在Spring框架中,Bean的生命周期指的是从创建到销毁的整个过程。了解Bean的生命周期对于解决复杂的依赖关系、管理资源和实现特定的初始化或销毁逻辑非常重要。

为什么要知道Bean的生命周期?
了解Bean的生命周期可以帮助开发者:

  • 管理资源,如数据库连接或文件句柄。
  • 控制Bean的创建和销毁过程。
  • 实现自定义的初始化和销毁逻辑。
  • 保证Bean的线程安全性和单例性。

6.1 Bean的生命周期之5步

  • 实例化:Spring容器通过反射创建Bean的实例。
  • 属性赋值:将配置文件或注解中定义的属性值注入到Bean中。
  • 初始化:调用@PostConstruct注解的方法或实现InitializingBean接口的afterPropertiesSet方法。
  • 使用:Bean被应用于业务逻辑中。
  • 销毁:容器关闭时,调用@PreDestroy注解的方法或实现DisposableBean接口的destroy方法。

6.2 Bean生命周期之7步

  • 实例化:Spring容器通过反射创建Bean的实例。
  • 属性赋值:将配置文件或注解中定义的属性值注入到Bean中。
  • Bean后处理器before执行:调用BeanPostProcessor接口的postProcessBeforeInitialization方法,进行自定义处理。
  • 初始化Bean:调用@PostConstruct方法或实现InitializingBean接口的afterPropertiesSet方法,完成初始化。
  • Bean后处理器after执行:调用BeanPostProcessor接口的postProcessAfterInitialization方法,进行后续处理。
  • 使用:Bean被应用于业务逻辑中,执行相应的操作。
  • 销毁:容器关闭时,调用@PreDestroy方法或实现DisposableBean接口的destroy方法,进行清理。

6.3 Bean生命周期之10步

  • 实例化Bean:Spring容器通过反射创建Bean的实例。
  • Bean属性赋值:将配置文件或注解中定义的属性值注入到Bean中。
  • 检查Bean是否实现了Aware的相关接口,并设置相关依赖:如BeanNameAware、BeanFactoryAware等接口的实现。
  • Bean后处理器before执行:调用BeanPostProcessor的postProcessBeforeInitialization方法,进行自定义处理。
  • 检查Bean是否实现了InitializingBean接口,并调用接口方法:如果实现,调用afterPropertiesSet方法。
  • 初始化Bean:调用@PostConstruct注解的方法进行初始化。
  • Bean后处理器after执行:调用BeanPostProcessor的postProcessAfterInitialization方法,进行后续处理。
  • 使用Bean:Bean被应用于业务逻辑中,执行相应的操作。
  • 检查Bean是否实现了DisposableBean接口,并调用接口方法:如果实现,调用destroy方法。
  • 销毁Bean:容器关闭时,调用@PreDestroy注解的方法进行清理。

6.4 Bean的作用域不同,管理方式不同

不同作用域的Bean在Spring容器中的管理方式不同:

  • singleton:Spring容器中只创建一个Bean实例。
  • prototype:每次请求都会创建一个新的Bean实例。
  • request:每个HTTP请求都会创建一个新的Bean,适用于Web应用程序。
  • session:每个HTTP会话都会创建一个新的Bean,适用于Web应用程序。
  • application:每个ServletContext都会创建一个Bean实例。
  • websocket:每个WebSocket都会创建一个Bean实例。

6.5 自己new的对象如何让Spring管理

如果你希望Spring管理一个自己通过new关键字创建的对象,可以使用FactoryBean或通过编程方式将对象注册到Spring容器中。

使用FactoryBean:

  • 创建一个实现了FactoryBean接口的类,然后在Spring配置中声明这个FactoryBean,Spring将会调用FactoryBean的getObject()方法来获取Bean实例。

编程方式注册:

  • 获取BeanFactory或ApplicationContext。
  • 通过registerSingleton或registerBean方法注册Bean。

示例代码:

使用FactoryBean,实现FactoryBean接口:

public class UserFactoryBean implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        return new User(); // 通过new创建对象
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

    @Override
    public boolean isSingleton() {
        return true; // 是否为单例
    }
}

在Spring配置中声明FactoryBean:

<bean id="user" class="com.example.UserFactoryBean" />

编程方式注册,创建和注册Bean:

public class RegisterBeanTest {

    @Test
    public void testBeanRegister() {
        // 自己new的对象
        User user = new User();
        System.out.println(user);

        // 创建 DefaultListableBeanFactory 对象
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        // 注册Bean
        factory.registerSingleton("userBean", user);
        // 从Spring容器中获取bean
        User userBean = factory.getBean("userBean", User.class);
        System.out.println(userBean);
    }
}

通过以上方法,即使是手动创建的对象,也可以利用Spring的依赖注入和生命周期管理功能。

七、Bean的循环依赖问题

7.1 什么是Bean的循环依赖

Bean的循环依赖是指在Spring容器中,两个或多个Bean相互依赖,形成了一个闭环。即Bean A依赖于Bean B,同时Bean B也依赖于Bean A。

示例:

@Component
public class A {
    private B b;

    @Autowired
    public void setB(B b) {
        this.b = b;
    }
}

@Component
public class B {
    private A a;

    @Autowired
    public void setA(A a) {
        this.a = a;
    }
}

在这个例子中,A依赖于B,B也依赖于A,形成了循环依赖。

7.2 singleton下的set注入产生的循环依赖

在singleton作用域下,如果使用set方法注入(属性注入),Spring可以通过使用三级缓存来解决循环依赖的问题。

Spring的三级缓存机制:

  • singletonObjects:已经完全初始化好的对象。
  • earlySingletonObjects:提前暴露的对象,还没有完成依赖注入。
  • singletonFactories:存储bean工厂对象,用于创建bean。

当创建一个bean时,Spring会先检查一级缓存,如果不存在,则去二级缓存中创建bean,并将其放入三级缓存。当因为循环依赖回溯时,可以从三级缓存中获取bean的早期引用。

7.3 prototype下的set注入产生的循环依赖

在prototype作用域下,由于每次请求都会创建一个新的Bean实例,Spring无法使用三级缓存机制来解决循环依赖问题。因此,prototype作用域下的循环依赖会导致异常。

7.4 singleton下的构造注入产生的循环依赖

在singleton作用域下,如果使用构造方法注入,Spring无法解决循环依赖问题,因为构造方法注入需要一次性提供所有依赖,无法进行延迟加载。

7.5 Spring解决循环依赖的机理

Spring通过使用三级缓存机制来解决singleton作用域下的set注入循环依赖问题:

  • singletonObjects:存储完全初始化好的Bean。
  • earlySingletonObjects:存储正在创建中的Bean,允许循环引用的Bean被提前引用。
  • singletonFactories:存储bean工厂对象,用于解决循环依赖问题。

Spring通过将“实例化Bean”和“给Bean属性赋值”这两个动作分开来解决了单例模式下的循环依赖问题。

解决循环依赖的机制:

  • 实例化Bean:当Spring容器启动时,它会调用无参数构造方法实例化所有单例Bean。这个过程中,Spring并不会立即给Bean的属性赋值,而是将这些Bean对象提前“曝光”到容器中。

  • 曝光Bean:在实例化完成后,Spring将这些Bean对象放入一个缓存集合中,这样其他Bean在依赖注入时可以获取到这个尚未完全初始化的Bean实例。

  • 属性赋值:在所有单例Bean都实例化后,Spring会再逐个调用setter方法或使用其他方式(如构造器注入)来完成属性赋值。由于Bean已经曝光,即使某个Bean需要依赖其他尚未赋值的Bean,也能够通过缓存获取到相应的对象。

循环依赖的处理流程:

  • 假设有两个Bean A和B,A依赖B,B又依赖A。
  • Spring首先实例化A和B,并将它们放入缓存中。
  • 然后,Spring逐个调用setter方法,完成依赖注入。即使A在初始化时需要B,Spring会将B的实例返回(尽管B的属性尚未完全赋值)。
  • 这样,通过这种分离的机制,循环依赖得以解决。

总结

通过将实例化和属性赋值两个步骤分开,Spring实现了更灵活的Bean管理,使得循环依赖问题在单例模式下也能得到妥善处理。这种设计使得Spring能够在复杂的依赖关系中保持容器的稳定性与灵活性。

八、手写Spring框架

通过上述内容,试着自己实现一下简易版的Spring框架

参考:手写Spring框架

九、Spring IoC注解式开发

9.1 回顾注解

注解(Annotations)是Java语言的一个特性,用于为代码添加元数据。在Spring框架中,注解被广泛用于简化配置,实现IoC容器的自动化配置。

9.2 声明Bean的注解

在Spring中,可以使用@Bean注解来声明一个Bean。

示例代码:

@Configuration
public class AppConfig {
    @Bean
    public MyBean myBean() {
        return new MyBean();
    }
}

@Configuration注解标记这是一个配置类,而@Bean注解标记方法将返回值注册为Spring容器的Bean。

9.3 Spring注解的使用

Spring提供了一系列的注解来支持IoC容器的功能,包括:

  • @Autowired:自动注入依赖。
  • @Component:标记一个类作为Bean。
  • @Service、@Repository、@Controller:分别用于标记服务层、数据访问层和表现层的Bean。
  • @Lazy:懒加载Bean。
  • @Scope:指定Bean的作用域。

9.4 负责注入的注解

9.4.1 @Value

@Value注解用于将外部配置注入到Bean的字段中。

示例代码:

@Component
public class MyBean {
    @Value("${my.property}")
    private String myProperty;
}

9.4.2 @Autowired与@Qualifier

@Autowired注解用于自动注入依赖的Bean。单独使用@Autowired注解,默认根据类型装配。【默认是byType】

示例代码:

@Component
public class MyBean {
    @Autowired
    private MyDependency myDependency;
}

@Autowired注解和@Qualifier注解联合起来才可以根据名称进行装配,在@Qualifier注解中指定Bean名称。

示例代码:

@Component
public class MyBean {
    @Autowired
    @Qualifier("specificBean")
    private MyDependency myDependency;
}

总结:
● @Autowired注解可以出现在:属性上、构造方法上、构造方法的参数上、setter方法上。
● 当带参数的构造方法只有一个,@Autowired注解可以省略。
● @Autowired注解默认根据类型注入。如果要根据名称注入的话,需要配合@Qualifier注解一起使用。

9.4.3 @Resource

@Resource注解也用于注入Bean,可以通过名称来指定注入的Bean。
@Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。

示例代码:

@Component
public class MyBean {
    @Resource(name="myDependency")
    private MyDependency myDependency;
}

9.5 全注解式开发

全注解式开发是指完全使用注解来配置Spring应用,而不使用XML配置文件。

示例代码:

@Configuration
@ComponentScan(basePackages="com.example")
public class AppConfig {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }
}

@Component
public class MyBean {
    @Autowired
    private MyDependency myDependency;
    
    @Value("${my.property}")
    private String myProperty;
    
    @PostConstruct
    public void init() {
        // 初始化代码
    }
}

在这个例子中,@Configuration标记这是一个配置类,@ComponentScan指定了组件扫描的包,@Bean声明了一个Bean,@Component标记了一个Bean类,@Autowired用于自动注入依赖,@Value用于注入外部配置,@PostConstruct用于指定初始化方法。

十、GoF之代理模式

代理模式(Proxy Pattern)是一种结构型设计模式,它能够提供对另一个对象的代理以控制对这个对象的访问。代理模式在不直接与实际对象交互的情况下,提供了对目标对象的间接访问。

10.1 对代理模式的理解

代理模式的主要目的是:

  • 控制访问:在不改变实际对象的情况下,通过引入一个代理对象来控制对这个对象的访问。
  • 增加额外的处理:在访问实际对象时,可以在代理对象中添加额外的处理逻辑。
  • 保护和隐藏对象:代理对象可以隐藏实际对象的实现细节,保护对象不被外部直接访问。
  • 延迟初始化:代理对象可以在需要时才创建实际对象,从而节省资源。

代理模式通常有两种形式:静态代理和动态代理。

10.2 静态代理

静态代理是一种编译时就确定代理类和目标类的关系。代理类和目标类实现相同的接口,代理类在内部持有目标类的引用,并在转发请求时执行额外的逻辑。

示例代码:

public interface Service {
    void serve();
}

public class RealService implements Service {
    @Override
    public void serve() {
        System.out.println("Serving...");
    }
}

public class ServiceProxy implements Service {
    private Service service;

    public ServiceProxy(Service service) {
        this.service = service;
    }

    @Override
    public void serve() {
        preServe();
        service.serve();
        postServe();
    }

    private void preServe() {
        System.out.println("Before serving...");
    }

    private void postServe() {
        System.out.println("After serving...");
    }
}

在这个例子中,ServiceProxy是代理类,RealService是实际对象。代理类在调用实际对象的方法前后添加了额外的逻辑。

10.3 动态代理

动态代理是在运行时动态创建代理类和对象。Java提供了两种动态代理的实现方式:JDK动态代理和CGLIB动态代理。

10.3.1 JDK动态代理

JDK动态代理是基于接口的代理方式,代理类在运行时由JVM创建,不需要手动编写代理类。

示例代码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public interface Service {
    void serve();
}

public class RealService implements Service {
    @Override
    public void serve() {
        System.out.println("Serving...");
    }
}

public class ServiceInvocationHandler implements InvocationHandler {
    private Object target;

    public ServiceInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before serving...");
        Object result = method.invoke(target, args);
        System.out.println("After serving...");
        return result;
    }

    public static Service newProxyInstance(Service target) {
        return (Service) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{Service.class},
                new ServiceInvocationHandler(target)
        );
    }
}

在这个例子中,ServiceInvocationHandler是调用处理器,它实现了InvocationHandler接口。newProxyInstance方法用于创建代理对象。

10.3.2 CGLIB动态代理

CGLIB(Code Generation Library)是一个强大的高性能代码生成库,它可以在运行时扩展Java类和实现Java接口。CGLIB动态代理不依赖于接口,因此可以代理没有实现接口的类。

示例代码:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class RealService {
    public void serve() {
        System.out.println("Serving...");
    }
}

public class ServiceMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before serving...");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After serving...");
        return result;
    }

    public static RealService newProxyInstance() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealService.class);
        enhancer.setCallback(new ServiceMethodInterceptor());
        return (RealService) enhancer.create();
    }
}

在这个例子中,ServiceMethodInterceptor是方法拦截器,它实现了MethodInterceptor接口。newProxyInstance方法用于创建代理对象。

总结:

代理模式提供了一种灵活的方式来控制对目标对象的访问,可以在不改变目标对象的前提下,通过引入代理对象来增加额外的处理逻辑。静态代理在编译时确定代理关系,而动态代理在运行时创建代理对象,提供了更大的灵活性。

十一、面向切面编程AOP

11.1 AOP介绍

面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,它允许将横切关注点(如日志、事务、安全性等)与业务逻辑分离,从而提高代码的模块化和可维护性。

11.2 AOP的七大术语

  • 切面(Aspect):模块化的横切关注点的代码。
  • 连接点(Join Point):程序执行的特定点,如方法调用或字段访问。
  • 通知(Advice):在切点处执行的动作,包括前置通知、后置通知、环绕通知等。
  • 切点(Pointcut):指定通知应用的连接点。
  • 引入(Introduction):在不修改代码的情况下,向类添加新方法或字段。
  • 目标对象(Target Object):被代理的对象。
  • 织入(Weaving):将切面应用到目标对象的过程。

11.3 切点表达式

切点表达式用于指定哪些连接点会被通知。Spring支持Java和AspectJ两种切点表达式。
切入点表达式语法格式:

execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])

访问控制权限修饰符:
● 可选项。
● 没写,就是4个权限都包括。
● 写public就表示只包括公开的方法。
返回值类型:
● 必填项。
● * 表示返回值类型任意。
全限定类名:
● 可选项。
● 两个点“..”代表当前包以及子包下的所有类。
● 省略时表示所有的类。
方法名:
● 必填项。
表示所有方法。
● set
表示所有的set方法。
形式参数列表:
● 必填项
● () 表示没有参数的方法
● (..) 参数类型和个数随意的方法
● () 只有一个参数的方法
● (
, String) 第一个参数类型随意,第二个参数是String的。
异常:
● 可选项。
● 省略时表示任意异常类型。

11.4 切点表达式示例

在Spring AOP中,execution表达式用于匹配方法的执行。它通常包括方法的访问控制权限修饰符、返回值类型、全限定类名、方法名以及形式参数列表和异常。以下是三个execution表达式的例子及其解释:

11.4.1 例子1:

@Pointcut("execution(public * com.example.service.*.*(..))")
public void serviceMethods() {}
  • 解释:这个切点表达式匹配com.example.service包及其子包中所有公共(public)方法。这些方法可以返回任何类型(*),并且可以有任意数量和类型的参数(..)。它不关心方法抛出的异常。

11.4.2 例子2:

@Pointcut("execution(int com.example.dao.*.*(java.lang.String))")
public void daoMethodsWithStringParameter() {}
  • 解释:这个切点表达式匹配com.example.dao包及其子包中的所有方法,这些方法返回int类型,有一个String类型的参数。它不关心方法的访问控制权限修饰符(默认是任意访问级别),也不关心方法抛出的异常。

11.4.3 例子3:

@Pointcut("execution(* com.example.controller.*.*(..) throws java.lang.Exception)")
public void controllerMethodsThrowingException() {}
  • 解释:这个切点表达式匹配com.example.controller包及其子包中的所有方法,这些方法可以返回任何类型(*),可以有任意数量和类型的参数(..),并且抛出java.lang.Exception或其子类的异常。它不关心方法的访问控制权限修饰符。

11.5 使用Spring的AOP

11.5.1 准备工作

  • 添加依赖:确保项目中包含了Spring AOP和AspectJ的依赖。
  • 启用AOP:在配置类上添加@EnableAspectJAutoProxy注解。

11.5.2 基于AspectJ的AOP注解式开发

实现步骤:

  • 定义切面:创建一个类,使用@Aspect注解标记。
  • 通知类型:使用@Before、@After、@AfterReturning、@AfterThrowing、@Around等注解定义通知。
  • 切面的先后顺序:使用@Order或@Priority注解指定切面的执行顺序。
  • 优化使用切点表达式:精确地定义切点表达式以减少不必要的代理创建。

示例代码:

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore() {
        System.out.println("Method is about to be called.");
    }

    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void logAfter(JoinPoint joinPoint, Object result) {
        System.out.println("Method returned: " + result);
    }
}

11.6 AOP的实际案例

实现日志记录和异常处理功能

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Executing method: " + joinPoint.getSignature().getName());
    }

    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("Method executed: " + joinPoint.getSignature().getName());
    }

    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable ex) {
        System.out.println("Method " + joinPoint.getSignature().getName() + " threw exception: " + ex.getMessage());
    }
}

LoggingAspect类是一个Spring AOP切面,用于记录服务层方法的执行过程。它包含三个主要的通知:

  • logBefore:使用@Before注解,在目标方法执行前打印方法名。
  • logAfter:使用@After注解,在目标方法执行后打印方法名。
  • logAfterThrowing:使用@AfterThrowing注解,在目标方法抛出异常时打印异常信息。

总结:

AOP提供了一种强大的方式来实现关注点分离,使得横切关注点如日志、事务、安全性等可以与业务逻辑分离。Spring AOP支持注解和XML配置两种方式,使得开发者可以根据项目需求灵活选择。

十二、Spring中常见的八大设计模式:

  • 简单工厂模式:BeanFactory 就是简单工厂模式的体现,它是用来创建对象的工厂。
  • 工厂方法模式:ApplicationContext 作为工厂方法模式的实现,提供了更多高级功能,如生命周期管理。
  • 单例模式:Spring默认情况下管理的Bean都是单例模式,即每个Bean默认只创建一个实例。
  • 代理模式:Spring AOP 使用代理模式来在不改变原有代码的情况下,增加额外功能。
  • 装饰器模式:在Spring中,装饰器模式可以用于动态地添加额外的功能,例如通过@Transactional注解动态添加事务管理。
  • 观察者模式:Spring的事件监听机制使用了观察者模式,允许Bean监听和响应应用程序中的各种事件。
  • 策略模式:Spring允许通过不同的策略实现相同的接口,根据不同的策略来执行不同的行为。
  • 模板方法模式:在Spring的JdbcTemplate中,模板方法模式被用来执行数据库操作,定义了一系列步骤,子类可以重写某些步骤。
posted @ 2024-09-28 20:01  Abufan  阅读(34)  评论(0)    收藏  举报