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注入的过程:
- 配置文件定义: 在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>
- 容器启动: 当Spring容器启动时,它会读取XML配置文件并创建定义的bean。
- 解析属性: 对于
标签,Spring会获取属性名(例如userDao)并推断出相应的set方法名(即setUserDao)。 - 反射调用set方法: 使用Java的反射机制,Spring会调用setUserDao方法,将对应的bean(userDaoBean)作为参数传入,从而完成依赖注入。
- 依赖关系建立: 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及以后的版本中被推荐使用,因为它可以保证对象在创建时就完全初始化,并且依赖不可变。
构造注入的过程:
- 配置文件定义: 在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>
- 容器启动: 当Spring容器启动时,它会读取XML配置文件,并创建所有定义的bean。
- 构造函数调用: 对于每个bean,Spring会根据其构造函数的参数列表,找到对应的依赖bean(如userDaoBean),并使用这些依赖创建该bean的实例。
- 依赖关系建立: 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中,模板方法模式被用来执行数据库操作,定义了一系列步骤,子类可以重写某些步骤。
浙公网安备 33010602011771号