简介
Spring是一个开源的、轻量级的Java开发框架,旨在简化企业级应用开发。
Spring是Java EE编程领域的一个轻量级开源框架,该框架由一个叫Rod Johnson的程序员在 2002 年最早提出并随后创建,是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架 。 [2]Spring是一个开源容器框架,它集成各类型的工具,通过核心的Bean factory实现了底层的类的实例化和生命周期的管理。在整个框架中,各类型的功能被抽象成一个个的 Bean,这样就可以实现各种功能的管理,包括动态加载和切面编程。 [3]Spring是独特的,因为若干个原因: 它定位的领域是许多其他流行的framework没有的。Spring致力于提供一种方法管理你的业务对象。 Spring是全面的和模块化的。Spring有分层的体系结构,这意味着你能选择使用它孤立的任何部分,它的架构仍然是内在稳定的。例如,你可能选择仅仅使用Spring来简单化JDBC的使用,或用来管理所有的业务对象。 它的设计从底部帮助你编写易于测试的代码。Spring是用于测试驱动工程的理想的framework。 Spring对你的工程来说,它不需要一个以上的framework。Spring是潜在地一站式解决方案,定位于与典型应用相关的大部分基础结构。它也涉及到其他framework没有考虑到的内容。
Spring框架优缺点
优点
- 依赖注入(DI)和面向切面编程(AOP):
- 依赖注入(DI):Spring允许通过依赖注入来管理对象的生命周期和依赖关系,减少了对象间的耦合,提高了代码的可维护性和测试性。
- 面向切面编程(AOP):提供了对跨越多个对象的关注点(如事务处理、安全性)的支持,使代码更加模块化。
- 模块化:
- Spring框架由多个模块组成(如Spring Core, Spring MVC, Spring Data, Spring Security等),可以根据需要选择性地使用。
- 开箱即用:
- 提供了多种开箱即用的功能,如事务管理、数据访问、消息传递等,简化了开发过程。
- 灵活性和可扩展性:
- Spring支持多种配置方式(XML、Java配置、注解等),允许开发者根据需求选择最适合的方式。
- Spring框架本身具有很高的灵活性和可扩展性,它允许开发者根据需要选择适合的功能和组件,并且可以与其他框架如Hibernate、MyBatis等集成,提供更加灵活和强大的应用开发能力。
- 与其他技术兼容:
- 与各种技术和框架(如Hibernate、JPA、JMS、Quartz等)兼容,提供了广泛的集成支持。
- 社区支持和文档:
- 拥有活跃的社区支持和详细的文档,有助于解决开发过程中的问题和学习框架。
- 测试支持:
- 提供了对单元测试和集成测试的支持,简化了测试的编写和执行。
- 事务管理:
- 支持声明式事务管理,允许开发者以声明的方式处理事务,提高了事务管理的灵活性和可控性。
缺点
- 学习曲线:
- Spring框架的学习曲线较陡,特别是对初学者来说。理解和掌握其核心概念(如DI、AOP)和相关配置可能需要时间。
- 复杂性:
- 大型Spring应用程序可能会变得复杂,配置和管理的难度也会增加,尤其是在使用多个模块和集成时。
- 配置文件较多:
- Spring框架需要管理大量的配置文件,这些文件必须进行恰当的设置和管理,否则可能导致应用程序出现各种问题。
- 性能开销:
- 由于依赖注入和AOP等功能可能引入额外的性能开销,尽管在大多数情况下这种开销是可以接受的,但在高性能要求的场景下需谨慎使用。
- 配置管理:
- 尽管Spring提供了多种配置方式,但在大型项目中,配置文件和类的管理可能会变得繁琐。
- 过度使用:
- 在某些情况下,Spring框架的功能可能被过度使用,导致项目过度复杂化或引入不必要的功能。
- 版本兼容性:
- 随着Spring框架的版本更新,某些功能和配置可能会发生变化,这可能导致现有代码的兼容性问题。
- Spring框架频繁更新,新版本的发布可能会引入破坏性变更,需要开发者不断学习和适应。
Spirng 核心
1、IOC(Inversion of Control,控制反转)
IOC是Spring容器的核心,它并不是一种技术,而是一种设计思想。IOC将对象的使用和创建分开,把对象的创建和生命周期的管理,以及相关的依赖资源获取等控制权,反转给Spring容器,而不是由对象自身管理。这有助于降低代码之间的耦合度,提高模块化和可维护性。
具体来说,IOC在Spring中可以理解为一个Map容器,用于存放和管理Bean对象,并控制Bean的生命周期。当服务启动时,JVM会扫描jar包或war包中的class文件,找到配置类,并将加了相关注解的实例Bean加入到IOC容器中。这样,开发者在使用对象时,就无需自己创建对象和控制对象的生命周期,只需从IOC容器中获取所需的对象即可。
IOC的主要作用包括:
- 创建对象:IOC容器可以自动创建Bean对象,而无需手动new出对象。
- 管理对象的依赖关系:通过配置文件或注解,可以描述类和类之间的依赖关系,IOC容器会自动完成依赖关系的装配。
- 避免大量单例模式和工厂模式的泛滥:IOC容器可以管理多个Bean对象,并控制它们的生命周期,从而避免了大量单例模式和工厂模式的使用。
IOC的实现方式:
先创建两个类User
和Profile
类
@Data
public class User {
public User(int id, String username, String password, String email, String name, Profile profile) {
this.id = id;
this.username = username;
this.name = name;
this.password = password;
this.email = email;
this.profile = profile;
}
public User() {
}
private int id;
private String username;
private String name;
private String password;
private String email;
private Profile profile;
}
@Data
public class Profile {
private String name;
private String birthday;
private String address;
private String phoneNumber;
}
- 构造器注入:通过构造器参数传递依赖对象。
构造器注入是通过对象的构造器将依赖项注入到对象中。这种方式确保了对象在创建时就处于完全初始化的状态。
在applicationContext.xml
文件添加以下配置
<!-- Bean构造函数注入 1.通过name属性名称进行注入 -->
<bean id="user" class="org.example.model.User">
<constructor-arg value="xx@qq.com" name="email"/>
<constructor-arg value="xx123" name="username"/>
<constructor-arg value="1" name="id"/>
<constructor-arg value="xxxxxxx" name="password"/>
<constructor-arg value="xxx" name="name"/>
<constructor-arg ref="profile" name="profile"/>
</bean>
<!-- Bean构造函数注入 2.通过index下标进行注入 -->
<bean id="user1" class="org.example.model.User">
<constructor-arg value="xx@qq.com" index="3"/>
<constructor-arg value="xx123" index="1"/>
<constructor-arg value="1" index="0"/>
<constructor-arg value="xxxxxxx" index="2"/>
<constructor-arg value="xxx" index="4"/>
<constructor-arg ref="profile" index="5"/>
</bean>
- Setter注入:通过Setter方法设置依赖对象。
Setter注入是通过对象的setter方法将依赖项注入到对象中。这种方式允许在对象创建后更改其依赖项。
在applicationContext.xml
文件添加以下配置
<!-- Bean Set注入 -->
<bean id="profile" class="org.example.model.Profile">
<property value="xxx" name="name"/>
<property name="birthday" value="2024-10-25"/>
<property value="xx省xx市xx区" name="address"/>
<property value="135xxxx9586" name="phoneNumber"/>
</bean>
在测试类上面添加以下方法
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class Test {
// 构造器注入
@org.junit.Test
public void constructorSet(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取应用上下文
User user = (User) context.getBean("user"); // 获取Bean实例,强转为实体类,通过name属性注入
System.out.println("user:" + user);
User user1 = (User) context.getBean("user1"); // 获取Bean实例,强转为实体类,通过index下标进行注入
System.out.println("user1:" + user1);
}
}
启动该方法,获取结果
user:User(id=1, username=xx123, name=xxx, password=xxxxxxx, email=xx@qq.com, profile=Profile(name=xxx, birthday=2024-10-25, address=xx省xx市xx区, phoneNumber=135xxxx9586))
user1:User(id=1, username=xx123, name=xxx, password=xxxxxxx, email=xx@qq.com, profile=Profile(name=xxx, birthday=2024-10-25, address=xx省xx市xx区, phoneNumber=135xxxx9586))
- 字段注入(注解注入):直接在对象字段上使用注解注入依赖。
字段注入是直接在对象的字段上注入依赖项。这种方式简单直接,但不够灵活,因为它不允许在对象创建后更改其依赖项。
@Autowire
注解,@Autowire
主要有三个属性值:constructor
,byName
,byType
。
constructor: 通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。
byName: 被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。
byType: 查找所有的set方法,将符合参数类型的bean注入。
@Autowire
的局限性和解决方法
-
局限性
- 当通过类型匹配找不到唯一的 Bean 时,Spring 会抛出异常。例如,如果容器中有两个
UserRepository
的实现类,且没有明确指定要注入哪一个,就会出现问题。
- 当通过类型匹配找不到唯一的 Bean 时,Spring 会抛出异常。例如,如果容器中有两个
-
解决方法
- 可以使用
@Qualifier
注解来指定要注入的 Bean 的名称。例如,如果有UserRepositoryImpl1
和UserRepositoryImpl2
两个实现类,并且想注入UserRepositoryImpl1
,可以这样修改UserService
类。 - 实现
@Autowired @Qualifier("userRepositoryImpl1") private UserRepository userRepository;
- 可以使用
主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
- @Component: 可以用于注册所有bean
- @Repository: 主要用于注册dao层的bean
- @Controller: 主要用于注册控制层的bean
- @Service: 主要用于注册服务层的bean
-
在
User
类上面添加@Component
注解 -
在测试类上面添加以下方法
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class Test {
@Resource
private User user;
// 字段注入
@org.junit.Test
public void fieldInjection(){
user.setName("xxx");
user.setId(1);
user.setPassword("xxxx");
user.setUsername("xxxx");
user.setEmail("xxxx@qq.com");
System.out.println("user:" + user);
}
}
测试结果
user:User(id=1, username=xxxx, name=xxx, password=xxxx, email=xxxx@qq.com, profile=Profile(name=xxx, birthday=2024-10-25, address=xx省xx市xx区, phoneNumber=135xxxx9586))
2、AOP(Aspect Oriented Programming,面向切面编程)
概念:
面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP是Spring框架的另一个核心特性,它提供了对横切关注点(如日志、事务管理等)的模块化支持。通过AOP,可以将横切关注点与业务逻辑代码分离,从而提高代码的可维护性和可重用性。
Spring AOP模块提供了满足AOP Alliance规范的实现,并整合了AspectJ这种AOP语言级的框架。通过AOP,可以方便地进行面向切面编程,实现如日志记录、事务管理、权限控制等横切关注点的模块化。
Spirng 7大核心模块
1. Spring Core(核心容器)
1.1 主要功能和特点
- 控制反转(IoC):
- Spring Core 通过 IoC 容器管理应用程序中的对象(称为 Beans)的创建、生命周期和依赖关系。
- 一种设计原则,通过将对象的创建和管理交给容器来实现,应用程序只需要关注业务逻辑,而不需要关心对象的创建和生命周期管理。
- Spring 容器负责创建和管理对象,应用程序可以通过依赖注入的方式获取所需的对象,从而降低了对象之间的耦合度,提高了代码的可维护性和可测试性。
- 依赖注入(DI):
- Spring Core 支持依赖注入,这是一种将对象之间的依赖关系自动连接起来的方法,从而降低了代码的耦合度。
- 管理对象之间的依赖关系,将对象的创建和组装职责从应用代码转移到容器中。通过构造函数注入、Setter 方法注入和字段注入等方式,实现对象之间的松耦合。
- 例如,一个服务类需要依赖一个数据访问对象(DAO),可以在服务类的构造函数中接收 DAO 对象作为参数,或者通过 Setter 方法设置 DAO 对象,Spring 容器会在运行时自动将 DAO 对象注入到服务类中。
- 面向切面编程(Aspect-Oriented Programming,AOP):
- 一种编程思想,将横切关注点(如日志记录、事务管理、安全检查等)从业务逻辑中分离出来,通过切面(Aspect)和通知(Advice)的方式在不修改业务代码的情况下为业务方法添加额外的功能。
- Spring AOP 基于代理模式实现,可以在运行时动态地为目标对象生成代理对象,在代理对象中织入切面逻辑,从而实现对目标对象的增强。
- Bean 工厂:
- 提供了一个中央化的 Bean 工厂,用于配置和管理应用程序中的 Beans。
- Bean 生命周期管理:
- 允许开发者通过实现特定的接口或使用注解来控制 Bean 的生命周期。
- 类型转换和数据绑定:
- 提供了强大的类型转换和数据绑定机制,可以将外部数据(如表单提交的数据)自动转换并绑定到 Java 对象的属性上。
- 事件处理:
- 支持事件驱动模型,允许应用程序的不同部分通过事件进行通信。
- 定义了一套事件处理机制,允许 Bean 发布和监听应用中的事件。首先,定义一个事件类继承自
ApplicationEvent
,然后在需要发布事件的地方通过ApplicationContext
发布事件:
- 资源抽象:
- 提供了一套资源抽象机制,使得访问不同类型(如文件、类路径资源、URL资源等)的资源变得更加简单和统一。
- 外部资源访问:可以方便地加载和访问各种外部资源,如属性文件、XML 文件等。通过
ResourceLoader
接口和ApplicationContext
实现类,可以轻松地获取资源的输入流进行读取。例如:
- 异常处理:
- 提供了统一的异常处理机制,允许开发者捕获和处理整个应用程序范围内的异常。
- 表达式语言(SpEL):
- 提供了一个强大的表达式语言,用于在运行时查询和操作对象图。
- AOP 支持:
- 提供了对面向切面编程的支持,允许开发者将横切关注点(如日志记录、事务管理)与业务逻辑分离。
- 模块化:
- Spring Core 是高度模块化的,允许开发者根据需要选择使用特定的模块。
- 兼容性:
- 与 Java 平台标准兼容,支持 Java SE 和 Java EE。
- 集成性:
- 可以与其他 Java 技术(如 JDBC、JMS、JPA、Hibernate 等)和框架无缝集成。
- 测试支持:
- 提供了对单元测试和集成测试的支持,使得测试 Spring 应用程序变得更加容易。
- 无代码侵入性:
- 通过使用接口和注解,Spring Core 允许开发者以非侵入的方式构建应用程序。
- 可扩展性:
- 允许开发者通过自定义扩展来满足特定的需求。
1.2 代码示例:
XML文件
resources目录下的文件
<!-- applicationContext.xml -->
<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="myBean" class="com.example.MyBean">
<property name="name" value="Spring Core Bean"/>
</bean>
</beans>
Java 代码:
public class MyBean {
private String name;
public void setName(String name) {
this.name = name;
}
public void printName() {
System.out.println("Bean name: " + name);
}
}
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取应用上下文
MyBean myBean = (MyBean) context.getBean("myBean"); // 获取Bean实例,强转为实体类
myBean.printName(); // 执行实体类方法
}
}
2. Spring Context(应用上下文)
2.1 主要功能和特点
- 扩展了BeanFactory:Spring Context扩展了Spring Core中的BeanFactory,提供了更完整的框架式对象访问方式。它不仅支持Bean的创建和管理,还提供了对国际化(I18N)、事件传播、资源加载等功能的支持。
- 应用上下文:Spring Context引入了应用上下文(ApplicationContext)的概念,它是BeanFactory的一个超集。ApplicationContext提供了更丰富的功能,比如自动装配、事件发布、资源加载等。
- 国际化支持:通过MessageSource接口,Spring Context支持消息的国际化,使得应用可以根据用户的地区设置来显示不同语言的消息。
- 事件传播:Spring Context提供了应用事件和监听器的支持,允许Bean之间通过事件进行通信。
- 资源加载:Spring Context提供了对资源的统一加载方式,包括类路径资源、文件系统资源、URL资源等。
- 环境抽象:Spring 3.1及以后版本引入了Environment抽象,它封装了配置的属性,并允许你区分配置文件(profiles)和环境。
- 生命周期回调:Spring Context支持Bean的生命周期回调,允许你在Bean的创建、初始化、销毁等阶段执行自定义的代码。
2.2 常见实现类
类型名 | 简介 |
---|---|
ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象(当前工程下获取)用的比较多 |
FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象(从磁盘中获取) |
ConfigurableApplicationContex | ConfigurableApplicationContext是Spring框架的核心接口之一,它定义了用于配置和管理Spring应用上下文的一系列方法。这些方法允许开发者在运行时动态地修改和管理应用上下文,包括设置上下文ID、设置父应用上下文、添加监听器、刷新容器、关闭上下文等。此外,ConfigurableApplicationContext还提供了启动、刷新和关闭Spring应用上下文的功能。 |
WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中 |
AnnotationConfigApplicationContext | 它用于加载和注册注解配置的 Spring 组件。这个类是专为基于 Java 配置的应用程序设计的,它允许你通过注解(如 @Configuration 、@Bean 、@Component 等)来定义 Spring 容器的配置,而不是使用传统的 XML 配置文件。 |
1.ClassPathXmlApplicationContext
:从类路径下加载 XML 配置文件来创建应用上下文。
- 示例代码:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
2.FileSystemXmlApplicationContext
:从文件系统路径下加载 XML 配置文件创建应用上下文。
- 示例代码:
ApplicationContext context = new FileSystemXmlApplicationContext("path/to/applicationContext.xml");
3.AnnotationConfigApplicationContext
:通过 Java 配置类来创建应用上下文。
- 示例代码:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
3. Spring AOP(Aspect - Oriented Programming)(面向切面编程)
3.1 概念:
面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
3.2 功能:
- 横切关注点:指的是在多个业务逻辑模块中都普遍存在的非业务核心逻辑,例如日志记录、事务管理、安全检查等。这些横切关注点如果分散在各个业务逻辑代码中,会导致代码重复、可维护性差等问题。分离业务逻辑和跨切关注点(如事务、日志)的关注,使代码更模块化。
- 切面(Aspect):是对横切关注点的抽象,它将横切逻辑封装起来。在 Spring AOP 中,切面可以通过类来实现,这个类中定义了在何处(切点)以及如何(增强)执行横切逻辑。
- 连接点(Join Point):在程序执行过程中能够插入切面的点,例如方法调用、异常抛出等。Spring AOP 主要支持方法调用作为连接点。
- 通知(Advice):在连接点处执行的具体操作,包括前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After,无论方法执行结果如何都会执行)和环绕通知(Around,可以在方法调用前后进行自定义行为)。
- 切入点(Pointcut):用于定义在哪些连接点上应用切面的规则。它可以是基于方法签名、注解、表达式等多种方式来指定。例如,通过表达式
execution(* com.example.service.*.*(..))
可以指定对com.example.service
包下所有类的所有方法应用切面。 - 织入(Weaving):将切面(aspects)与核心业务逻辑代码结合在一起的过程。它把横切关注点的代码(在切面中定义)集成到应用程序的目标对象的执行流程中。可以在编译时、类加载时或运行时进行。
3.3 特点:
- 声明性事务管理:Spring AOP提供了声明性事务管理,使得事务管理变得简单且易于维护。
- 灵活性:通过定义切面,可以灵活地添加、删除或修改横切关注点,而无需修改业务逻辑代码。
- 解耦:AOP通过将横切关注点与业务逻辑分离,实现了关注点之间的解耦,提高了代码的可维护性和可扩展性。
- 易于测试:由于横切关注点被封装在单独的模块中,因此可以更容易地对它们进行测试。
- 集成性:Spring AOP与Spring框架的其他部分(如IoC容器)紧密集成,使得在Spring应用程序中使用AOP变得非常简单。
- 支持多种通知类型:Spring AOP支持多种类型的通知,包括环绕通知,它允许在方法执行前后添加自定义行为,并控制方法的执行。
- 性能考虑:虽然AOP带来了许多好处,但它也可能对性能产生一定影响,因为代理的创建和通知的执行都需要额外的开销。然而,在大多数情况下,这种影响是可以接受的。
3.4 应用场景
- 事务管理(Transaction Management):在业务方法执行前开启事务,在业务方法执行成功后提交事务,在出现异常时回滚事务,保证数据的一致性。
- 事务管理(Transaction Management):在方法调用前后记录方法的输入参数、输出结果以及执行时间等信息,方便进行系统监控和问题排查。
- 权限验证:在执行需要权限控制的方法前检查当前用户是否具有相应的权限,确保系统的安全性。
- 异常处理(Exception Handling): AOP可以捕获方法执行过程中的异常,并进行统一处理和日志记录。
3.5 实现原理
Spring AOP基于动态代理技术实现
动态代理机制
JDK 动态代理:
- 原理
- JDK 动态代理是基于 Java 的反射机制实现的。当调用代理对象的方法时,实际上是调用了
InvocationHandler
接口的invoke
方法,在这个方法中可以对被代理对象的方法进行增强处理。
- JDK 动态代理是基于 Java 的反射机制实现的。当调用代理对象的方法时,实际上是调用了
- 使用条件
- 被代理的类必须实现了接口。因为 JDK 动态代理是通过代理接口来实现的,它会在运行时动态地创建一个实现了指定接口的代理类。
- 实现步骤
- 定义一个接口,该接口包含被代理类需要实现的方法。
- 创建一个实现了
InvocationHandler
接口的类,在这个类中重写invoke
方法,在invoke
方法中编写代理逻辑。 - 使用
Proxy.newProxyInstance
方法创建代理对象,该方法需要传入类加载器、被代理类实现的接口数组以及InvocationHandler
的实现类对象。
当被代理的目标对象实现了至少一个接口时,Spring AOP 默认会使用 JDK 动态代理。JDK 动态代理是基于 Java 的反射机制实现的,它在运行时创建代理类的字节码,该代理类实现了与目标对象相同的接口,并将方法调用转发到目标对象,同时可以在代理类中添加额外的横切逻辑(如通知 Advice)。例如,以下是一个简单的 JDK 动态代理的示例代码:
JDKProxyExample
类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
interface HelloInterface {
void sayHello();
}
// 实现接口的类
class Hello implements HelloInterface {
@Override
public void sayHello() {
System.out.println("Hello!");
}
}
// 实现 InvocationHandler 接口
class HelloInvocationHandler implements InvocationHandler {
private Object target;
public HelloInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoking sayHello");
Object result = method.invoke(target, args);
System.out.println("After invoking sayHello");
return result;
}
}
public class JDKProxyExample {
public static void main(String[] args) {
HelloInterface hello = new Hello();
// 创建代理对象
HelloInterface proxy = (HelloInterface) Proxy.newProxyInstance(
hello.getClass().getClassLoader(),
hello.getClass().getInterfaces(),
new HelloInvocationHandler(hello)
);
proxy.sayHello();
}
}
CGLIB 动态代理:
- 原理
- CGLIB(Code Generation Library)是一个基于 ASM(Java 字节码操作框架)的字节码生成库。它通过在运行时动态地生成被代理类的子类来实现代理。当调用代理对象的方法时,实际上是调用了子类覆盖的方法,在子类的方法中可以添加增强逻辑。
- 使用条件
- 不需要被代理的类实现接口,它可以代理普通的类(即没有实现任何接口的类)。
- 实现步骤
- 导入 CGLIB 相关的依赖库。
- 创建一个实现了
MethodInterceptor
接口的类,在这个类中重写intercept
方法,在intercept
方法中编写代理逻辑。 - 使用
Enhancer
类来创建代理对象,设置被代理类、Callback
对象(通常是MethodInterceptor
的实现类对象)等参数
当被代理的目标对象没有实现任何接口时,Spring AOP 会使用 CGLIB(Code Generation Library)动态代理。CGLIB 通过继承目标对象来创建代理对象,它在运行时生成目标对象的子类,并重写子类中的方法来添加横切逻辑。CGLIB 代理不需要接口,但要求目标对象不能是 final 类,因为 final 类不能被继承。以下是一个 CGLIB 动态代理的示例代码:
pom.xml
依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
CGLIBProxyExample
类
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 没有实现接口的类
class HelloClass {
public void sayHello() {
System.out.println("Hello!");
}
}
// 实现 MethodInterceptor 接口
class HelloMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before invoking sayHello");
Object result = proxy.invokeSuper(obj, args);
System.out.println("After invoking sayHello");
return result;
}
}
public class CGLIBProxyExample {
public static void main(String[] args) {
HelloClass hello = new HelloClass();
// 创建增强器对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(hello.getClass());
enhancer.setCallback(new HelloMethodInterceptor());
// 创建代理对象
HelloClass proxy = (HelloClass) enhancer.create();
proxy.sayHello();
}
}
JDK动态代理和CGLIB动态代理的区别
- 实现机制
- JDK动态代理:基于Java反射机制,通过java.lang.reflect.Proxy类和InvocationHandler接口实现。它要求被代理的目标类必须实现一个或多个接口。
- CGLIB动态代理:基于底层的字节码操作(使用ASM库),通过生成目标类的子类来实现代理。它可以代理没有实现接口的普通类。
- 使用限制
- JDK动态代理:只能代理实现了接口的类,不能代理普通类。方法调用时使用反射,性能相对较低。
- CGLIB动态代理:可以代理没有实现接口的类,也可以代理实现了接口的类。避免了反射调用,性能较高,但在创建代理类时需要进行字节码操作,性能开销较大。
- 性能特点
- JDK动态代理:在Java 1.8及以上版本中,由于JVM的优化,其性能通常优于CGLIB。
- CGLIB动态代理:在创建代理这一块没有JDK动态代理快,但是运行速度比JDK动态代理要快。
- 适用场景
- JDK动态代理:适用于目标类实现了接口的情况,特别是当需要代理的是一个接口的实现类时。
- CGLIB动态代理:适用于目标类没有实现接口的情况,或者需要在运行时动态添加方法到目标类中。
4. Spring DAO(JDBC抽象和DAO模块)
4.1 主要功能和特点
- JDBC抽象: Spring提供了JDBC抽象层,它封装了JDBC API的复杂性,使得开发者可以使用更加简洁的代码来执行数据库操作。Spring的
JdbcTemplate
是这个抽象层的核心,它提供了一系列的便捷方法来执行SQL语句。 - DAO模块: Spring DAO模块包括了
DataSource
、SessionFactory
等组件,以及与之相关的配置和模板类。这些组件帮助开发者管理数据库连接和会话,简化了数据访问层的实现。 - 事务管理: Spring提供了声明式事务管理,允许开发者通过注解或配置文件来管理事务的边界和行为。Spring的事务管理可以与JDBC、Hibernate、JPA等ORM框架集成,提供了一致的事务管理策略。
- 数据访问模板: Spring提供了
JdbcDaoSupport
、HibernateDaoSupport
等数据访问模板,这些模板类提供了一些基础的数据访问方法,简化了DAO类的实现。 - 集成ORM框架: Spring DAO模块可以与流行的ORM框架(如Hibernate、JPA、MyBatis等)集成,提供了一致的编程模型来处理数据库操作。
- 数据访问接口: Spring鼓励使用数据访问接口(而不是具体的实现类)来定义数据访问层的接口。这样做可以提高代码的可测试性和可维护性。
- 异常处理: Spring对JDBC异常进行了封装,将它们转换为Spring的
DataAccessException
层次结构。这使得异常处理更加统一和简单。
4.2 核心概念
- 抽象和封装
- 它对底层的数据访问技术(如 JDBC、Hibernate、MyBatis 等)进行了抽象,将数据访问的具体实现细节封装在 DAO 层中。这样,业务逻辑层不需要了解底层数据库的操作细节,只需要调用 DAO 层提供的接口方法即可。
- 异常处理
- Spring DAO 提供了一套统一的异常体系,将不同数据访问技术抛出的特定异常转换为 Spring 中的
DataAccessException
及其子类。这些异常是未经检查的异常,开发人员可以选择在适当的层次处理它们,而不必在每个数据访问方法中都进行繁琐的异常捕获和处理。
- Spring DAO 提供了一套统一的异常体系,将不同数据访问技术抛出的特定异常转换为 Spring 中的
4.3 主要优势
- 可维护性和可测试性
- 通过将数据访问代码与业务逻辑代码分离,使得代码结构更加清晰,易于维护。在进行单元测试时,可以很方便地模拟 DAO 层的行为,而不需要依赖真实的数据库环境,从而提高了测试的效率和独立性。
- 灵活性和可扩展性
- 当需要切换数据访问技术(例如从 JDBC 切换到 Hibernate)时,只需要在 DAO 层进行相应的配置更改,而不需要修改业务逻辑层的代码。这使得系统在面对不同的数据存储需求时具有更大的灵活性和可扩展性。
4.4 JdbcTemplate模板
JdbcTemplate
是 Spring 提供的工具,旨在简化 JDBC 操作。它通过封装常见的数据库交互模式,提供了便捷的方法来执行 SQL 查询、更新和批处理操作,减少了样板代码,提升了开发效率。此外,它还支持异常处理和事务管理,使得数据库操作更为高效和安全。
方法 | 说明 | 参数示例 |
---|---|---|
queryForObject(String sql, Class<T> requiredType) | 执行查询语句,返回指定类型的单个对象,常用于聚合函数查询结果获取 | jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Integer.class) |
queryForObject(String sql, Object[] args, Class<T> requiredType) | 执行带参数查询,返回指定类型单个对象,参数通过数组传递 | Object[] args = {1}; jdbcTemplate.queryForObject("SELECT name FROM users WHERE id =?", args, String.class) |
queryForList(String sql) | 执行查询,返回元素为Map 的List 集合,用于多行多列查询结果 | jdbcTemplate.queryForList("SELECT * FROM users") |
queryForList(String sql, Object[] args) | 执行带参数查询,返回元素为Map 的List 集合,参数通过数组传递 | Object[] args = {"John"}; jdbcTemplate.queryForList("SELECT * FROM users WHERE name =?", args) |
query(String sql, RowMapper<T> rowMapper) | 执行查询,通过自定义RowMapper 将结果集每行映射为 Java 对象 | jdbcTemplate.query("SELECT * FROM users", (rs, rowNum) -> new User(rs.getInt("id"), rs.getString("name"))) |
query(String sql, Object[] args, RowMapper<T> rowMapper) | 执行带参数查询,通过RowMapper 将结果集映射为 Java 对象,参数通过数组传递 | Object[] args = {1}; jdbcTemplate.query("SELECT * FROM orders WHERE user_id =?", args, (rs, rowNum) -> new Order(rs.getInt("order_id"), rs.getDate("order_date"))) |
update(String sql) | 执行增、删、改操作的 SQL 语句,返回受影响行数 | jdbcTemplate.update("DELETE FROM users WHERE id = 1") |
update(String sql, Object[] args) | 执行带参数的增、删、改操作,参数通过数组传递,返回受影响行数 | Object[] args = {"John", 30}; jdbcTemplate.update("INSERT INTO users (name, age) VALUES (?,?)", args) |
batchUpdate(String sql, List<Object[]> batchArgs) | 执行批量更新操作,List<Object[]> 中每个数组是一组参数 | List<Object[]> batchArgs = new ArrayList<>(); batchArgs.add(new Object[]{"Alice", 25}); batchArgs.add(new Object[]{"Bob", 32}); jdbcTemplate.batchUpdate("INSERT INTO users (name, age) VALUES (?,?)", batchArgs) |
execute(String sql) | 执行任意 SQL 语句,可用于 DDL 等操作 | jdbcTemplate.execute("CREATE TABLE new_table (id INT, name VARCHAR(50))") |
queryForMap(String sql) | 执行查询,返回一个Map ,适用于查询一行多列数据 | jdbcTemplate.queryForMap("SELECT id, name FROM users WHERE id = 1") |
queryForMap(String sql, Object[] args) | 执行带参数查询,返回一个Map ,参数通过数组传递 | Object[] args = {1}; jdbcTemplate.queryForMap("SELECT id, name FROM users WHERE id =?", args) |
execute(String sql, PreparedStatementCallback<T> action) | 执行给定 SQL 和PreparedStatementCallback ,自定义PreparedStatement 操作 | jdbcTemplate.execute("SELECT * FROM users", ps -> { ps.setInt(1, 1); return ps.executeQuery(); }) |
execute(ConnectionCallback<T> action) | 在 JDBC 连接上执行操作,通过ConnectionCallback 操作连接 | jdbcTemplate.execute(connection -> { Statement stmt = connection.createStatement(); return stmt.executeQuery("SELECT * FROM users"); }) |
4.5 Spring DAO实战
1、添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.20</version> <!-- 请根据需要选择合适的版本 -->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.20</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
2、创建实体类
在org.example
下创建model
包并创建User
类
注意: 需要先根据实体类创建对应的数据库表
@Data
public class User {
private int id;
private String username;
private String name;
private String password;
private String email;
}
3、创建数据访问层 DAO 接口
在org.example
下创建dao
包并创建UserDao
接口
public interface UserDao {
User getUserById(int id);
List<User> getUserList(Integer id, String email, String name);
void addUser(User user);
void updateUser(User user);
void deleteUser(int id);
}
4、实现数据访问层 DAO 接口
在org.example
下创建dao
包并创建UserDaoImpl
接口实现类
@Repository
public class UserDaoImpl implements UserDao {
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public User getUserById(int id) {
String sql = "SELECT * FROM sys_user WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper<>(User.class));
}
@Override
public List<User> getUserList(Integer id, String email, String name) {
String sql = "SELECT * FROM sys_user WHERE (id = ? OR email LIKE ? OR name LIKE ?)";
return jdbcTemplate.query(sql, new Object[]{id,"%" + email + "%","%" + name + "%"}, new BeanPropertyRowMapper<>(User.class));
}
@Override
public void addUser(User user) {
String sql = "INSERT INTO sys_user (name, email, password, username) VALUES (?, ?, ?, ?)";
jdbcTemplate.update(sql, user.getName(), user.getEmail(), user.getPassword(), user.getUsername());
}
@Override
public void updateUser(User user) {
String sql = "UPDATE sys_user SET name = ?, email = ?, password = ?, username = ? WHERE id = ?";
jdbcTemplate.update(sql, user.getName(), user.getEmail(), user.getPassword(), user.getUsername(), user.getId());
}
@Override
public void deleteUser(int id) {
String sql = "DELETE FROM sys_user WHERE id = ?";
jdbcTemplate.update(sql, id);
}
}
5、创建服务层 Service 接口
在org.example
下创建service
包并创建UserService
接口
public interface UserService {
User getUserById(int id);
List<User> getUserList(Integer id, String email, String name);
void addUser(User user);
void updateUser(User user);
void deleteUser(int id);
}
6、实现数据访问层 Service 接口
在org.example
下创建service
包并创建UserServiceImpl
接口实现类
@Service
public class UserServiceImpl implements UserService{
@Resource
private UserDao userDao;
@Override
public void addUser(User user) {
userDao.addUser(user);
}
@Override
public void updateUser(User user) {
userDao.updateUser(user);
}
@Override
public void deleteUser(int id) {
userDao.deleteUser(id);
}
@Override
public User getUserById(int id) {
return userDao.getUserById(id);
}
@Override
public List<User> getUserList(Integer id, String email, String name){
return userDao.getUserList(id,email,name);
}
}
7、配置applicationContext.xml
文件
<!-- applicationContext.xml -->
<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
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--定义数据源(dataSource)-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://ip地址:端口/数据库名" />
<property name="username" value="账号" />
<property name="password" value="密码" />
</bean>
<!--组件扫描(<context:component-scan>-->
<context:component-scan base-package="org.example"/> <!--指示 Spring 在org.example包及其子包中扫描并自动注册 Spring 组件,例如带有@Component、@Service、@Repository等注解的类。-->
<!--创建JdbcTemplate实例(jdbcTemplate)-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/> <!--释义:将前面定义的dataSource注入到JdbcTemplate中,使得JdbcTemplate可以使用这个数据源来执行数据库操作-->
</bean>
</beans>
8、创建测试了进行测试
在测试源跟目录下面创建测试类进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class Test {
@Resource
private UserService userService;
@org.junit.Test
public void addUser(){
// 创建一个新用户
User user = new User();
user.setUsername("new_user1");
user.setPassword("new_password");
user.setEmail("new_email1@163.com");
user.setName("晚秋的风");
// 调用服务层方法添加用户
userService.addUser(user);
System.out.println("添加成功");
// 调用服务层方法获取用户信息
user = userService.getUserById(1);
System.out.println("获取用户信息:" + user.toString());
}
@org.junit.Test
public void updateUser(){
// 创建一个新用户
User user = userService.getUserById(1);
user.setUsername("old_user");
user.setPassword("old_password");
user.setEmail("old_email@163.com");
user.setName("old晚秋的风");
// 调用服务层方法更新用户信息
userService.updateUser(user);
System.out.println("更新成功");
// 调用服务层方法获取用户信息
user = userService.getUserById(1);
System.out.println("获取用户信息:" + user.toString());
}
@org.junit.Test
public void deleteUser(){
// 调用服务层方法删除用户
userService.deleteUser(1);
System.out.println("删除成功");
}
@org.junit.Test
public void getUserById(){
// 调用服务层方法获取用户信息
User user = userService.getUserById(5);
System.out.println("获取用户信息:" + user.toString());
}
@org.junit.Test
public void getUserList(){
Integer id = null;
String email = "@";
String name = "晚";
// 调用服务层方法获取用户信息
List<User> userList = userService.getUserList(id,email,name);
System.out.println("获取用户信息:");
userList.forEach(System.out::println);
}
}
5. Spring ORM(对象/关系映射集成模块)
5.1 定义
Spring ORM 是 Spring 框架中的一个重要组成部分,它提供了对对象 / 关系映射(ORM)框架的集成支持。ORM 的主要目的是在面向对象的编程模型和关系型数据库之间建立一种映射关系,使得开发人员可以使用面向对象的方式来操作关系型数据库,而不必编写大量的 SQL 语句。
5.2 主要功能和特点
- 核心组件:Spring ORM的核心组件包括JdbcTemplate、HibernateTemplate、SqlSessionTemplate等。这些组件为不同的持久化技术提供了统一的编程模型。
- 事务管理:Spring ORM支持声明式和编程式两种事务管理方式。声明式事务管理通常通过注解(如@Transactional)或XML配置来实现,而编程式事务管理则需要在代码中显式地开启、提交或回滚事务。
- 异常体系:Spring ORM定义了一套自己的异常体系,将不同持久化技术的异常转换为统一的Spring DataAccessException层次结构,便于处理和日志记录。
- 集成多种ORM框架:Spring ORM可以与多种ORM框架集成,包括但不限于Hibernate、JPA、MyBatis等。通过整合这些框架,Spring ORM允许开发者选择最适合项目需求的ORM解决方案。
- 数据源配置:在使用Spring ORM时,需要配置数据源,这可以通过XML配置文件或Java配置类来完成。数据源的配置包括指定数据库连接信息,如URL、用户名、密码等。
- 实体管理:实体类通常对应于数据库中的表,每个实体类的实例对应表中的一行记录。实体类需要使用注解或XML配置文件来映射到数据库表和字段。
- 查询方法:Spring ORM提供了丰富的查询方法,包括基于SQL语句的查询、基于命名查询的方法以及基于Criteria API的动态查询等。
- 缓存支持:为了提高性能,Spring ORM支持集成缓存机制,例如EhCache、Redis等。通过缓存可以减少对数据库的访问次数,从而提高应用的性能。
- 测试支持:Spring ORM支持使用Mock对象进行单元测试和集成测试,这使得开发者可以在不依赖实际数据库环境的情况下测试数据访问层的代码。
- 安全性:Spring Security可以与Spring ORM结合使用,提供安全的数据访问控制。通过配置,可以限制用户只能访问他们被授权的数据。
5.3 ORM框架
6. Spring MVC(MVC模块)
6.1 为什么要使用SpringMVC?
Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的Spring MVC框架或集成其他MVC开发框架,如Struts1(现在一般不用),Struts 2(一般老项目使用)等等。
6.2 gon
- 功能:是一个全功能的构建Web应用程序的MVC实现,通过策略接口,MVC框架编程高度可配置的。
- 特点:将应用程序的业务逻辑、表现层和数据层进行分离,提高了应用程序的可维护性和可扩展性。支持多种视图技术(如JSP、Velocity等),并通过注解和配置简化Web开发的复杂度。
6.3 MVC:
把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)
- Model(模型):业务模型,负责在数据库中存取数据,对应项目只读service和dao
- View(视图):渲染数据,生成页面,是应用程序中处理数据显示的部分。对应项目中的Jsp
- Controller(控制器):控制MVC流程,接收用户在View层的输入,调用Model层获取数据,填充给View层。对应项目中的Servlet
通常控制器负责从视图读取用户输入的数据,然后从模型中请求数据,并将其交给视图
6.4 SpringMVC流程
具体流程如下:
- 客户端请求被DispatcherServlet接收
- 根据HandlerMapping映射到Handler
- 生成Handler和HandlerInterceptor
- Handler和HandlerInterceptor以HandlerExecutionChain的形式一并返回给DispatcherServlet
- DispatcherServlet通过HandlerAdapter调用Handler(常称为 Controller)的方法完成业务逻辑处理
- 返回一个ModelAndView对象给DispatcherServlet
- DispatcherServlet把获取的ModelAndView对象传给ViewResolver视图解析器,把逻辑视图解析成物理视图
- ViewResolver返回一个View进行视图渲染(把模型填充到视图中)
- DispatcherServlet把渲染后的视图响应给客户端
6.5 SpringMVC实战
1、新建项目
2、配置Maven仓库路径
3、添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.19</version>
</dependency>
4、补充目录结构
5、创建控制器
在com.mvc目录下创建controller目录和HelloWorldController目录
@Controller
public class HelloWorldController {
@RequestMapping("/hello")
public ModelAndView hello() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("message", "Hello, World!");
modelAndView.setViewName("hello");
return modelAndView;
}
}
6、创建SpringMVC核心配置类
在resources目录下创建springmvc.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 配置自动扫包 -->
<context:component-scan base-package="com.mvc.controller"></context:component-scan>
<!-- 启用Spring MVC的注解驱动功能。它会自动注册一些默认的处理器映射器和处理器适配器,
以便能够处理基于注解的控制器方法(如@RequestMapping、@GetMapping、@PostMapping等)。
同时,它还会自动注册一些消息转换器,以便能够处理请求和响应中的JSON、XML等数据格式。
相当于RequestMappingHandlerMapping和RequestMappingHandlerAdapter的合集,配置了它就不需要额外配置另外两个了。-->
<mvc:annotation-driven/>
<!-- 2、处理器映射器:显式注册一个处理器映射器,用于根据注解(如@RequestMapping)将请求映射到对应的处理器方法。
但请注意,在启用了<mvc:annotation-driven/>后,这个bean通常会被自动注册,因此显式配置是多余的 -->
<!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>-->
<!-- 5、处理器适配器:配置一个处理器适配器,用于调用具体的处理器(控制器方法)并对其返回结果进行适配处理。
但请注意,在启用了<mvc:annotation-driven/>后,这个bean通常会被自动注册,因此显式配置是多余的 -->
<!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>-->
<!-- 7、视图解析器:将逻辑视图名解析为具体的视图资源路径,以便渲染模型数据并返回给用户。 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--给逻辑视图加上前缀和后缀 -->
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"></property>
<!--后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
7、配置核心控制器
修改SpringMVC
配置类web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- 1、配置核心控制器 前端控制器(DispatcherServlet) -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name> <!-- servlet名称 -->
<!-- 指定了这个 Servlet 的类为org.springframework.web.servlet.DispatcherServlet,它是 Spring MVC 框架中的核心控制器,负责接收所有的请求,并将请求分发给相应的处理组件。 -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- init-param 定义了Servlet的初始化参数
springmvc配置文件加载路径
1)默认情况下,读取WEB-INF下面的文件
2)可以改为加载类路径下(resources目录),加上classpath:
-->
<init-param>
<param-name>contextConfigLocation</param-name> <!-- 定义了初始化参数的名称 -->
<param-value>classpath:springmvc.xml</param-value> <!-- 定义对应的值 -->
</init-param>
<!--
DispatcherServlet对象创建时间问题
1)默认情况下,第一次访问该Servlet的创建对象,意味着在这个时间才去加载springMVC.xml
2)可以改变为在项目启动时候就创建该Servlet,提高用户访问体验。
<load-on-startup>1</load-on-startup>
数值越大,对象创建优先级越低! (数值越低,越先创建)
-->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 定义了Servlet的URL映射。
这里将dispatcherServlet映射到/,
意味着它将处理所有进入Web应用程序的请求(不包括直接请求的JSP页面或其他静态资源)。 -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name> <!-- servlet名称,用于将请求映射到对应的 Servlet。 -->
<!--/ 匹配所有的请求;(不包括.jsp)-->
<!--/* 匹配所有的请求;(包括.jsp)-->
<!--*.do拦截以do结尾的请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
8、创建JSP文件
在webapp
目录下创建index.jsp
文件
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false"%>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>Hello World JSP</h1>
</body>
</html>
在WEB-INF
目录下创建jsp
目录并创建hello.jsp
文件
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false"%>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>这是一个测试:${message}</h1>
</body>
</html>
9、最终目录结构
10、部署Spring MVC项目
-
点击当前文件 —> 编辑配置
-
点击
+
号,选择Tomcat服务器 —> 本地
-
点击部署 —>
+
号 —> 选择工件 —> 选择war
包 | 或者点击修复也可以
注意: 这一步非常重要,没有这一步项目跑不起来
- 点击服务器 —> 配置
tomcat
—> 选择对应的jdk
—> 自定义端口 —> 点击确认
11、启动项目,访问
启动项目
12、最终效果
图一
地址: http://localhost:8081
图二
地址: http://localhost:8081/hello
7. Spring Web
Spring Web是Spring框架中的一个模块,它基于Servlet API构建,为Web层开发提供了一整套完备的解决方案。以下是对Spring Web的详细介绍:
1、Spring Web的特点
- 原生产品:Spring Web是Spring家族的原生产品,与Spring框架中的其他基础设施如IOC容器等无缝对接。
- 前端控制器:提供了一个前端控制器DispatcherServlet,开发者无需额外开发控制器对象。DispatcherServlet负责统一处理请求和响应,是整个流程控制的中心。
- 自动绑定用户输入:能够自动绑定用户输入,并正确地转换数据类型,简化了开发流程。
- 代码简洁:代码清新简洁,大幅度提升了开发效率。
- 高组件化:内部组件化程度高,可插拔式组件即插即用,提高了系统的灵活性和可扩展性。
- 高性能:性能卓著,尤其适合现代大型、超大型互联网项目的要求。
2、Spring Web的功能
-
Spring MVC:实现了MVC(模型-视图-控制器)设计模式,支持请求的处理、视图的解析和数据的传递。提供了便捷的方法来构建RESTful Web服务,支持HTTP请求的映射和处理。
-
文件上传:提供处理文件上传的功能,简化了文件上传的流程。
-
WebSocket支持:支持WebSocket协议,允许在客户端和服务器之间进行双向通信。
-
国际化支持:提供国际化支持,方便开发多语言Web应用。
-
与其他Web技术的集成:支持与其他Web技术的集成,如Thymeleaf、JSP等视图解析器。
-
集成 Servlet API
-
Spring Web 无缝集成了 Java Servlet API,允许开发者在 Spring 容器管理下创建 Servlet、Filter 和 Listener 等组件。这使得 Web 应用的开发更加便捷,同时也能充分利用 Spring 的依赖注入和面向切面编程等特性来管理这些组件的生命周期和行为。
-
例如,可以通过在 Spring 配置文件中定义
@Bean
方法来创建一个 Servlet,并将其注入到其他组件中进行使用。
-
-
支持 RESTful Web 服务
-
Spring Web 提供了强大的支持来构建 RESTful Web 服务。通过使用
@RestController
注解,可以轻松创建处理 HTTP 请求并返回 JSON、XML 等格式数据的 RESTful 服务端点。 -
开发者可以利用
@RequestMapping
等注解来定义请求的 URL 路径和 HTTP 方法,从而实现对不同资源的访问和操作。
-
-
视图技术集成
-
Spring Web 支持多种视图技术,如 JSP、Thymeleaf、FreeMarker 等。通过配置视图解析器,可以将控制器返回的逻辑视图名转换为具体的视图资源,以便渲染数据并展示给用户。
-
例如,使用
InternalResourceViewResolver
可以将逻辑视图名解析为 JSP 页面的路径,从而实现动态页面的生成。
-
-
数据绑定和验证
-
Spring Web 提供了强大的数据绑定和验证功能。可以将 HTTP 请求中的参数自动绑定到 Java 对象中,并进行数据验证,确保输入数据的合法性。
-
通过使用
@Valid
注解和BindingResult
参数,可以在控制器方法中方便地进行数据验证和错误处理。
-
-
异常处理
-
Spring Web 提供了统一的异常处理机制,允许开发者定义全局的异常处理方法,以便在发生异常时能够返回友好的错误信息给客户端。
-
可以使用
@ControllerAdvice
注解来定义全局的异常处理类,并使用@ExceptionHandler
注解来指定处理特定类型异常的方法。
-
4、在 Web 应用开发中的优势
- 简化开发过程
- Spring Web 提供了丰富的功能和工具,大大简化了 Web 应用的开发过程。开发者无需关注底层的 Servlet 细节,只需专注于业务逻辑的实现。
- 通过依赖注入和面向切面编程等特性,使得代码更加易于维护和扩展。
- 强大的扩展性
- Spring Web 具有良好的扩展性,可以方便地集成其他第三方库和框架。例如,可以轻松集成 MyBatis、Hibernate 等数据访问框架,以及 Spring Security 等安全框架。
- 同时,Spring Web 也支持自定义扩展,可以根据具体需求开发自己的组件和功能。
- 测试友好
- Spring Web 提供了对测试的良好支持,可以方便地进行单元测试和集成测试。可以使用 Spring 的测试框架(如
SpringJUnit4ClassRunner
)来对控制器、服务层等进行测试,确保代码的质量和稳定性。
- Spring Web 提供了对测试的良好支持,可以方便地进行单元测试和集成测试。可以使用 Spring 的测试框架(如
总之,Spring Web 是一个功能强大、易于使用的 Web 开发框架,为开发者提供了丰富的功能和工具,帮助他们快速构建高效、稳定的 Web 应用程序。
1、SpringBean生命周期
Spring Bean的生命周期包含多个阶段,每个阶段都可以执行特定的操作。
Spring Bean的生命周期是指从Bean的创建到销毁所经历的一系列过程。下面将详细介绍Spring Bean的生命周期步骤,并通过代码实战来展示这些步骤。
1.1 SpringBean的生命周期步骤
- 实例化(Instantiation):
- Spring IoC容器根据配置文件或注解信息,通过反射机制创建Bean的实例对象。
- 属性赋值(Populate Properties):
- Spring容器将Bean所需的依赖注入到Bean的属性中。这通常涉及依赖注入(DI)的配置,如通过构造函数注入、Setter方法注入或字段注入等方式。
- 执行后置处理器前置处理方法:
- 在Bean初始化之前,Spring容器会调用
BeanPostProcessor
接口的postProcessBeforeInitialization
方法。这是Bean生命周期中的一个扩展点,允许开发者在Bean初始化之前进行一些定制操作。
- 在Bean初始化之前,Spring容器会调用
- 初始化(Initialization):
- 初始化阶段包括调用Bean的初始化方法,执行一些定制的初始化操作。这些初始化回调方法可以通过以下方式实现:
- 实现
InitializingBean
接口的afterPropertiesSet()
方法。 - 在配置文件中指定的自定义初始化方法(即
init-method
)。 - 使用
@PostConstruct
注解标注的方法。
- 实现
- 初始化阶段包括调用Bean的初始化方法,执行一些定制的初始化操作。这些初始化回调方法可以通过以下方式实现:
- 执行后置处理器后置处理方法:
- 在Bean初始化之后,Spring容器会调用
BeanPostProcessor
接口的postProcessAfterInitialization
方法。这是Bean生命周期中的另一个扩展点,允许开发者在Bean初始化之后进行一些定制操作。
- 在Bean初始化之后,Spring容器会调用
- 使用(Using):
- 经过初始化的Bean现在处于活动状态,可以被应用程序中的其他组件或Bean引用和调用。
- 销毁(Destruction):
- 当Bean不再需要时(例如,Spring容器关闭或显式地销毁Bean时),Spring容器会调用Bean的销毁方法,执行必要的清理操作。这些销毁方法可以通过以下方式实现:
- 实现
DisposableBean
接口的destroy()
方法。 - 在配置文件中指定的自定义销毁方法(即
destroy-method
)。
- 实现
- 当Bean不再需要时(例如,Spring容器关闭或显式地销毁Bean时),Spring容器会调用Bean的销毁方法,执行必要的清理操作。这些销毁方法可以通过以下方式实现:
1.2 代码实战
下面是一个简单的Spring Bean生命周期的代码示例:
- 创建一个Bean类:
package com.example.demo;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class MyBean implements InitializingBean, DisposableBean {
public MyBean() {
System.out.println("Bean实例化");
}
// 通过setter方法注入依赖
public void setDependency(String dependency) {
System.out.println("属性赋值:" + dependency);
}
// 实现InitializingBean接口的afterPropertiesSet方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("执行afterPropertiesSet方法");
}
// 使用@PostConstruct注解标注的初始化方法
@PostConstruct
public void init() {
System.out.println("执行@PostConstruct注解的初始化方法");
}
// 实现DisposableBean接口的destroy方法
@Override
public void destroy() throws Exception {
System.out.println("执行destroy方法");
}
// 在配置文件中指定的自定义销毁方法(可选)
public void customDestroy() {
System.out.println("执行自定义销毁方法");
}
}
- 配置Spring 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="myBean" class="com.example.demo.MyBean" init-method="init" destroy-method="customDestroy">
<property name="dependency" value="SomeDependency"/>
</bean>
</beans>
- 测试Bean的生命周期:
package com.example.demo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取Bean实例
MyBean myBean = (MyBean) context.getBean("myBean");
// 关闭容器,触发销毁方法
context.close();
}
}
1.3 运行结果
运行上述代码后,你将看到以下输出:
1.Bean实例化
2.属性赋值:SomeDependency
4.执行@PostConstruct注解的初始化方法
3.执行afterPropertiesSet方法
5.执行destroy方法
(在关闭容器时)
6.执行自定义销毁方法
这个输出展示了Spring Bean生命周期的各个阶段,从实例化到属性赋值,再到初始化,最后到销毁。通过代码实战,我们可以更直观地理解Spring Bean的生命周期过程。
2、Spring事务
2.1 什么是事务
事务是由数据库管理系统在执行过程中形成的一个逻辑单位,它由一组有限的数据库操作序列组成。通常情况下,事务是由程序单元通过高级语言或数据库的数据操作语言提交的。
2.2 事务的特性
Spring 事务管理是 Spring 框架中一个重要的功能,它为开发者提供了一种方便的方式来管理数据库事务。事务具有四个基本特性(ACID):
- 原子性(Atomicity):事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态。
- 隔离性(Isolation):一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的。
- 持久性(Durability):一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
2.3 Spring事务隔离级别
隔离级别 | 描述 |
---|---|
Isolation.DEFAULT (默认) | 使用底层数据库的默认隔离级别 |
Isolation.READ_UNCOMMITTED (未提交读) | 最低的隔离级别,允许读取未提交数据,可能会导致脏读、不可重复读或幻读 |
Isolation.READ_COMMITTED (已提交读) | 允许读取已提交数据,避免脏读,但可能会出现不可重复读或幻读 |
Isolation.REPEATABLE_READ (可重复读) | 保证在同一事务中多次读取同样数据的一致性,避免脏读和不可重复读,但可能出现幻读 |
Isolation.SERIALIZABLE (串行化) | 最高的隔离级别,完全串行化的事务执行,避免脏读、不可重复读和幻读,但可能会影响并发性能 |
2.4 并发问题
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED(未提交读) | 可能 | 可能 | 可能 |
READ COMMITTED(已提交读) | 不可能 | 可能 | 可能 |
REPEATABLE READ(可重复读) | 不可能 | 不可能 | 可能 |
SERIALIZABLE(串行化) | 不可能 | 不可能 | 不可能 |
解释
- 脏读(Dirty Read):一个事务读取了另一个未提交事务修改的数据。例如,事务 A 修改了某数据但未提交,事务 B 却读取到了事务 A 修改后的值,若事务 A 之后回滚了修改,事务 B 读取到的数据就是无效的(“脏” 数据)。
- 不可重复读(Non-Repeatable Read):在一个事务中多次读取同一数据,由于其他事务对该数据进行了修改并提交,导致多次读取结果不同。例如,事务 A 在两次读取某数据之间,事务 B 修改并提交了该数据,使得事务 A 两次读取结果不一样。
- 幻读(Phantom Read):一个事务在读取某个范围的数据后,另一个事务插入新的数据,导致第一个事务再次读取该范围时出现新的数据(好像出现了 “幻影” 数据)。例如,事务 A 查询某条件下的一组数据,事务 B 插入了符合该条件的新数据,事务 A 再次查询时,结果集中出现了新数据。这些问题在不同的事务隔离级别下有不同的处理方式。
2.5 Spring事务的实现方式
Spring事务可以通过两种方式来实现:编程式事务和声明式事务。
- 编程式事务:指在业务功能代码中嵌入事务管理的代码,手动控制事务的各种操作,属于侵入性事务管理。在Spring中为了支持和简化编程式事务,专门提供了一个类TransactionTemplate,在它的execute方法中就能实现事务的功能。
- 声明式事务:通过配置文件或注解的方式来管理事务,避免了在代码中手动控制事务的繁琐和容易出错的问题。声明式事务的优点是代码简洁、可读性好、灵活性和可用性高、容易维护和调试。
- XML配置方式:在Spring的XML配置中,可以通过配置事务管理器(transactionManager)、事务通知(txAdvice)以及将事务通知应用于特定的切入点(pointcut)来实现事务管理。
- 注解方式:可以在需要使用事务的方法或者类上添加
@Transactional
注解,并通过该注解的属性来指定事务的传播机制、隔离级别、超时时间等信息。当被标注为@Transactional
的方法被调用时,Spring会在方法执行前创建事务,并在方法执行结束后提交或回滚事务。
2.6 Spring事务的传播行为
Spring支持多种事务传播行为,可以控制多个事务之间的关系。常见的事务传播行为包括:
- REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则新建一个事务。
- REQUIRES_NEW:无论当前是否存在事务,都会新建一个事务,挂起当前事务(如果存在)。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则将当前事务挂起。
- NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- NESTED:如果当前存在事务,则在当前事务中嵌套执行;如果当前没有事务,则创建一个新事务。
2.7 Spring事务的失效场景及解决方案
在Spring事务管理中,存在一些常见的事务失效场景,包括:
- 自调用问题:在同一个类中,一个非事务方法直接调用带有
@Transactional
注解的事务方法,事务注解将不会生效。解决方案是使用AopContext.currentProxy()获取代理对象进行调用,或重构代码以避免自调用。 - 异常被捕获且未重新抛出:在事务方法中,如果捕获了异常但未重新抛出,Spring无法识别到异常,导致事务不会回滚。解决方案是确保所有需要触发事务回滚的异常被重新抛出,或显式指定
@Transactional
的rollbackFor属性。 - 非公共方法:事务注解仅对公共方法有效。如果在私有或受保护的方法上使用
@Transactional
,事务将不会启动。解决方案是确保事务方法为公共方法,或调整设计以避免在非公共方法上使用事务。 - 事务异常类型不对:如果方法中抛出的异常类型并不在事务管理的默认回滚规则内,或者没有明确指定需要回滚的异常类型,那么即使方法执行过程中发生了异常,事务也可能不会自动回滚。解决方案是在
@Transactional
注解中使用rollbackFor属性指定需要回滚的异常类型,或让自定义异常继承自RuntimeException。
TransactionTemplate,在它的execute方法中就能实现事务的功能。 - 声明式事务:通过配置文件或注解的方式来管理事务,避免了在代码中手动控制事务的繁琐和容易出错的问题。声明式事务的优点是代码简洁、可读性好、灵活性和可用性高、容易维护和调试。
- XML配置方式:在Spring的XML配置中,可以通过配置事务管理器(transactionManager)、事务通知(txAdvice)以及将事务通知应用于特定的切入点(pointcut)来实现事务管理。
- 注解方式:可以在需要使用事务的方法或者类上添加
@Transactional
注解,并通过该注解的属性来指定事务的传播机制、隔离级别、超时时间等信息。当被标注为@Transactional
的方法被调用时,Spring会在方法执行前创建事务,并在方法执行结束后提交或回滚事务。
2.6 Spring事务的传播行为
Spring支持多种事务传播行为,可以控制多个事务之间的关系。常见的事务传播行为包括:
- REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则新建一个事务。
- REQUIRES_NEW:无论当前是否存在事务,都会新建一个事务,挂起当前事务(如果存在)。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则将当前事务挂起。
- NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- NESTED:如果当前存在事务,则在当前事务中嵌套执行;如果当前没有事务,则创建一个新事务。
2.7 Spring事务的失效场景及解决方案
在Spring事务管理中,存在一些常见的事务失效场景,包括:
- 自调用问题:在同一个类中,一个非事务方法直接调用带有
@Transactional
注解的事务方法,事务注解将不会生效。解决方案是使用AopContext.currentProxy()获取代理对象进行调用,或重构代码以避免自调用。 - 异常被捕获且未重新抛出:在事务方法中,如果捕获了异常但未重新抛出,Spring无法识别到异常,导致事务不会回滚。解决方案是确保所有需要触发事务回滚的异常被重新抛出,或显式指定
@Transactional
的rollbackFor属性。 - 非公共方法:事务注解仅对公共方法有效。如果在私有或受保护的方法上使用
@Transactional
,事务将不会启动。解决方案是确保事务方法为公共方法,或调整设计以避免在非公共方法上使用事务。 - 事务异常类型不对:如果方法中抛出的异常类型并不在事务管理的默认回滚规则内,或者没有明确指定需要回滚的异常类型,那么即使方法执行过程中发生了异常,事务也可能不会自动回滚。解决方案是在
@Transactional
注解中使用rollbackFor属性指定需要回滚的异常类型,或让自定义异常继承自RuntimeException。