Spring
约 11793 字大约 39 分钟
2025-01-31
1.Spring简述
1.1 什么是Spring框架
Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。
1.2 Spring的核心功能
Spring提供的核心功能为 IOC控制反转(Inversion of Control)和 AOP面向切面编程 (Aspect-Oriented Programming)
1.2.1 IOC
- IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。
- IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。
- 控制:指的是对象创建(实例化、管理)的权力
- 反转:控制权交给外部环境(Spring 框架、IoC 容器)
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。
1.2.2 AOP
- AOP(Aspect-Oriented Programming:面向切面编程):能够将那些与业务无关,却为业务模块所 共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
- Spring AOP 就是基于动态代理的
- 如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,
- 对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
术语 | 含义 |
---|---|
目标(Target) | 被通知的对象 |
代理(Proxy) | 向目标对象应用通知之后创建的代理对象 |
连接点(JoinPoint) | 目标对象的所属类中,定义的所有方法均为连接点 |
切入点(Pointcut) | 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点) |
通知(Advice) | 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情 |
切面(Aspect) | 切入点(Pointcut)+通知(Advice) |
Weaving(织入) | 将通知应用到目标对象,进而生成代理对象的过程动作 |
Spring AOP和AspectJ AOP有什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多
多个切面的执行顺序如何控制?
- 通常使用 @Order 注解直接定义切面顺序
// 值越小优先级越高
@Order(3)
@Component
@Aspect
public class LoggingAspect implements Ordered {
}
- 实现
Ordered
接口重写getOrder
方法
@Component
@Aspect
public class LoggingAspect implements Ordered {
// ....
@Override
public int getOrder() {
// 返回值越小优先级越高
return 1;
}
}
2.搭建Spring项目环境
2.1 安装导入Spring框架库
下载路径: https://repo.spring.io/release/org/springframework/spring
下载完成之后,在libs 目录中可以找到所有 Spring 库
IDEA导入Spring框架库
2.2 创建类
- HelloWorld类
public class HelloWorld {
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
- MainApp类——主应用程序
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
- 第一步是在我们使用框架 API ClassPathXmlApplicationContext() 的地方创建应用程序上下文。 此 API 加载 bean 配置文件,并最终基于提供的 API,它负责创建和初始化所有对象,即配置文件中提到的 bean。
- 第二步用于使用创建的上下文的 getBean() 方法获取所需的 bean。 该方法使用 bean ID 返回一个通用对象,最终可以转换为实际对象。 一旦有了对象,就可以使用该对象调用任何类方法。
2.3 Bean配置文件
该配置文件放置在src目录下即可
通常开发人员将此文件命名为 Beans.xml,必须确保该文件在 CLASSPATH 中可用,并在主应用程序中使用相同的名称
<?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-3.0.xsd">
<bean id = "helloWorld" class = "com.tutorialspoint.HelloWorld">
<property name = "message" value = "Hello World!"/>
</bean>
</beans>
运行MainApp程序,输出结果:
Your Message : Hello World!
3.Spring Bean
3.1 Bean定义
简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。
我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。
3.1.1 元数据—>Bean标签的属性
在IOC容器创建Bean对象时候,需要知道这三个信息
- 如何创建一个bean(class)
- bean的生命周期
- bean的依赖关系
所有配置元数据都转换为一组构成每个 bean 标签定义的以下属性:
序号 | 属性 | 描述 |
---|---|---|
1 | class | 指定了用于创建 bean 的 bean类型 |
2 | id或者name | 唯一地指定 bean 标识符 |
3 | scope | 指定从特定 bean 定义创建的对象的范围 |
4 | constructor-arg | 用于注入依赖关系 |
5 | properties | 用于注入依赖关系 |
6 | autowiring mode | 用于注入依赖关系 |
7 | lazy-initialization mode | 延迟初始化的 bean,即 告诉 IoC 容器在第一次被请求时创建一个 bean 实例,而不是在启动时 |
8 | initialization method | 在容器设置了 bean 上的所有必要属性之后调用的回调 |
9 | destruction method | 包含 bean 的容器被销毁时要使用的回调 |
3.1.2 配置方式
Spring IoC 容器与实际写入此配置元数据的格式完全分离。 以下是向 Spring 容器提供配置元数据的三个重要方法 −
- 基于 XML 的配置文件。(Spring)
- 基于注解的配置(SpringBoot)
- 基于 Java 的配置
XML方式
<bean id = "helloWorld" class = "com.tutorialspoint.HelloWorld">
<property name = "message" value = "Hello World!"/>
</bean>
注解方式
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个 Bean 不知道属于哪个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller
: 对应 Spring MVC 控制层,主要用于接受用户请求并调用Service
层返回数据给前端页面。
3.1.3 @Component和@Bean的区别
相同:两者都是用来声明一个Bean类,交给IOC容器处理
不同:
- 作用的地方不同:@Component 注解作用于类,而==@Bean注解作用于方法==。
- 产生的作用不同:
@Component
通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用@ComponentScan
注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean
注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean
告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
- 自定义强度不同:
@Bean
注解比@Component
注解的自定义性更强- 很多地方我们只能通过
@Bean
注解来注册 bean。比如当我们引用第三方库中的类需要装配到Spring
容器时,则只能通过@Bean
来实现
@Bean使用示例
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
上面的代码相当于下面的 xml 配置:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
3.2 Bean作用域
3.2.1 作用域分类
singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续
getBean()
两次,得到的是不同的 Bean 实例。request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
3.2.2 配置作用域的方式
xml方式
<bean id="..." class="..." scope="singleton"></bean>
注解方式
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
return new Person();
}
3.3 Bean生命周期
对于普通的 Java 对象来说,它们的生命周期就是:实例化、该对象不再被使用时通过垃圾回收机制进行回收
对于 Spring Bean 的生命周期来说:
- 实例化bean
- bean属性赋值
- 初始化bean
- 执行处理aware接口,分别调用 setBeanName()、setBeanFactory()、 setApplicationContext()方法
- 调用BeanPostProcessor的预初始化方法
- 调用InitializingBean的afterPropertiesSet()方法
- 调用init-method属性指定初始化方法
- 调用BeanPostProcessor的初始化后方法
- 使用bean
- 销毁bean
- 调用DisposableBean的destroy方法
- 调用destroy-method属性指定的方法
3.3.1 初始化回调
org.springframework.beans.factory.InitializingBean 接口指定单个方法
void afterPropertiesSet() throws Exception;
可以简单地实现上述接口,并且可以在 afterPropertiesSet() 方法中完成初始化工作,如下所示 −
public class ExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
public class ExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
基于 XML 的配置元数据的情况下,您可以使用 init-method 属性来指定具有 void 无参数签名的方法的名称。
<bean id = "exampleBean" class = "examples.ExampleBean" init-method = "init"/>
- 下面是类的定义
public class ExampleBean {
public void init() {
// do some initialization work
}
}
3.3.2 销毁回调
DisposableBean的destroy方法
org.springframework.beans.factory.DisposableBean 接口指定一个方法
void destroy() throws Exception;
因此,您可以简单地实现上述接口,并且可以在destroy() 方法中完成如下工作
public class ExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work
}
}
destroy-method属性指定销毁方法
- xml配置文件
<bean id = "exampleBean" class = "examples.ExampleBean" destroy-method = "destroy"/>
- 类的定义
public class ExampleBean {
public void destroy() {
// do some destruction work
}
}
3.4 Bean依赖注入
3.4.1 定义
- 依赖:bean对象的依赖于容器
- 注入:bean对象中的所有属性,由容器来注入
依赖注入,是IOC的一个方面,是个通常的概念,它有多种解释。这概念是说你不用创建对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器(IOC容器)负责把他们组装起来。
3.4.2 依赖注入的作用
作用:降低程序间的耦合
依赖关系的管理,以后都交给spring来维护
在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明依赖关系的维护,就称之为依赖注入。
3.4.3 依赖注入的方式
可以注入的数据:
- 基本类型和String
- 其他bean类型(在配置文件中或者注解配置过的bean)
- 复杂类型/集合类型:(不演示)
注入的方式:
- 构造函数注入
- set方法注入
- 基于xml的自动装配(bean标签的autowire属性(byName—>根据bean标签的id属性查找、byType——>根据bean标签的Class属性查找))
- 注解注入
1、构造函数注入
定义:用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。
说明:
- 使用的标签:constructor-arg
- 标签出现的位置:bean标签的内部
- 标签中的属性:
- type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型。
- index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始。
- name:用于指定给构造函数中指定名称的参数赋值。
- ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
- value:要注入的数据
- 优点:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
- 缺点:改变了bean对象的实例化方式,使得我们在创建对象时,如果用不到这些数据,也必须提供。
举例
<!--默认构造器方式--> <bean id="user" class="com.kuang.pojo.User"> <property name="name" value="张三"/> </bean> <!--通过有参构造创建对象。方式一:下标赋值--> <bean id="user" class="com.kuang.pojo.User"> <constructor-arg index="0" value="jerry"/> </bean> <!--通过有参构造创建对象。方式二:类型创建,不建议使用--> <bean id="user" class="com.kuang.pojo.User"> <constructor-arg type="java.lang.String" value="jarry"/> </bean> <!--通过有参构造创建对象。方式三:通过参数名,推荐使用--> <bean id="user" class="com.kuang.pojo.User"> <constructor-arg name="name" value="jarry"/> <constructor-arg name="birthday" ref="now"/> </bean> <!-- 配置一个日期对象 --> <bean id="now" class="java.util.Date"></bean>
Set方式注入(常用)
- 定义:在类中提供需要注入成员的 set 方法
- 说明:
- 涉及的标签:property
- 出现的位置:bean标签的内部
- 标签的属性
- name:用于指定注入时注入的对象的名称(IOC容器会调用该对象的set方法)
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他的bean类型数据。
- 优势:创建对象时没有明确的限制,可以直接使用默认构造函数。
- 弊端:如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
基于xml的自动装配
- 定义:
- 自动装配是spring满足bean依赖注解一种方式
- Spring会在上下文中自动寻找,并自动给bean装配属性
- 三种自动装配方式:
- 在xmI中显示的配置(ByName、ByType)
- 注解显示配置
- 隐式的自动装配bean 【重要】
①搭建环境
public class Cat {
public void shout(){
System.out.println("喵喵");
}
}
public class Dog {
public void shout(){
System.out.println("旺旺");
}
}
public class Person {
private Cat cat;
private Dog dog;
private String name;
@Override
public String toString() {
return "Person{" +
"cat=" + cat +
", dog=" + dog +
", name='" + name + '\'' +
'}';
}
//getting、setting
}
②ByName自动装配
- byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的beanId
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="person" class="com.kuang.pojo.Person" autowire="byName">
<property name="name" value="小海子"/>
</bean>
③ByType自动装配
- byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean
<bean class="com.kuang.pojo.Cat"/>
<bean class="com.kuang.pojo.Dog"/>
<bean id="person" class="com.kuang.pojo.Person" autowire="byType">
<property name="name" value="小海子"/>
</bean>
④小结
- byname的时候,需要保证所有bean的id唯一 ,并且这个bean需要和自动注入的属性的set方法的值一致。
- bytype的时候, 需要保证所有bean的class唯一 ,并且这个bean需要和自动注入的属性的类型一致;全局唯一,id属性可以省略。
基于注解的自动装配
jdk1.5支持的注解,Spring2.5支持注解
作用:和在xml配置文件中的bean标签中写一个标签的作用是一样
注解:
注解 功能 使用前提 @Autowired 根据属性类型进行自动装配
即通过byType——>bean标签的class属性方式实现/ @Qualifer 根据属性名称(由@Component的value值指定)进行注入
即通过byName——>bean标签的id属性实现在该属性注入的基础上 @Resource 根据类型注入,可以根据名称注入(默认根据类型注入) / @Value 注入普通类型属性 /
3.4.4 @Autowired、@Qualifer、@Resource、@Value区别
spring的IOC底层实际上就是一个Map结构容器,所谓**key 就是 bean标签 中的 id,value 则是对应 bean标签 中的 class**
@Autowired | @Qualifer | @Resource | |
---|---|---|---|
使用场景 | 变量 | 变量 | 变量 |
作用 | |||
(1)
①用在何处?
- Spring 2.5 引入了 @Autowired 注释,可以对类成员变量、方法及构造函数、参数等进行标注【主要还是用在变量和方法上】,完成自动装配的工作。
- 使用@Autowired注解注入的属性,该属性不需要这个类提供set方法,方便快捷
- @Autowired作用就和在xml配置文件中的bean标签中写一个
< property >
标签的作用是一样的。 - @Autowired自动装配首先会在IOC容器中跳过key直接去容器中找到对应的属性(bean标签中的Class)!也就是说与key无关
②三种情况
- 容器中有==唯一的一个bean对象类型(Class类路径)==和被@Autowired修饰的变量类型匹配,就可以注入成功!
- 容器中没有一个bean对象类型和被@Autowired修饰的变量类型匹配,则注入失败运行报错。
- 容器中有==多个bean对象类型和被@Autowired修饰的变量类型匹配,则根据被@Autowired修饰的变量名寻找==,找到则注入成功【重点】
(2)
①用在何处
- @Qualifier的作用是在按照类中注入的基础之上再按照名称注入。
- 它在给类成员注入时不能单独使用(但是在给方法参数注入时可以单独使用)
- @Qualifier常常组合@Autowired一起使用,用来==指明具体名字的自动装配==
②使用情况
- UserDaoImpl2类
- UserService3类注入UserDaoImpl2类
(3)@Resource
@Resource由J2EE提供,默认是按照byName自动注入(通过名字自动注入),
@Resource有两个重要的属性,name和type
当然默认是通过name属性
type属性多此一举,还不如用@Autowired
@Resource 相当于 @Autowired + @Qualifier
举例:
@Autowired @Qualifier(value="userDaoImpl2") //相当于 @Resource(name="userDaoImpl2")
(4)@Value
- @Value专门用来注入基本类型和String类型数据
- @Value注解有一个value 属性:用于指定数据的值。它可以使用spring中SpEL(也就是spring的EL表达式)。SpEL的写法:${表达式},当然也可以类似mybatis中的 #{表达式} 的写法
@Value("#{2*3}") //#写法 表示6
private int age;
@Value("178") //普遍写法 178
private int height;
@Value("${man.weight}") //SpEL的写法一般操作配置文件中数据
private int weight;
(5)辨别@Autowired和@Resource的异同
- 相同:两者都是用来自动装配的,都可以放在属性字段上
- 不同:
- 实现方式不同:
- @ Autowired 通过byType的方式实现
- @Resource 默认通过byname的方式实现
- 找的过程不同:
- 若@ Autowired 通过byType的方式找不到,则报错
- 若@Resource 默认通过byname的方式找不到,则通过byType实现。如果两个都找不到的情况下,就报错。
- 实现方式不同:
4.AOP面向切面编程
4.1 什么是AOP
AOP(Aspect Oriented Programming)即面向切面编程,AOP 是 OOP(面向对象编程)的一种延续,二者互补,并不对立。
AOP 的目的是将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从核心业务逻辑中分离出来,通过动态代理、字节码操作等技术,实现代码的复用和解耦,提高代码的可维护性和可扩展性。
4.2 相关术语
AOP 之所以叫面向切面编程,是因为它的核心思想就是将横切关注点从核心业务逻辑中分离出来,形成一个个的切面(Aspect)。
这里顺带总结一下 AOP 关键术语(不理解也没关系,可以继续往下看):
- 横切关注点(cross-cutting concerns) :多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂等等)。
- 切面(Aspect):对横切关注点进行封装的类,一个切面是一个类。切面可以定义多个通知,用来实现具体的功能。
- 连接点(JoinPoint):连接点是方法调用或者方法执行时的某个特定时刻(如方法调用、异常抛出等)。
- 通知(Advice):通知就是切面在某个连接点要执行的操作。通知有五种类型,分别是前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。前四种通知都是在目标方法的前后执行,而环绕通知可以控制目标方法的执行过程。
- 切点(Pointcut):一个切点是一个表达式,它用来匹配哪些连接点需要被切面所增强。切点可以通过注解、正则表达式、逻辑运算等方式来定义。比如
execution(* com.xyz.service..*(..))
匹配com.xyz.service
包及其子包下的类或接口。 - 织入(Weaving):织入是将切面和目标对象连接起来的过程,也就是将通知应用到切点匹配的连接点上。常见的织入时机有两种,分别是编译期织入(AspectJ)和运行期织入(AspectJ)。
4.3 AOP应用场景
- 日志记录:自定义日志记录注解,利用 AOP,一行代码即可实现日志记录。
- 性能统计:利用 AOP 在目标方法的执行前后统计方法的执行时间,方便优化和分析。
- 事务管理:
@Transactional
注解可以让 Spring 为我们进行事务管理比如回滚异常操作,免去了重复的事务管理逻辑。@Transactional
注解就是基于 AOP 实现的。 - 权限控制:利用 AOP 在目标方法执行前判断用户是否具备所需要的权限,如果具备,就执行目标方法,否则就不执行。例如,SpringSecurity 利用
@PreAuthorize
注解一行代码即可自定义权限校验。 - 接口限流:利用 AOP 在目标方法执行前通过具体的限流算法和实现对请求进行限流处理。
- 缓存管理:利用 AOP 在目标方法执行前后进行缓存的读取和更新。
4.4 AOP实现方式
- AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。
- 静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
- 静态代理
- AspectJ是静态代理的增强,即AOP框架会在编译阶段生成AOP代理类
- 因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
- 动态代理
- AOP框架不会去修改字节码,
- 每次运行时在内存中临时为方法生成一个AOP对象
- 这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
4.5.1 动态代理的实现
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
(1)JDK代理
- JDK动态代理只提供接口的代理,不支持类的代理。
- 核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
- InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; proxy 是代理对象的引用;method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
(2)CGLB代理
- 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
- CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
(3)基于接口的动态代理
public class Test1 {
@Test
public void test1(){
//被代理类对象要声明为最终的
final Producer producer=new Producer();
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 涉及的类:Proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
* Class[]:字节码数组
* 它是用于让代理对象和被代理对象相同方法。固定写法。
* InvocationHandler:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
*/
//代理对象和被代理类对象要实现同一个接口
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* @param proxy 代理对象的引用
* 1. 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName()。
* 2. 可以将代理对象返回以进行连续调用,这就是proxy存在的目的,因为this并不是代理对象。
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object value=null;
//获取方法执行的参数
//判断当前方法是不是销售
if ("saleProduct".equals(method.getName())){
Float money= (Float) args[0];
//两个参数:被代理类对象,方法增强的参数
value=method.invoke(producer,money*0.8f);
}
return value;
}
});
proxyProducer.saleProduct(10000f);
}
}
- IProducer 生产者的接口
public interface IProducer {
/**
* 销售
* @param money
*/
public void saleProduct(float money);
/**
* 售后
* @param money
*/
public void afterService(float money);
}
- Producer 生产者实现类:
public class Producer implements IProducer{
/**
* 销售
* @param money
*/
public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
(4)基于子类的动态代理
public class Test4 {
@Test
public void test() {
final Producer producer = new Producer();
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于子类的动态代理:
* 涉及的类:Enhancer
* 提供者:第方cglib库
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类是最终类
* create方法的参数:
* Class:字节码
* 它是用于指定被代理对象的字节码。
*
* intercept:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
*/
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float) args[0];
//2.判断当前方法是不是销售
if ("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
}
}
- 生产者类
public class Producer {
/**
* 销售
* @param money
*/
public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
(5)使用动态代理对spring进行方法增强
- 实现类
public class java1 implements MyInterface{
public int add(int a,int b){
return a+b;
}
public int del(int a,int b){
return a-b;
}
public int che(int a,int b){
return a*b;
}
public int div(int a,int b){
return a/b;
}
}
- BeanFactory 类
public class BeanFactory {
private java1 java;
public void setJava(java1 java) {
this.java = java;
}
public MyInterface getBean(){
MyInterface proxyJava = (MyInterface) Proxy.newProxyInstance(
java.getClass().getClassLoader(),
java.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object value = null;
System.out.println("方法执行前....");
value = method.invoke(java, args);
System.out.println("方法执行之后....");
return value;
}
}
);
return proxyJava;
}
}
- 配置文件
<bean id="factory" class="com.atguigu.factory.BeanFactory">
<property name="java" ref="java"></property>
</bean>
<bean id="java" class="com.atguigu.java1.java1"></bean>
<bean id="proxyJava" factory-bean="factory" factory-method="getBean"></bean>
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test1 {
@Autowired
@Qualifier("proxyJava")
private MyInterface myInterface;
@Test
public void test1(){
System.out.println(myInterface.add(1, 2));
}
@Test
public void test2(){
System.out.println(myInterface.del(1, 2));
}
@Test
public void test3(){
System.out.println(myInterface.che(1, 2));
}
@Test
public void test4(){
System.out.println(myInterface.div(1, 2));
}
}
4.5.2基于xml的配置
(1)步骤说明
把通知Bean也交给spring来管理
使用 aop:config标签表明开始AOP的配置
使用 aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的Id。
在aop:aspect标签的内部使用对应标签来配置通知的类型。 示例:让printLog方法在切入点方法执行之前:所以是前置通知,aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
举例
<aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
(2)切入点表达式写法
关键字:execution(表达式)
表达式:访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是几级包,就需要写几个*.
* *.*.*.*.AccountServiceImpl.saveAccount())
包名可以使用…表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配
* *..*.*()
- 参数列表
- 基本类型直接写名称 int
- 引用类型写包名.类名的方式 java.lang.String
- 可以使用通配符表示任意类型,但是必须参数
- 可以使用…表示无参数均可,参数可以是任意类型
全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法: 切到业务层实现类下的所方法:
* com.itheima.service.impl.*.*(..)
(3)通知类型说明
- 前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
- 返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
- 抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
- 后通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- 环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。
(4)代码实现
- 配置文件:
<?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"
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">
<!-- 配置srping的Ioc,把service对象配置进来-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
此标签写在aop:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect外面,此时就变成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
- 实现类
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService{
public void saveAccount() {
System.out.println("执行了保存");
// int i=1/0;
}
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
- 日志类
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
}
- 测试类
public class AOPTest {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
}
}
4.5.3基于注解的配置(重要)
(1)步骤说明
- 首先在配置文件里开启声明式aop注解支持
<!--开启声明式事务注解,对aop支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 在logging类上声明其为一个切面类
@Aspect //表示当前类是一个切面类
- 在类中声明一个方法作为切入点表达式
@Pointcut("execution(* com.itheima.service.impl.*.*(..))") //表示切入点
private void pt1(){
}
@Pointcut("execution(
* 包名.方法名.参数类型
*)")
- 在各个方法上添加注解
@Component("logger") //表示把当前类放入spring容器中
@Before("pt1()") //表示前置通知,在方法执行之前执行
@AfterReturning("pt1()") //表示后置通知,在方法执行之后执行
@AfterRunning:返回通知,在方法返回结果之后执行
@AfterThrowing("pt1()") //表示异常通知,在方法抛出异常之后执行
@After("pt1()") //表示最终通知
@Around("pt1()") //表示环绕通知,围绕着方法执行
@Service("accountService") //表示当前类是一个业务层
- 设置切面优先级
@Order(2)//通过@Order(2)注解指定切面优先级,value值越小,优先级越高,默认是int最大值。
注解:(context名称空间和约束)
(2)代码实现
- 配置文件:
<?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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- 日志类
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){}
/**
* 前置通知
*/
// @Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
// @AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
// @AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
// @After("pt1()")
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
@Around("pt1()")
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
- 实现类
/**
* 账户的业务层实现类
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
public void saveAccount() {
System.out.println("执行了保存");
int i=1/0;
}
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
- 测试类
/**
* 测试AOP的配置
*/
public class AOPTest {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
}
}
5.Spring事务
5.1 基本概念
事务:把一组业务当成一个业务来做;要么都成功,要么都失败。
事务的四个特性
- 原子性:事务应被视为单个操作单元,这意味着整个操作序列要么成功,要么不成功。
- 一致性:这代表了数据库的引用完整性、表中唯一主键等的一致性。
- 隔离性:一个事务的执行不能其它事务干扰。
- 持续性:一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。
5.2 Spring的事务管理
Spring支持编程式事务管理和声明式事务管理两种方式:
- 编程式事务管理:通过
TransactionTemplate
或者TransactionManager
手动管理事务,实际应用中很少使用 - 声明式事务管理:建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务
声明式事务管理有两种实现方式:XML、注解
5.2.1 基于XML方式
基本步骤如下:
配置事务管理器
配置事务的通知:此时我们需要 导入事务的约束 tx名称空间和约束,同时也需要aop的。
使用tx:advice标签配置事务通知。
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用。
举例:
<tx:advice id = "txAdvice" transaction-manager = "transactionManager"> <tx:attributes> <tx:method name = "create"/> </tx:attributes> </tx:advice>
配置AOP中的通用切入点表达式
建立事务通知和切入点表达式的对应关系
配置事务的属性:在事务的通知tx:advice标签的内部。
5.2.2 基于注解方式
基本步骤:
配置事务管理器
开启spring对注解事务的支持
在需要事务支持的地方使用 @Transactional注解(业务层的实现)
作用范围:
- 方法:推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。
- 类:如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。
- 接口:不推荐在接口上使用。
属性:
- propagation:事务的传播行为,默认值为 REQUIRED
- isolation:事务的隔离级别,默认值采用 DEFAULT
- readOnly:指定事务是否为只读事务,默认值为 false。(对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中)
- rollbackFor:用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。
- timeout:事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。
举例:
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class}, isolation = Isolation.DEFAULT, readOnly = false)
5.3 @Transactional注解属性
- **propagation属性(七种):**代表事务的传播行为,默认值为 Propagation.REQUIRED
- required:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )
- requires_new:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )
- supports:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行
- not_supported:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
- mandatory:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常
- never:以非事务的方式运行,如果当前存在事务,则抛出异常
- nested:和 Propagation.REQUIRED 效果一样
- **isolation属性:**事务隔离级别,默认值为 Isolation.DEFAULT。
- DEFAULT:使用底层数据库默认的隔离级别
- READ_UNCOMMITTED:读取未提交的
- READ_COMMITTED:读取提交的
- REPEATABLE_READ:可重读
- SERIALIZABLE:串行化
- **timeout属性:**事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务
- **readOnly属性:**指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true
- **roallbackFor属性:**用于指定能够触发事务回滚的异常类型,可以指定多个异常类型
- **noRollbackFor属性:**抛出指定的异常类型,不回滚事务,也可以指定多个异常类型
5.4 @Transacional失效场景
- @Transactional 应用在非 public 修饰的方法上
- 事务传播方式配置错误,以下三种 propagation,事务将不会发生回滚
- PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- 触发事务回滚的异常类型配置错误,Spring管理事务默认回滚的异常是运行时异常或错误,如果是检查异常(文件找不到异常),事务不会回滚
- 同一类中方法调用,导致@Transactional失效
- 开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。
6.Spring的设计模式
- 工厂模式:IoC 容器可以看作是一个巨大的工厂,负责创建和管理 Bean 的生命周期和依赖关系
- 单例模式:Spring 容器中的 Bean 默认都是单例的,这样可以保证 Bean 的唯一性,减少系统开销
- 模板模式:Spring 中的 JdbcTemplate,HibernateTemplate 等以 Template 结尾的类,都使用了模板方法模式
- 适配器模式:Spring MVC 中的 HandlerAdapter 就用了适配器模式。它允许 DispatcherServlet 通过统一的适配器接口与多种类型的请求处理器进行交互
- 代理模式:AOP 的实现就是基于代理模式的,如果配置了事务管理,Spring 会使用代理模式创建一个连接数据库的代理对象,来进行事务管理
- 观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用,Spring 中的 ApplicationListener 就是观察者,当有事件(ApplicationEvent)被发布,ApplicationListener 就能接收到信息
7.Spring常用注解
- Web:
- @Controller
- @RestController
- @RequestMapping:4个
- @ResponseBody、@RequestBody
- @PathVariable、@RequestParam
- 容器:
- @Component、@Bean、@Configuration
- @Autowired、@Resource、@Qualifier
- @Value
- AOP:
- @Aspect:@Before、@After、@Around、@PointCut
- 事务:@Transacional