文章目录
- 1. Spring 是什么?
- 2. IoC
- 3. Spring Demo
- 4. IoC 创建对象的方式 / DI 方式
- 注入的默认参数在哪里设定?
- 5. Spring 配置
- tx:annotation-driven 用于启用基于注解的事务管理
- 6. Bean的作用域
- 7. 在Spring中有三种自动装配的方式
- 1. 在xml中显式的配置
- 2. 在java中显式配置
- 3. 隐式的自动装配bean。【重要!】
- byName 自动装配
- byType 自动装配
- 8. 注解
- 9. applicationContext.xml与beans.xml有什么区别
- 10. Java Config
- 11. 横向开发 VS 纵向开发
- 12. 代理模式
- 13. 静态代理 VS 动态代理
- 静态代理
- 动态代理
- InvocationHandler 与 Proxy
- 基于接口——JDK动态代理
- 基于类——CGLIB动态代理
- 14. AOP
- 核心概念
- 实现
- 1. 基于 xml 配置类实现
- 2. 自定义类
- 3. 使用注解实现
- 15. 静态资源过滤问题
- 16. Spring 整合Mybatis
- 17. 声明式事务
- 18. spring事务传播特性
- 参考
1. Spring 是什么?
Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。
Spring 框架的核心特点包括:
依赖注入(Dependency Injection,DI):Spring 提供了一个容器,负责管理应用程序中的对象(bean)以及它们之间的依赖关系。通过依赖注入,开发人员可以将对象的依赖关系从代码中解耦,提高了代码的灵活性和可维护性。
面向切面编程(Aspect-Oriented Programming,AOP):Spring 支持 AOP,允许开发人员在应用程序中声明性地定义横切关注点(cross-cutting concerns),如日志记录、事务管理等,并将它们与核心业务逻辑分开,提高了代码的模块化和可维护性。
模块化:Spring 框架被组织为多个模块,每个模块都提供不同的功能,如核心容器、数据访问、Web 开发、安全性等。开发人员可以根据需要选择并使用这些模块,使得 Spring 框架具有高度的可定制性和灵活性。
声明式事务管理:Spring 提供了声明式事务管理的支持,开发人员可以通过配置简单的元数据来管理事务,而无需编写复杂的事务管理代码。
面向接口编程:Spring 鼓励开发人员使用接口来编程,而不是直接使用实现类。这种面向接口的设计使得代码更易于扩展和维护,并且使得单元测试更加容易进行。
简化企业级开发:Spring 提供了许多工具和技术,如 Spring MVC、Spring Boot、Spring Data 等,可以帮助开发人员快速地构建企业级应用程序,并且减少了开发过程中的样板代码。
2. IoC
-
控制反转loC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现loC的一种方法
-
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)
-
对象由Spring 来创建 , 管理 , 装配 !
控制反转(IoC)是一种软件设计原则,它反转了传统应用程序中对象的创建和管理流程。在传统的应用程序中,对象之间的依赖关系通常由对象自身来管理或硬编码。但在IoC容器中,对象的创建和管理由容器来负责,它会自动地将依赖关系注入到对象中。这种反转的控制权使得对象之间的耦合度降低,更容易进行测试、维护和扩展。
在Spring框架中,IoC容器负责管理应用程序中的组件,例如Java类和对象。Spring的IoC容器利用依赖注入(DI)的机制来实现控制反转。通过依赖注入,容器会在对象被创建的时候自动将其依赖的其他对象注入进来,从而实现了解耦和松散耦合的目标。
3. Spring Demo
UserService.java
public class UserService {
public String getUserInfo() {
return "User information from UserService";
}
}
UserDao.java
public class UserDAO {
public String getUserData() {
return "User data from UserDAO";
}
}
UserController.java
依赖注入
public class UserController {
private UserService userService;
private UserDAO userDAO;
// 通过构造函数注入 UserService 和 UserDAO
public UserController(UserService userService, UserDAO userDAO) {
this.userService = userService;
this.userDAO = userDAO;
}
public String getUserInfo() {
String userInfo = userService.getUserInfo();
String userData = userDAO.getUserData();
return "User Info: " + userInfo + ", User Data: " + userData;
}
}
applicationContext.xml
配置 Spring 容器以管理这些组件之间的依赖关系
<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">
<!-- 配置 UserService -->
<bean id="userService" class="com.example.UserService" />
<!-- 配置 UserDAO -->
<bean id="userDAO" class="com.example.UserDAO" />
<!-- 配置 UserController,并注入 UserService 和 UserDAO -->
<bean id="userController" class="com.example.UserController">
<constructor-arg ref="userService" />
<constructor-arg ref="userDAO" />
</bean>
</beans>
applicationContext.xml / beans.xml 有三种方式编写
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 第一种:根据index参数下标设置 --> <!-- <bean id="userT" class="com.github.subei.pojo.UserT">--> <!-- <constructor-arg index="0" value="subeily——"/>--> <!-- </bean>--> <!-- 第二种:根据参数类型设置,不建议使用 --> <!-- <bean id="userT" class="com.github.subei.pojo.UserT">--> <!-- <constructor-arg type="java.lang.String" value="subeily2——"/>--> <!-- </bean>--> <!-- 第三种:根据参数名字设置 --> <bean id="userT" class="com.github.subei.pojo.UserT"> <!-- name指参数名 --> <constructor-arg name="name" value="subeily3——"/> </bean> </beans>
MainApplication
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
// 加载 Spring 容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取 UserController bean
UserController userController = (UserController) context.getBean("userController");
// 调用 getUserInfo 方法
String userInfo = userController.getUserInfo();
System.out.println(userInfo);
}
}
4. IoC 创建对象的方式 / DI 方式
- 构造函数注入(Constructor Injection):
在构造函数注入中,依赖关系通过构造函数来注入。IoC容器会在创建对象时调用其构造函数,并将依赖的对象作为参数传递给构造函数。这样,对象在创建时就能够获得它所需要的依赖对象。
public class MyClass {
private MyDependency dependency;
public MyClass(MyDependency dependency) {
this.dependency = dependency;
}
}
- Setter方法注入(Setter Injection):
在Setter方法注入中,依赖关系通过setter方法来注入。IoC容器会先创建对象,然后调用对象的setter方法来设置依赖对象。这种方式可以使得依赖对象是可选的,因为如果没有设置相应的依赖,对象也能够被创建。
public class MyClass {
private MyDependency dependency;
public void setDependency(MyDependency dependency) {
this.dependency = dependency;
}
}
或:
<?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="address" class="com.github.subei.pojo.Address">
<property name="address" value="成都"/>
</bean>
<bean id="student" class="com.github.subei.pojo.Student">
<!-- 第一种:普通值注入,value -->
<property name="name" value="subei"/>
<!-- 第二种:Bean注入,ref -->
<property name="address" ref="address"/>
<!-- 第三种:数组注入 -->
<property name="books">
<array>
<value>Mybatis</value>
<value>Spring</value>
<value>SpringMVC</value>
</array>
</property>
<!-- 第四种:list注入 -->
<property name="hobby">
<list>
<value>家里蹲</value>
<value>精神萎靡</value>
<value>无法沟通</value>
</list>
</property>
<!-- 第五种:Map注入 -->
<property name="card">
<map>
<entry key="学生证" value="20201014"/>
<entry key="身份证" value="14253686"/>
</map>
</property>
<!-- 第六种:set注入 -->
<property name="games">
<set>
<value>保卫萝卜1</value>
<value>保卫萝卜2</value>
<value>保卫萝卜3</value>
</set>
</property>
<!-- 第七种:null注入 -->
<property name="wife">
<null/>
</property>
<!-- 第八种:Properties注入 -->
<property name="info">
<props>
<prop key="学号">20210106</prop>
<prop key="性别">保密</prop>
<prop key="姓名">subei</prop>
</props>
</property>
</bean>
</beans>
3. **接口注入(Interface Injection)**:
在接口注入中,对象实现一个特定的接口,该接口包含一个方法用于注入依赖。IoC容器通过调用这个方法来注入依赖对象。
```java
public interface Injectable {
void injectDependency(MyDependency dependency);
}
public class MyClass implements Injectable {
private MyDependency dependency;
@Override
public void injectDependency(MyDependency dependency) {
this.dependency = dependency;
}
}
- 注解(Annotation):
使用注解方式可以更加简洁地指定依赖注入的方式。在Java中,常用的注解包括@Autowired
、@Inject
等。通过在需要注入的字段或方法上添加注解,IoC容器会自动进行依赖注入。
public class MyClass {
@Autowired
private MyDependency dependency;
}
- 拓展方式注入
在Spring Framework中,除了使用常规的构造函数注入和Setter方法注入外,还可以使用XML配置文件中的p:
命名空间和c:
命名空间进行属性注入。
- p:命名空间:
- `p:`命名空间用于简化在XML配置文件中为Bean属性进行赋值。它允许您在Bean定义中**直接设置属性值,而无需显式调用相应的Setter方法**。
- 例如,假设有一个名为`Person`的Bean,具有`name`和`age`属性,您可以使用`p:`命名空间进行注入,如下所示:
```xml
<bean id="person" class="com.example.Person" p:name="John" p:age="30" />
```
- c:命名空间:
- `c:`命名空间用于在XML配置文件中为**构造函数参数进行注入(有参构造函数)**。它允许您在Bean定义中直接设置构造函数参数值。
- 例如,假设有一个名为`Person`的Bean,构造函数需要`name`和`age`参数,您可以使用`c:`命名空间进行注入,如下所示:
```xml
<bean id="person" class="com.example.Person" c:_0="John" c:_1="30" />
```
- 这里`c:_0`表示构造函数的第一个参数,`c:_1`表示构造函数的第二个参数,以此类推。
注入的默认参数在哪里设定?
- 通过配置文件:
许多IoC容器允许使用配置文件(如XML、JSON、YAML等)来指定默认参数。在配置文件中,可以为特定的类或对象指定默认参数值。当IoC容器创建对象时,如果没有显式指定参数值,它将会使用配置文件中的默认值。
例如,在Spring Framework中,可以使用XML配置文件或者基于Java的配置类来指定默认参数值:
<bean id="myBean" class="com.example.MyClass">
<constructor-arg value="defaultParameterValue" />
</bean>
- 通过注解:
有些IoC容器支持使用注解来指定默认参数。通过在类或方法上添加特定的注解,可以指定默认参数值。
例如,在Spring Framework中,可以使用@Value
注解来指定默认参数值:
@Component
public class MyClass {
@Value("${my.property:defaultValue}")
private String myProperty;
}
在这个例子中,如果在配置文件中没有指定my.property
的值,则默认使用defaultValue
。
- 通过代码配置:
一些IoC容器支持通过编程方式来指定默认参数值。开发者可以在代码中显式地为对象设置默认参数值。
例如,在Spring Framework中,可以使用Java配置类来指定默认参数值:
@Configuration
public class AppConfig {
@Bean
public MyClass myClass() {
MyClass myClass = new MyClass();
myClass.setDefaultParameterValue("defaultValue");
return myClass;
}
}
在这个例子中,MyClass
对象的默认参数值被显式地设置为defaultValue
。
5. Spring 配置
在Spring中,配置文件通常是XML格式的,可以使用一系列元素来定义和配置应用程序的组件。以下是一些常见的配置元素:
<bean>
元素:<bean>
元素用于定义Spring容器中的bean。它包括属性如id
(用于唯一标识bean)、class
(指定bean的类)、scope
(指定bean的作用域)、init-method
(指定bean初始化时调用的方法)、destroy-method
(指定bean销毁时调用的方法)等。例如:
<bean id="myBean" class="com.example.MyClass" scope="singleton">
<property name="propertyName" value="propertyValue" />
</bean>
<alias>
元素:<alias>
元素用于为现有的 bean 创建一个别名。这对于简化配置或者提供更有意义的名称很有用。例如:
<alias name="myBean" alias="myAlias" />
<import>
元素:<import>
元素用于将其他 XML 配置文件导入到当前的配置文件中。这使得配置文件可以分解为更小的模块,更易于管理和维护。例如:
<import resource="classpath:other-config.xml" />
- 命名空间元素:
Spring提供了一系列命名空间,用于简化配置文件。例如,<context:component-scan>
用于扫描指定包下的类,并将其注册为bean。另一个例子是<tx:annotation-driven>
用于启用基于注解的事务管理。例如:
<context:component-scan base-package="com.example" />
<tx:annotation-driven />
- 注释元素:
Spring还支持使用注释来配置bean,包括@Component
、@Autowired
、@Bean
等。这种方式是基于Java的配置方式的一部分,通过在类上添加注释来告诉Spring容器如何处理这些类。
@Component
public class MyClass {
// class definition
}
以上是Spring中常用的配置元素和方式。开发者可以根据项目的需要选择适合的配置方式。
tx:annotation-driven 用于启用基于注解的事务管理
<tx:annotation-driven>
是 Spring 中的一个 XML 配置元素,用于启用基于注解的事务管理支持。通过在 Spring 配置文件中包含该元素,Spring 容器会自动检测被 @Transactional
注解标记的方法,并在执行这些方法时提供事务管理支持。
具体来说,<tx:annotation-driven>
主要做了以下几件事情:
-
注册
TransactionInterceptor
bean:<tx:annotation-driven>
在 Spring 容器中注册一个TransactionInterceptor
bean,用于处理@Transactional
注解。 -
启用事务注解驱动:它告诉 Spring 启用基于注解的事务管理支持。
-
配置事务管理器:它会自动检测当前 Spring 上下文中的事务管理器(例如
DataSourceTransactionManager
或JpaTransactionManager
),并将其应用于被@Transactional
注解标记的方法。
示例用法如下所示:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- 数据源配置 -->
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 启用基于注解的事务管理 -->
<tx:annotation-driven />
<!-- 其他 bean 配置 -->
</beans>
在这个示例中,<tx:annotation-driven>
元素启用了基于注解的事务管理支持,并且自动检测并使用了名为 transactionManager
的事务管理器。接下来,你可以在需要事务管理的方法上使用 @Transactional
注解来启用事务管理。例如:
@Service
public class MyService {
@Autowired
private MyRepository repository;
@Transactional
public void performTransactionalOperation() {
// 执行数据库操作
repository.save(entity);
}
}
这样,在调用 performTransactionalOperation()
方法时,Spring 会自动为其创建一个事务,并在方法执行结束后根据执行情况来提交或回滚事务。
6. Bean的作用域
在Spring Framework中,"Bean的作用域"指的是Spring容器如何管理和创建Bean实例以及这些实例的生命周期。Spring定义了几种不同的作用域,每种作用域都控制着Bean实例的创建和销毁方式,以及在应用程序中的可见性。以下是Spring中常见的Bean作用域:
- Singleton(单例):
- 默认作用域,每个Spring容器中只存在一个Bean实例。
- Spring容器在第一次请求该Bean时创建实例,并在容器关闭时销毁实例。
- 所有对该Bean的请求都返回同一个实例。
- 适用于状态无关的Bean,如服务类、数据访问类等。
- Prototype(原型):
- 每次通过容器的getBean()方法请求时都会创建一个新的Bean实例。
- Spring容器不会管理Prototype作用域的Bean的生命周期,也不负责销毁它们。
- 每次请求该Bean时都会创建一个新的实例,并在返回后不再管理它。
- 适用于状态相关的Bean,如HTTP请求的处理类、会话Bean等。
- Request(请求):
- 每个HTTP请求都会创建一个新的Bean实例,该实例仅在当前HTTP请求范围内有效。
- 仅在Spring Web应用程序中有效,在单例Bean中注入Request作用域的Bean时需要注意。
- Spring MVC中常用于Web应用程序中处理每个HTTP请求的控制器。
- Session(会话):
- 每个HTTP会话创建一个新的Bean实例,该实例在整个会话期间有效。
- 仅在Spring Web应用程序中有效,在单例Bean中注入Session作用域的Bean时需要注意。
- 通常用于保存用户会话状态的Bean。
- Application(应用):
- 在ServletContext范围内创建一个Bean实例,该实例在整个应用程序的生命周期内有效。
- 仅在Spring Web应用程序中有效,在单例Bean中注入Application作用域的Bean时需要注意。
- 通常用于在应用程序级别共享的全局配置信息或状态。
- WebSocket(WebSocket):
- 每个WebSocket连接创建一个新的Bean实例,该实例在WebSocket会话期间有效。
- 用于在Spring WebSocket应用程序中保存WebSocket连接的状态。
选择适当的作用域取决于Bean的性质和在应用程序中的使用方式。默认情况下,建议使用Singleton作用域,除非有特定的需求需要使用其他作用域。
@Bean
public SingletonBean singletonBean() {
return new SingletonBean();
}
// Prototype Bean
@Bean
@Scope("prototype")
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
@RequestScope
@Component
public class LoginAction {
// ...
}
@SessionScope
@Component
public class UserPreferences {
// ...
}
或:
<bean id="user" class="com.github.subei.pojo.User"/>
<!-- 以下内容是等价的,尽管是多余的(默认为单例作用域) -->
<bean id="user2" class="com.github.subei.pojo.User"
scope="singleton"/>
<bean id="user2" class="com.github.subei.pojo.User"
scope="prototype"/>
7. 在Spring中有三种自动装配的方式
1. 在xml中显式的配置
这是最传统的方式,通过在XML配置文件中使用<bean>
元素来定义Bean,并在需要注入依赖的地方使用<property>
或<constructor-arg>
元素来显式指定依赖的Bean。这种方式需要开发人员手动配置所有的Bean和它们之间的依赖关系,不具备自动发现和连接的能力。
<bean id="dependencyBean" class="com.example.DependencyBean" />
<bean id="myBean" class="com.example.MyBean">
<property name="dependency" ref="dependencyBean" />
</bean>
2. 在java中显式配置
使用Java Config(也称为Java Configuration)的方式,通过在Java类中使用@Configuration
注解和@Bean
注解来声明Bean,并通过@Autowired
注解来注入依赖关系。这种方式将Bean的定义和依赖关系配置移到了Java代码中,使得配置更加类型安全,并且可以利用Java语言的特性进行更灵活的配置。
@Configuration
public class AppConfig {
@Bean
public DependencyBean dependencyBean() {
return new DependencyBean();
}
@Bean
public MyBean myBean() {
return new MyBean(dependencyBean());
}
}
3. 隐式的自动装配bean。【重要!】
Spring框架提供了自动装配机制,可以通过在类的构造器、属性、或方法上使用@Autowired
注解来隐式指定Bean之间的依赖关系。Spring会自动在容器中查找匹配的Bean,并将其注入到目标Bean中。这种方式使得开发人员可以将关注点集中在业务逻辑上,而不需要过多地关注Bean之间的依赖关系,从而提高了开发效率。
第一步:在 beans.xml / applicationContext.xml 导入context 约束,并开启注解支持
<?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:aop="http://www.springframework.org/schema/aop"
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/aop
https://www.springframework.org/schema/context/spring-aop.xsd">
<!-- 开启注解的支持 -->
<context:annotation-config/>
<bean id="cat" class="com.github.subei.pojo.Cat"/>
<bean id="dog" class="com.github.subei.pojo.Dog"/>
<bean id="people" class="com.github.subei.pojo.People"/>
</beans>
java:
public class MyBean {
private DependencyBean dependency;
@Autowired
public MyBean(DependencyBean dependency) {
this.dependency = dependency;
}
}
byName 和 byType 自动装配:
byName 自动装配
-
在byName自动装配中,Spring 容器会自动将一个 bean 的属性与另一个 bean 的名称匹配。如果一个 bean 的属性名称与另一个 bean 的名称相同,则会将这个 bean 注入到该属性中。
-
为了启用 byName 自动装配,需要在 XML 配置文件中使用
autowire="byName"
属性,或者在 Java 配置类中使用@Autowired
注解结合@Qualifier
注解。@Qualifier
注解: 指定一个唯一的bean对象注入 -
使用 byName 自动装配时,确保目标 bean 的属性名称与依赖 bean 的名称匹配。
-
byname的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致!
<bean id="dependencyBean" class="com.example.DependencyBean" />
<bean id="myBean" class="com.example.MyBean" autowire="byName" />
或者
@Configuration
public class AppConfig {
@Bean
public DependencyBean dependencyBean() {
return new DependencyBean();
}
@Bean
@Autowired
public MyBean myBean(@Qualifier("dependencyBean") DependencyBean dependency) {
return new MyBean(dependency);
}
}
byType 自动装配
- 在byType自动装配中,Spring 容器会自动将一个 bean 的属性与另一个 bean 的类型匹配。如果一个 bean 的属性类型与另一个 bean 的类型相同,则会将这个 bean 注入到该属性中。
- 为了启用 byType 自动装配,需要在 XML 配置文件中使用
autowire="byType"
属性,或者在 Java 配置类中使用@Autowired
注解。 - 使用 byType 自动装配时,确保目标 bean 的属性类型与依赖 bean 的类型匹配,且容器中只有一个与之匹配的 bean。
- bytype的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
<bean id="dependencyBean" class="com.example.DependencyBean" />
<bean id="myBean" class="com.example.MyBean" autowire="byType" />
或者
@Configuration
public class AppConfig {
@Bean
public DependencyBean dependencyBean() {
return new DependencyBean();
}
@Bean
@Autowired
public MyBean myBean(DependencyBean dependency) {
return new MyBean(dependency);
}
}
- byName中bean的名称是指 bean 的 id 吗?
- 是的,byName 自动装配中的名称指的是 bean 的 id。Spring 容器会将属性名称与其他 bean 的 id 进行匹配。
<bean/>
默认使用的是byName自动装配吗?
- 不是的,默认情况下,
<bean/>
元素并不会启用任何自动装配。你需要显式地指定 autowire 属性为 byName 或者 byType 才能启用对应的自动装配方式。
- byType 中 bean 的属性类型指的是 bean 的 class 吗?
- 是的,byType 自动装配中,Spring 容器会尝试查找与属性类型匹配的 bean,并将其注入到对应的属性中。
- @Resource 和@Autowired 分别使用的是 byName 还是 byType?
@Resource
注解默认使用 byName 自动装配,它会根据属性名称去查找对应的 bean。你也可以通过name
属性指定要注入的 bean 的名称。@Autowired
注解默认使用 byType 自动装配,它会根据属性类型去查找对应的 bean。你也可以结合@Qualifier
注解使用 byName 自动装配,或者使用@Autowired(required = false)
实现可选的自动装配。
- @Resource 和@Autowired的区别:
-
都是用来自动装配的,都可以放在属性字段上!
-
@Autowired 通过byType的方式实现,而且必须要求这个对象存在!
-
@Resource默认通过byname的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的倩况下,就报错!【常用】
-
执行顺序不同:@Autowired通过byType的方式实现。@Resource默认通过byname的方式实现。
8. 注解
第一步:在 beans.xml / applicationContext.xml 导入context 约束,并开启注解支持
<!-- 开启注解的支持 -->
<context:annotation-config/>
- @ComponentScan: 用于启用组件扫描,自动扫描指定包及其子包中的组件(例如,带有
@Component
、@Service
、@Repository
、@Controller
等注解的类),并将其注册到 Spring 容器中。
applicationContext.xml:
<!-- 指定要扫描的包,这个包下的注解就会生效! -->
<context:component-scan base-package="com.github.subei.pojo"/>
或者 使用Java配置类(@Configuration),相当于 xml 配置文件:
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 这里可以添加其他配置
}
-
@Component: 用于标识一个类作为 Spring 组件,让 Spring 自动扫描并注册为 bean。
-
@Service: 通常用于标识服务层组件,类似于
@Component
,但提供了更具体的语义。 -
@Repository: 通常用于标识数据访问层组件(如 DAO),它们通常用于与数据库或其他持久化机制交互。
-
@Controller: 用于标识控制器层组件,通常用于处理 Web 请求。
-
@Autowired: 用于进行自动装配,可以用在构造函数、setter 方法、字段或者在配置类中的方法上。Spring 将根据类型进行匹配,将相应的 bean 注入到标记了
@Autowired
的属性中。 -
@Qualifier: 与
@Autowired
结合使用,用于指定要注入的具体 bean 的名称,用以解决多个候选 bean 的自动装配歧义问题。 -
@Value: 用于将外部属性值注入到 bean 的属性中,可以从属性文件、环境变量等地方获取值。
-
@Configuration: 用于标识一个类为配置类,通常与
@Bean
注解一起使用,用于定义 bean。 -
@Bean: 通常用于在配置类中定义 bean,Spring 容器会根据其返回值注册一个 bean。
-
@Scope: 用于指定 bean 的作用域,例如 Singleton、Prototype 等。
-
@PostConstruct 和 @PreDestroy: 用于标识初始化和销毁回调方法,分别在 bean 的初始化和销毁阶段执行。
9. applicationContext.xml与beans.xml有什么区别
applicationContext.xml
和 beans.xml
是两种常见的 Spring XML 配置文件,它们在用途和功能上有一些区别。
- applicationContext.xml:
applicationContext.xml
是 Spring 应用程序上下文的配置文件,通常用于配置整个应用程序的上下文环境。- 在
applicationContext.xml
中,可以定义各种类型的 bean、组件扫描、AOP、事务管理、国际化配置等等。 applicationContext.xml
可以包含多个模块或者组件的配置,通常用于配置整个应用程序的所有组件。
- beans.xml:
beans.xml
通常是用于某个特定模块或者组件的配置文件,用于配置该模块或者组件所需要的 bean。- 在
beans.xml
中,通常只包含与特定模块或者组件相关的 bean 配置。 - 使用
beans.xml
可以将应用程序的配置模块化,使得每个模块的配置都更加清晰和独立。
总的来说,applicationContext.xml
是整个 Spring 应用程序的上下文配置文件,用于配置应用程序中的所有组件,而 beans.xml
则是针对某个特定模块或者组件的配置文件,用于配置该模块或者组件所需要的 bean。在实际开发中,可以根据需要选择使用其中的一个或者同时使用两者。
10. Java Config
-
Java配置类(@Configuration),相当于 xml 配置文件:
@Configuration @ComponentScan(basePackages = "com.example") public class AppConfig { // 这里可以添加其他配置 }
-
导入其他配置
package com.github.subei.config; import org.springframework.context.annotation.Configuration; @Configuration //代表这是一个配置类 public class SunConfig2 { }
package com.github.subei.config;
import com.github.subei.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
// 这个也会Spring容器托管,注册到容器中,因为他本来就是一个@Component
// @Configuration代表这是一个配置类,就和我们之前看的beans.xml
@Configuration // 代表这是一个配置类
@ComponentScan("com.github.subei.pojo")
@Import(SunConfig2.class) // 导入合并其他配置类,类似于配置文件中的 inculde 标签
public class SunConfig {
// 注册一个bean,就相当于我们之前写的一个bean标签;
// 这个方法的名字,就相当bean标签中的id属性;
// 这个方法的返回值,就相当bean标签中的cLass属性;
@Bean
public User getUser(){
return new User(); // 就是返回要注入到bean的对象
}
}
11. 横向开发 VS 纵向开发
12. 代理模式
代理模式是一种结构型设计模式,其目的是通过代理对象来控制对其它对象的访问。代理对象通常充当了被代理对象的包装器或者中间人的角色,从而可以在访问实际对象之前或之后执行一些额外的操作。
代理模式主要有三种形式:
-
静态代理:在编译时就已经确定代理对象和被代理对象的关系,代理类与被代理类是一对一的关系。静态代理的优点是简单易懂,缺点是每一个被代理的类都需要一个代理类,因此扩展性不强。
-
动态代理:在运行时动态生成代理类,无需为每个被代理类编写一个代理类。Java 中的动态代理通常使用 JDK 的动态代理或者 CGLIB 等第三方库来实现。动态代理的优点是可以减少重复代码,增强灵活性和可维护性。
-
虚拟代理:延迟创建代理对象的实例,直到真正需要使用它时才进行实例化。虚拟代理通常用于需要消耗大量资源或者初始化较慢的对象,通过延迟加载来提升性能。
代理模式常见的应用场景包括:
- 远程代理:在客户端和远程服务器之间创建代理对象,用于隐藏真实对象的网络细节。
- 虚拟代理:延迟加载大对象,例如图片、视频等资源,在需要时才真正加载。
- 安全代理:控制对真实对象的访问权限,例如权限验证、身份验证等。
- 日志记录:在访问真实对象前后记录日志信息,用于调试或者监控。
- 缓存代理:缓存真实对象的结果,避免重复计算,提升性能。
代理模式可以帮助我们实现一些横切关注点(cross-cutting concerns),例如日志记录、事务管理等,同时也可以提高代码的可维护性和可扩展性。
13. 静态代理 VS 动态代理
静态代理
静态代理是指在编译期间就已经确定代理对象和被代理对象的关系的代理模式。在静态代理中,代理类和被代理类是一对一的关系,代理类充当了被代理类的包装器,对外提供与被代理类相同的接口,但在调用这些接口方法时可以在前后执行一些额外的操作。
静态代理的基本结构包括三个角色:
-
抽象接口(Subject):定义了代理类和被代理类共同实现的接口,代理类通过实现这个接口来与被代理类保持一致。
-
被代理类(Real Subject):实际执行业务逻辑的类,是代理类所代理的对象。
-
代理类(Proxy):持有一个对被代理类的引用,同时实现了抽象接口,通过调用被代理类的方法来完成实际的业务逻辑,在方法执行前后可以执行一些额外的操作,如权限验证、日志记录等。
静态代理的优点包括:
- 可以在不修改被代理类的情况下对其进行扩展或者增强,符合开闭原则。
- 可以控制对被代理对象的访问,提高系统的安全性。
- 可以将横切关注点(如日志记录、权限验证等)集中处理,提高代码的可维护性。
静态代理的缺点包括:
- 每个被代理类都需要对应一个代理类,如果被代理类较多,会导致类的数量增加,代码量增加,不利于系统的维护和扩展。
- 静态代理在编译时就已经确定了代理对象和被代理对象的关系,不够灵活,无法动态改变代理对象。
抽象接口 Subject
,其中定义了一个 request()
方法:
// Subject.java
public interface Subject {
void request();
}
实际的被代理类 RealSubject
,实现了 Subject
接口:
// RealSubject.java
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
代理类 Proxy
,也实现了 Subject
接口:
// Proxy.java
public class Proxy implements Subject {
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
// 在调用真实主题前可以执行一些额外的操作
System.out.println("Proxy: Pre-processing");
// 调用真实主题的方法
realSubject.request();
// 在调用真实主题后可以执行一些额外的操作
System.out.println("Proxy: Post-processing");
}
}
测试类来演示如何使用静态代理:
// ProxyPatternDemo.java
public class ProxyPatternDemo {
public static void main(String[] args) {
// 创建真实主题对象
RealSubject realSubject = new RealSubject();
// 创建代理对象,将真实主题对象传递给代理对象
Proxy proxy = new Proxy(realSubject);
// 通过代理对象调用方法
proxy.request();
}
}
输出将会是:
Proxy: Pre-processing
RealSubject: Handling request.
Proxy: Post-processing
动态代理
动态代理分为两大类:基于接口的动态代理,基于类的动态代理。
-
基于接口——JDK动态代理
-
基于类——cglib
基于接口的动态代理是通过 Java 标准库中的 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口实现的。它要求被代理的类必须实现一个接口,代理类通过实现同样的接口,并在 InvocationHandler
的实现中拦截方法调用来实现代理功能。
而基于类的动态代理则是通过第三方库实现的,比如 CGLIB。它允许在不需要目标对象实现接口的情况下创建代理,通过继承目标类并重写它的方法来实现代理功能。因为是基于继承,所以它无法代理被 final
修饰的类和方法。
选择使用哪种类型的动态代理取决于具体的需求和场景。如果被代理的类已经实现了接口,并且你希望代理类与目标类之间存在松耦合的关系,那么 JDK 动态代理是一个不错的选择。但如果被代理的类没有实现接口,或者你希望在代理过程中拦截对被代理类的所有方法调用,包括 final
方法,那么就需要使用基于类的动态代理,比如 CGLIB。
InvocationHandler 与 Proxy
- InvocationHandler(调用处理器):
java.lang.reflect.InvocationHandler
接口是用于实现动态代理的关键之一。它只定义了一个方法invoke(Object proxy, Method method, Object[] args)
,在代理对象上调用方法时会触发此方法的调用。在这个方法中,你可以实现代理对象方法的拦截和自定义行为。invoke
方法的参数解释:
proxy
:代理对象,调用方法时会自动传递给invoke
方法。method
:被调用的方法对象。args
:方法调用时传递的参数数组。InvocationHandler
的实现类实际上就是在定义代理对象的行为。你可以在invoke
方法中添加额外的逻辑,比如记录日志、执行前置/后置操作等。
- Proxy(代理类):
java.lang.reflect.Proxy
类是 JDK 提供的用于创建动态代理对象的工具类。它提供了一系列静态方法来创建代理类实例。Proxy
类的静态方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
用于创建代理对象。
loader
:类加载器,用于加载代理类。interfaces
:被代理类实现的接口数组。h
:InvocationHandler
对象,用于处理代理对象方法的调用。Proxy
类创建的代理对象实际上是在运行时动态生成的,它实现了被代理接口,并且在方法调用时会委托给传入的InvocationHandler
实现类处理。- 动态代理的核心就是通过
Proxy
类来创建代理对象,并且在代理对象的方法被调用时,会自动触发InvocationHandler
实现类中的invoke
方法。
基于接口——JDK动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
interface Hello {
void sayHello();
}
// 实现接口的被代理类
class HelloImpl implements Hello {
public void sayHello() {
System.out.println("Hello, world!");
}
}
// 实现 InvocationHandler 接口的代理处理类
class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method invocation");
Object result = method.invoke(target, args); // 调用被代理对象的方法
System.out.println("After method invocation");
return result;
}
}
public class Main {
public static void main(String[] args) {
// 创建被代理对象
Hello hello = new HelloImpl();
// 创建 InvocationHandler 实现类的实例
MyInvocationHandler handler = new MyInvocationHandler(hello);
// 使用 Proxy.newProxyInstance() 创建代理对象
Hello proxyHello = (Hello) Proxy.newProxyInstance(
hello.getClass().getClassLoader(),
hello.getClass().getInterfaces(),
handler
);
// 通过代理对象调用方法
proxyHello.sayHello();
}
}
基于类——CGLIB动态代理
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 被代理的类,无需实现接口
class Hello {
public void sayHello() {
System.out.println("Hello, world!");
}
}
// 实现 MethodInterceptor 接口的代理处理类
class MyMethodInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method invocation");
Object result = proxy.invokeSuper(obj, args); // 调用被代理对象的方法
System.out.println("After method invocation");
return result;
}
}
public class Main {
public static void main(String[] args) {
// 创建 Enhancer 对象
Enhancer enhancer = new Enhancer();
// 设置被代理类为父类
enhancer.setSuperclass(Hello.class);
// 设置回调函数
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
Hello proxyHello = (Hello) enhancer.create();
// 通过代理对象调用方法
proxyHello.sayHello();
}
}
14. AOP
AOP(Aspect-Oriented Programming)是一种编程范式,旨在通过横切关注点(cross-cutting concerns)的方式来解耦系统中的各个模块,使得代码更加模块化、可维护和可复用。在 AOP 中,横切关注点是指在应用程序中影响多个类和方法的功能需求,例如日志记录、事务管理、安全性等。
AOP 通过在程序执行期间动态地将这些横切关注点与核心业务逻辑分离开来,从而达到降低代码耦合度的目的。它主要通过两种方式实现:
-
基于代理的 AOP:在这种方式下,AOP 框架通过动态代理为目标对象创建一个代理对象,代理对象包含了额外的横切逻辑。当调用目标对象的方法时,会首先执行代理对象中的横切逻辑,然后再调用目标对象的方法。
-
基于字节码操作的 AOP:在这种方式下,AOP 框架通过修改目标类的字节码来插入横切逻辑,实现对目标对象的增强。常见的字节码操作工具有 ASM、CGLIB 等。
核心概念
-
横切关注点(Cross-cutting Concerns):这指的是在应用程序中涉及多个模块的功能需求,例如日志记录、安全性、事务管理等。横切关注点横跨应用程序的不同部分,并且通常与应用程序的核心功能无关。
-
切面(Aspect):切面是 AOP 的核心组件之一,用于封装横切关注点。它定义了何时何地应用横切逻辑(通知)以及应用哪些连接点(切入点)。切面将横切逻辑与应用程序的核心业务逻辑分离开来。
-
通知(Advice):通知是切面中定义的实际行为,它决定了横切逻辑在何时、何地执行以及执行哪些操作。通知类型包括前置通知(Before advice)、后置通知(After advice)、返回通知(After-returning advice)、异常通知(After-throwing advice)和环绕通知(Around advice)。
-
目标(Target):目标是被切面影响的对象或类。它通常包含应用程序的核心业务逻辑。
-
代理(Proxy):代理是在目标对象之上创建的对象,用于控制对目标对象的访问。在 AOP 中,代理包含了切面所定义的横切逻辑,并且在调用目标对象方法时会执行这些逻辑。
-
切入点(Pointcut):切入点用于定义横切逻辑在何处执行。它是一个表达式,指示了在应用程序中哪些连接点应该被通知影响。
-
连接点(Join Point):连接点是在应用程序执行过程中可以插入横切逻辑的具体点。这些点可以是方法调用、异常抛出、字段访问等。通知可以在连接点处被执行。
实现
1. 基于 xml 配置类实现
UserService.java
package com.fatfish.aop.service;
/**
* @author fatfish
* @version 1.0
* @date 2024/6/10 21:13
*/
public interface UserService {
void add();
void delete();
void select();
void update();
}
UserServiceImpl.java
package com.fatfish.aop.service;
/**
* @author fatfish
* @version 1.0
* @date 2024/6/10 21:14
*/
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void select() {
System.out.println("查询了一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
}
切面类:
Log.java
package com.fatfish.aop.log;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
/**
* @author fatfish
* @version 1.0
* @date 2024/6/10 21:15
*/
public class Log implements MethodBeforeAdvice {
// method:要执行的目标对象的方法
// args:参数
// target:目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行了!");
}
}
AfterLog.java
package com.fatfish.aop.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
/**
* @author fatfish
* @version 1.0
* @date 2024/6/10 21:16
*/
public class AfterLog implements AfterReturningAdvice {
// returnValue:返回值
// method:被调用的方法
// args:被调用的方法的对象的参数
// target:被调用的目标对象
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + method.getName() + target.getClass().getName()
+ "的" + "返回结果为:" + returnValue);
}
}
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"
xmlns:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注册bean -->
<bean id="userService" class="com.fatfish.aop.service.UserServiceImpl"/>
<bean id="log" class="com.fatfish.aop.log.Log"/>
<bean id="afterLog" class="com.fatfish.aop.log.AfterLog"/>
<!-- 方式一:使用原生Spring API接口 -->
<!-- 配置AOP:需要导入AOP的约束 -->
<aop:config>
<!-- 切入点:expression:表达式 , execution(要执行的位置!* * * *) -->
<aop:pointcut id="pointcut" expression="execution(* com.fatfish.aop.service.*.*(..))"/>
<!-- 执行环绕增加! advice-ref执行方法 , pointcut-ref切入点 -->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试类:
package com.fatfish.aop;
import com.fatfish.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author fatfish
* @version 1.0
* @date 2024/6/10 21:19
*/
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 动态代理代理的是接口
UserService userService = context.getBean("userService", UserService.class);
userService.select();
}
}
结果:
com.fatfish.aop.service.UserServiceImpl的select方法被执行了!
查询了一个用户
执行了selectcom.fatfish.aop.service.UserServiceImpl的返回结果为:null
2. 自定义类
切入类
package com.github.subei.diy;
public class DiyPointCut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
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"
xmlns:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注册bean -->
<bean id="userService" class="com.fatfish.aop.service.UserServiceImpl"/>
<bean id="log" class="com.fatfish.aop.log.Log"/>
<bean id="afterLog" class="com.fatfish.aop.log.AfterLog"/>
<!-- 方式一:使用原生Spring API接口 -->
<!-- 配置AOP:需要导入AOP的约束 -->
<aop:config>
<!-- 切入点:expression:表达式 , execution(要执行的位置!* * * *) -->
<aop:pointcut id="pointcut" expression="execution(* com.fatfish.aop.service.*.*(..))"/>
<!-- 执行环绕增加! advice-ref执行方法 , pointcut-ref切入点 -->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
<!-- 第二种:自定义类 -->
<bean id="diy" class="com.fatfish.aop.log.DiyPointCut"/>
<aop:config>
<!-- 自定义切面,ref要引用的类 -->
<aop:aspect ref="diy">
<!-- 切入点 -->
<aop:pointcut id="point" expression="execution(* com.fatfish.aop.service.UserServiceImpl.*(..))"/>
<!-- 通知 -->
<aop:before method="before" pointcut-ref="point" />
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
测试类与上面一致,测试结果如下:
com.fatfish.aop.service.UserServiceImpl的select方法被执行了!
---------方法执行前---------
查询了一个用户
---------方法执行后---------
执行了selectcom.fatfish.aop.service.UserServiceImpl的返回结果为:null
3. 使用注解实现
编写一个注解实现的增强类
package com.fatfish.aop.log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* @author fatfish
* @version 1.0
* @date 2024/6/10 22:09
*/
// 使用注解方式实现AOP
@Aspect // 标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.fatfish.aop.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.fatfish.aop.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
// 在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
@Around("execution(* com.fatfish.aop.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
Signature signature = jp.getSignature();
System.out.println("签名:"+ signature); // 获得签名
// 执行目标方法:proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
在Spring配置文件中,注册bean,并增加支持注解的配置。
<!-- 第三种方法:使用注解方式实现 -->
<bean id="annotationPointCut" class="com.fatfish.aop.log.AnnotationPointCut"/>
<!-- 开启注解支持! -->
<aop:aspectj-autoproxy/>
测试代码同上,结果如下:
22:25:34.076 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@442d9b6e
22:25:34.249 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 12 bean definitions from class path resource [applicationContext.xml]
22:25:34.265 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.aop.config.internalAutoProxyCreator'
22:25:34.345 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'userService'
22:25:34.363 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
22:25:34.378 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#1'
22:25:34.378 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.aop.aspectj.AspectJPointcutAdvisor#0'
22:25:34.468 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.aop.aspectj.AspectJPointcutAdvisor#1'
22:25:34.482 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public void com.fatfish.aop.log.AnnotationPointCut.around(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable
22:25:34.482 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public void com.fatfish.aop.log.AnnotationPointCut.before()
22:25:34.482 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public void com.fatfish.aop.log.AnnotationPointCut.after()
22:25:34.498 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'log'
22:25:34.498 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'afterLog'
22:25:34.514 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'diy'
22:25:34.514 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'annotationPointCut'
com.fatfish.aop.service.UserServiceImpl的select方法被执行了!
---------方法执行前---------
环绕前
签名:void com.fatfish.aop.service.UserService.select()
---------方法执行前---------
查询了一个用户
---------方法执行后---------
环绕后
null
---------方法执行后---------
执行了selectcom.fatfish.aop.service.UserServiceImpl的返回结果为:null
aop:aspectj-autoproxy——说明
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class=“true”/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
15. 静态资源过滤问题
在 Maven 项目中,静态资源过滤问题通常指的是在构建过程中,Maven 会对项目中的资源文件(如文本文件、XML 文件、属性文件等)进行过滤,即在部署或打包之前,将其中的占位符或变量替换为实际的值。这种过滤通常用于替换项目中的配置文件中的变量,比如将开发环境和生产环境中的配置信息进行替换。
然而,有时候 Maven 的静态资源过滤可能会导致一些问题,比如:
- 错误的变量替换:Maven 可能会误将某些内容识别为变量并进行替换,导致配置文件中的内容被错误地修改。
- 文件类型不匹配:Maven 可能会对不应该被过滤的文件进行了过滤,导致文件内容损坏或变得不可用。
- 过滤规则错误:配置文件中的过滤规则可能不正确,导致过滤效果不符预期。
要解决 Maven 静态资源过滤问题,可以尝试以下方法:
- 调整过滤规则:检查 Maven 的过滤配置,确保只对需要过滤的文件进行过滤,且过滤规则设置正确。
- 配置文件排除:如果有些文件不需要过滤,可以在 Maven 的配置中明确排除这些文件,避免对其进行过滤。
- 调整占位符:如果过滤的变量与其他内容冲突,考虑调整变量的占位符或替换方式,确保不会误替换其他内容。
- 使用不同的过滤方式:Maven 默认使用的是基于 ${} 形式的过滤方式,你可以尝试使用其他方式,比如使用 @ 符号或其他自定义的占位符。
- 仔细检查配置文件:检查被过滤的配置文件,确保其中的占位符和变量使用正确,不会误被替换。
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
16. Spring 整合Mybatis
Spring 整合 MyBatis 的主要原因之一是为了简化数据访问层的开发,以及利用 MyBatis 的优势来提高开发效率和灵活性。MyBatis 提供了一种将 SQL 查询映射到 Java 对象的简单方式,同时还提供了灵活的 SQL 编写和参数处理功能。Spring 则提供了一个全功能的框架,用于构建企业级 Java 应用程序,并提供了诸如依赖注入、事务管理、AOP 等功能。将两者结合可以使开发人员更轻松地管理数据库操作、事务和数据源配置,同时也能够充分利用 Spring 的各种特性和优势。
整合 MyBatis 和 Spring 的一种常见方式是使用 MyBatis-Spring 模块。这个模块提供了一些核心组件,用于将 MyBatis 和 Spring 集成在一起,包括:
-
SqlSessionFactoryBean: 这个类是整合的核心。它负责创建 MyBatis 的 SqlSessionFactory 实例,用于创建 SqlSession 对象,从而执行 SQL 查询。
-
MapperScannerConfigurer: 这个类可以帮助自动扫描指定包中的 Mapper 接口,并将其注册为 Spring 的 Bean,使得这些 Mapper 接口可以在 Spring 中被注入和使用。
-
TransactionManager: MyBatis-Spring 提供了对 Spring 事务管理器的支持,使得可以在 Spring 中管理 MyBatis 的事务,确保事务的一致性和完整性。
-
SqlSessionTemplate: 这个类提供了一种将 SqlSession 和 Spring 的事务管理结合起来的方式。它可以将 SqlSession 绑定到 Spring 的事务中,并确保在事务提交或回滚时正确关闭 SqlSession。
整合 MyBatis 和 Spring 的过程通常包括以下步骤:
-
添加依赖:在项目的 Maven 或 Gradle 配置文件中添加 MyBatis 和 MyBatis-Spring 的依赖。
-
配置数据源:在 Spring 的配置文件中配置数据源(如连接池),并将其注入到 MyBatis 的配置中。
-
配置 SqlSessionFactory:使用 SqlSessionFactoryBean 配置类创建 SqlSessionFactory,并将数据源等配置信息传递给它。
-
扫描 Mapper 接口:使用 MapperScannerConfigurer 类自动扫描 Mapper 接口,并将其注册为 Spring 的 Bean。
-
配置事务管理器:将 Spring 的事务管理器配置为 MyBatis 的事务管理器,以确保在 Spring 中管理 MyBatis 的事务。
-
使用 Mapper 接口:在业务逻辑中注入并使用 Mapper 接口,调用其中定义的方法执行数据库操作。
pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
<scope>test</scope>
</dependency>
User.java
public class User {
private Long id;
private String name;
// getters and setters
}
UserMapper.java
public interface UserMapper {
List<User> getAllUsers();
User getUserById(Long id);
void insertUser(User user);
void updateUser(User user);
void deleteUser(Long id);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="userResultMap" type="com.example.model.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
</resultMap>
<select id="getAllUsers" resultMap="userResultMap">
SELECT * FROM users
</select>
<select id="getUserById" resultMap="userResultMap" parameterType="java.lang.Long">
SELECT * FROM users WHERE id = #{id}
</select>
<insert id="insertUser" parameterType="com.example.model.User">
INSERT INTO users (id, name) VALUES (#{id}, #{name})
</insert>
<update id="updateUser" parameterType="com.example.model.User">
UPDATE users SET name = #{name} WHERE id = #{id}
</update>
<delete id="deleteUser" parameterType="java.lang.Long">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper>
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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 数据源配置 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:mem:testdb"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<!-- MyBatis SqlSessionFactory 配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath*:com/example/mapper/*.xml"/>
</bean>
<!-- Mapper 接口自动扫描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
</bean>
<!-- 事务管理器配置 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启注解驱动 -->
<context:annotation-config/>
<tx:annotation-driven/>
</beans>
Demo
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean(UserMapper.class);
// 插入用户
User user = new User();
user.setId(1L);
user.setName("Alice");
userMapper.insertUser(user);
// 查询所有用户
List<User> users = userMapper.getAllUsers();
System.out.println("All Users:");
for (User u : users) {
System.out.println(u.getId() + ": " + u.getName());
}
// 更新用户
user.setName("Bob");
userMapper.updateUser(user);
// 查询指定用户
User retrievedUser = userMapper.getUserById(1L);
System.out.println("Retrieved User: " + retrievedUser.getName());
// 删除用户
userMapper.deleteUser(1L);
}
}
17. 声明式事务
- 编程式事务管理
将事务管理代码嵌到业务方法中来控制事务的提交和回滚
缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
- 声明式事务管理==(交由容器管理事务)
一般情况下比编程式事务好用。
将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。
声明式事务是指在软件开发中使用一种声明性方式来定义事务的行为,而无需显式地编写事务管理代码。通常,声明式事务是通过在配置文件或注解中指定事务的属性和行为来实现的。
在Java领域中,Spring框架提供了声明式事务管理的支持。通过Spring的事务管理模块,开发者可以在方法或类级别声明事务属性,而不必在代码中编写显式的事务管理逻辑。在Spring中,常用的声明式事务管理方式包括基于XML配置和基于注解的配置两种方式。
基于XML配置的声明式事务管理通过在Spring配置文件中定义事务管理器、事务通知器以及事务的切入点来实现。开发者可以在XML配置文件中指定哪些方法需要被事务管理,以及事务的传播行为、隔离级别等属性。
**配置声明式事务**
<!-- 配置声明式事务 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
**配置好事务管理器后,去配置事务的通知**
<!-- 结合AOP实现事物的织入 --> <!-- 配置事务的通知: --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 配置哪些方法使用什么样的事务,配置事务的传播特性 --> <tx:method name="add" propagation="REQUIRED"/> <tx:method name="delete" propagation="REQUIRED"/> <tx:method name="update" propagation="REQUIRED"/> <tx:method name="search*" propagation="REQUIRED"/> <tx:method name="get" read-only="true"/> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
**配置AOP**
<!-- 配置事务的切入 --> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* com.github.subei.mapper.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config>
基于注解的声明式事务管理则是通过在方法上使用
@Transactional
注解来标记需要被事务管理的方法,开发者可以在注解中指定事务的属性,如传播行为、隔离级别等。这种方式简化了配置,使得事务管理更加灵活和方便。
applicationContext.xml
<context:component-scan base-package="com.example.service" /> <!-- 配置数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/mydb" /> <property name="username" value="root" /> <property name="password" value="password" /> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 开启对注解的支持 --> <tx:annotation-driven />
UserService.java
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class UserService { @Autowired private UserRepository userRepository; @Transactional public void createUser(User user) { userRepository.save(user); } @Transactional public void deleteUser(long userId) { userRepository.deleteById(userId); } @Transactional(readOnly = true) public User getUserById(long userId) { return userRepository.findById(userId).orElse(null); } // 其他方法... }
使用声明式事务管理的好处包括:
- 简化事务管理: 开发者无需编写大量的事务管理代码,而是通过配置文件或注解来定义事务的行为,减少了重复代码的编写。
- 提高可维护性: 通过将事务管理与业务逻辑分离,代码更易于理解和维护。
- 降低耦合性: 声明式事务将事务管理从业务逻辑中解耦,使得业务逻辑更加纯粹,更易于单元测试和重用。
- 灵活性: 可以通过配置文件或注解来动态调整事务的行为,满足不同业务场景下的需求。
18. spring事务传播特性
Spring 事务传播特性定义了在一个方法调用另一个方法时,当前方法的事务如何传播给被调用方法的行为。Spring 提供了多种事务传播特性,可以根据不同的需求选择合适的特性。以下是常见的 Spring 事务传播特性:
- PROPAGATION_REQUIRED(默认):
- 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- 这是最常用的传播特性,适用于绝大多数情况。
- PROPAGATION_SUPPORTS:
- 支持当前事务;如果当前没有事务,则以非事务方式执行。
- 如果调用者在事务中,则被调用方法也在该事务中;如果调用者不在事务中,则被调用方法以非事务方式执行。
- PROPAGATION_MANDATORY:
- 强制要求当前存在事务,否则抛出异常。
- 被调用方法必须在调用者的事务中执行,否则抛出异常。
- PROPAGATION_REQUIRES_NEW:
- 创建一个新的事务,并挂起当前事务(如果存在)。
- 被调用方法总是在新事务中执行,不受调用者事务的影响。
- PROPAGATION_NOT_SUPPORTED:
- 以非事务方式执行,并挂起当前事务(如果存在)。
- 被调用方法总是以非事务方式执行,不受调用者事务的影响。
- PROPAGATION_NEVER:
- 以非事务方式执行,如果当前存在事务,则抛出异常。
- 被调用方法不允许在任何事务中执行,如果调用者在事务中,则抛出异常。
- PROPAGATION_NESTED:
- 如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新的事务。
- 如果调用者在事务中,则被调用方法在嵌套事务中执行;如果调用者不在事务中,则行为类似于 PROPAGATION_REQUIRED。
这些传播特性可以在 @Transactional
注解中使用,例如:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// ...
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// ...
}
使用适当的事务传播特性可以确保事务在多个方法调用之间正确地传播和管理,从而保证数据的一致性和完整性。
参考
Spring学习目录(6天) - subeiLY - 博客园