目录
9、HandlerExceptionResolver(异常处理器)
15、FlashMapManager(Flash 属性管理器)
17、RequestToViewNameTranslator(ViewName翻译器)
33、@EnableTransactionManagement
一、Spring
IOC控制反转
应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的。这样控制权就由应用转移到了外部容器,控制权的转移就是所谓反转,即控制反转是指new实例工作不由我们程序员来做而是交给spring容器来做。
DI依赖注入
依赖注入(Dependency Injection,简称DI)是一种设计模式,其目的是减少软件组件之间的耦合度,提高代码的可重用性和可维护性。这是控制反转的一种形式
- 依赖:bean 对象的创建依赖于容器。应用程序依赖于IOC容器
- 注入:bean 对象中的所有属性,由容器来注入!IOC容器注入某个对象所需要的外部资源(包括对象、资源、常量数据)
几种注入方式:
1、Setter方法注入【常用】
指通过setter方法将依赖项注入到对象中。在setter方法中,将依赖项作为参数传入,然后将其保存在成员变量中。Set属性注入:必须要有一个无参的构造方法,否则只能用构造方法构造
Setter方法注入需要在对象创建之后进行注入,因此需要确保依赖项已经创建并初始化完成。
Setter方法注入可以保证依赖项的可变性。在实际开发中,如果依赖项可能发生变化,或者是可选的,可以考虑使用Setter方法注入。
优点:这种方式可以在对象的整个生命周期内动态地改变依赖,具有很高的灵活性。
缺点:依赖对象初始化完成后由于尚末注入被依赖对象,因此还不能使用。
通过<bean>标签下的<property>标签进行注入:
- 若要注入简单值,则通过<property>的value属性
- 若要注入引用其他的bean的id或name,则使用<property>的ref属性
- 若要注入集合,则<property>下有对应<set>、<list>、<map>、<props>。若注入简单值,直接使用<value>;如果集合中是引用的其他bean,使用<ref>
- 对于map存储的是键值对,应使用<enter>标签。如果键是简单值,则使用key属性指定其值,如果是引用的其他bean,使用key-ref属性指定bean的id或name;如果值是简单值,则使用value属性指定其值,如果是引用的其他bean,使用value-ref属性指定bean的id或name
- 注入Properties集合,可以使用<props>或<value>
public class SpringAction {
//注入简单值
private String name;
public void setName(String name) {
this.name = name;
}
//注入对象springDao
private SpringDao springDao;
public void setSpringDao(SpringDao springDao) {
this.springDao = springDao;
}
public void ok(){
springDao.ok();
}
}
--------------------------------------
<!--配置bean,配置后该类由spring管理-->
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<!--1、若要注入简单值(基本数据类型或字符串),则通过value属性-->
<property name="name" value="小明" />
<!--2、若要注入引用其他的bean的id或name,则使用ref属性-->
<property name="springDao" ref="springDao"></property>
<!--3、若要注入集合,则<property>下有对应<set>、<list>、<map>、<props>。若注入简单值,直接使用<value>;如果集合中是引用的其他bean,使用<ref>-->
<property name="branchNames">
<!-- set标记用于注入set集合的值 -->
<set>
<!-- 如果集合中是引用的其他bean,使用ref标记,通过bean指定引用的bean的id或name -->
<!-- <ref bean="id或name" /> -->
<!-- 如果集合中是简单值,使用value指定值即可 -->
<value>山东分公司</value>
<value>山西分公司</value>
<value>广东分公司</value>
<value>广西分公司</value>
<value>海南分公司</value>
</set>
</property>
<property name="partnerNames">
<list>
<!-- 如果集合中是引用的其他bean,使用ref标记,通过bean指定引用的bean的id或name -->
<!-- <ref bean="id或name" /> -->
<!-- 如果集合中是简单值,使用value指定值即可 -->
<value>Oracle</value>
<value>Microsoft</value>
<value>IBM</value>
<value>Google</value>
</list>
</property>
<property name="employeeList">
<list>
<!-- 如果集合中是引用的其他bean,使用ref标记,通过bean指定引用的bean的id或name -->
<ref bean="emp1" />
<ref bean="emp2" />
</list>
</property>
<!--4、对于map存储的是键值对,应使用<enter>标签。如果键是简单值,则使用key属性指定其值,如果是引用的其他bean,使用key-ref属性指定bean的id或name;如果值是简单值,则使用value属性指定其值,如果是引用的其他bean,使用value-ref属性指定bean的id或name-->
<property name="branchManagers">
<map>
<!-- entry用于指定一项数据的注入 -->
<!-- 如果键是简单值,则使用key指定其值,如果是引用的其他bean,使用key-ref指定bean的id或name -->
<!-- 如果值是简单值,则使用value指定其值,如果是引用的其他bean,使用value-ref指定bean的id或name -->
<entry key="山东分公司" value-ref="emp1" />
<entry key="山西分公司" value-ref="emp2" />
</map>
</property>
<!--5、注入Properties集合,可以使用<props>或<value>-->
<property name="extraInfo1">
<props>
<prop key="registeredCapital">1000000</prop>
<prop key="legalPerson">小胖</prop>
<prop key="type">Joint venture</prop>
</props>
</property>
<property name="extraInfo2">
<value>
registeredCapital=2000000
legalPerson=Allen
type=Joint venture
</value>
</property>
</bean>
<bean name="springDao" class="com.bless.springdemo.dao.impl.SpringDaoImpl"></bean>
2、构造方法注入
指通过构造方法将依赖项注入到对象中。在构造方法中,将依赖项作为参数传入,然后在对象被创建时将其保存在成员变量中。
构造器注入需要在对象被创建时完成注入,因此需要确保依赖项已经创建并初始化完成。
构造器注入是一种简单有效的依赖注入方式,可以保证依赖项的不可变性。在实际开发中,如果依赖项是必需的,且不需要在对象生命周期内发生变化,可以考虑使用构造器注入。
优点:构造方法能够清晰地表现出类对其他类的依赖,使代码易于理解和维护。对象初始化完成后便可获得可使用的对象。
缺点:可能导致构造函数参数过多,使代码显得臃肿。不够灵活。若有多种注入方式,每种方式只需注入指定几个依赖,那么就需要提供多个重载的构造函数,麻烦。此外,如果某些类需要使用默认构造函数,那么这种方式就无法使用了。
通过<bean>标签下的<constructor-arg>标签实现注入:
- 注入简单值,通过value属性指定要注入的值即可
- 如果注入引用的其他bean,使用ref属性指定要注入的bean的id或name
- 若要注入集合,则<constructor-arg>下有对应<set>、<list>、<map>、<props>。若注入简单值,直接使用<value>;如果集合中是引用的其他bean,使用<ref>
- 对于map存储的是键值对,应使用<enter>标签。如果键是简单值,则使用key属性指定其值,如果是引用的其他bean,使用key-ref属性指定bean的id或name;如果值是简单值,则使用value属性指定其值,如果是引用的其他bean,使用value-ref属性指定bean的id或name
- 注入Properties集合,可以使用<props>或<value>
注意,如果使用setter注入,<constructor-arg>的name属性是类中的属性名;但这里使用构造函数注入,<constructor-arg>的name属性指定的是构造函数的参数的名称。也可不写参数名称,但是必须保证参数的顺序是对的。可以使用参数索引index属性(0表示第一个参数),这样参数顺序随便写。但是如果不写参数名称,也不写参数索引,则参数顺序必须正确。
public class SpringAction {
//注入简单值
private String id;
public void setId(String id) {
this.id= id;
}
//注入对象springDao
private SpringDao springDao;
private User user;
public SpringAction(String id,SpringDao springDao,User user){
this.id= id;
this.springDao = springDao;
this.user = user;
System.out.println("构造方法调用springDao和user");
}
public void save(){
user.setName("卡卡");
springDao.save(user);
}
}
----------------------------------------------------
<!--配置bean,配置后该类由spring管理-->
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<!--1、注入简单值(基本数据类型或字符串)-->
<constructor-arg name="id" value="001" />
<!--2、注入引用的其他bean(引用类型)-->
<constructor-arg name="springDao" ref="springDao" />
<!--3、注入集合(Set、List、Map、Properties) -->
<constructor-arg name="branchNames">
<set>
<!-- 如果集合中是简单值,使用value指定值即可 -->
<value>山东分公司</value>
<value>山西分公司</value>
<value>广东分公司</value>
<value>广西分公司</value>
<value>海南分公司</value>
</set>
</constructor-arg>
<constructor-arg name="partnerNames">
<list>
<value>Oracle</value>
<value>Microsoft</value>
<value>IBM</value>
<value>Google</value>
</list>
</constructor-arg>
<constructor-arg name="employeeList">
<list>
<!-- 如果集合中是引用的其他bean,使用ref标记,通过bean指定引用的bean的id或name -->
<ref bean="emp1" />
<ref bean="emp2" />
</list>
</constructor-arg>
<!--4、注入map-->
<constructor-arg name="branchManagers">
<map>
<!-- entry用于指定一项数据的注入 -->
<!-- 如果键是简单值,则使用key指定其值,如果是引用的其他bean,使用key-ref指定bean的id或name -->
<!-- 如果值是简单值,则使用value指定其值,如果是引用的其他bean,使用value-ref指定bean的id或name -->
<entry key="山东分公司" value-ref="emp1" />
<entry key="山西分公司" value-ref="emp2" />
</map>
</constructor-arg>
<!-- 5、注入Properties集合,可以使用props标记或value标记 -->
<constructor-arg name="extraInfo1">
<props>
<prop key="registeredCapital">1000000</prop>
<prop key="legalPerson">小胖</prop>
<prop key="type">Joint venture</prop>
</props>
</constructor-arg>
<constructor-arg name="extraInfo2">
<value>
registeredCapital=2000000
legalPerson=Allen
type=Joint venture
</value>
</constructor-arg>
</bean>
<bean name="springDao" class="com.bless.springdemo.dao.impl.SpringDaoImpl"></bean>
解决构造方法参数的不确定性,你可能会遇到构造方法传入的两参数都是同类型的,为了分清哪个该赋对应值,则需要进行一些小处理:
//法一:设置参数位置:
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<constructor-arg index="0" ref="springDao"></constructor-arg>
<constructor-arg index="1" ref="user"></constructor-arg>
</bean>
//法二:设置参数类型
<constructor-arg type="java.lang.String" ref=""/>
//constructor-arg标签,属性含义:
type属性:使用类型指定构造方法中参数的类型
index属性:使用构造方法中参数索引值来 进行注入
name属性 :使用构造方法中参数名称 来注入 ( 常用!)
value属性:要注入的值(基本数据类型和String类型)
ref属性:要注入的值(引用在IOC容器中其他的bean对象)
指通过注解将依赖项注入到对象中。在依赖项上添加注解,然后在对象中使用@Autowired注解将依赖项注入到对象中。
注解注入需要在对象创建之后进行注入,因此需要确保依赖项已经创建并初始化完成。
注解注入是一种简单便捷的依赖注入方式,可以保证依赖项的可变性。在实际开发中,如果使用Spring等框架,可以考虑使用注解注入。
@Component
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public User getUserById(int id) {
return userRepository.getUserById(id);
}
}
4、接口注入
接口注入是指通过实现接口将依赖项注入到对象中。在接口中定义依赖项的setter方法,然后在实现类中实现该方法,将依赖项注入到对象中。
接口注入需要定义接口,并且在实现类中实现接口中的方法。
接口注入相对于构造方法注入和Setter方法注入,需要定义额外的接口,增加了代码复杂度,但可以保证依赖项的可变性。
优点:接口名字、函数名字都不重要,只要保证函数的参数是要注入的对象类型即可。
缺点:侵入性(如果类A要使用别人提供的一个功能,若为了使用这功能,需要在自己的类中增加额外的代码,这就是侵入性。)太强,不建议使用。
public interface UserRepositorySetter {
void setUserRepository(UserRepository userRepository);
}
public class UserService implements UserRepositorySetter {
private UserRepository userRepository;
@Override
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(int id) {
return userRepository.getUserById(id);
}
}
AOP面向切面编程
AOP(Aspect-OrientedProgramming,面向切面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。
OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的复用。
所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点、横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物、安全性、异常处理。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
AOP核心概念
- 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
- 切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象。切面是一个类, 由通知和切点组成, 在AOP代码的实现中为主要部分。而一个通知则包括; 切点、连接点, 所以说, 一个切面就直接包括 AOP中所有东西了。
//切面类 @Aspect @Component public class Point { @Before("execution(* io.renren.test.service.Buy.Play(..))") public void Before(ProceedingJoinPoint point) { System.out.println("男孩女都有自己喜欢的事情"); } }
- 连接点(joinpoint):被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点可以是:字段的设置和获取、构造方法调用、方法的调用、方法的执行、异常的处理执行、类的初始化。它自身还可以嵌套其它 joint point
// 通过注解来把增强方法和原有方法链接到一起,实现了增强的功能。 @Around("execution(* io.renren.test.service.Buy.Play(..))")
- 切入点(pointcut):对连接点进行拦截的定义。表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方
@Around("execution(* io.renren.test.service.Buy.Play(..))") public void Around(ProceedingJoinPoint point) throws Throwable { System.out.println("环绕前....."); System.out.println(point); } // ProceedingJoinPoint point 过程切入点,增强方法在此实现对被增强方法的功能增强。 // 通过切入点我们可以得到很多东西,举例如下 //得到参数 Object[] args = point.getArgs(); System.out.println("args=> " + args.toString()); //得到切入点的类型 String kind = point.getKind(); System.out.println("kind=> " + kind); //切入点 得到被增强方法的方法签名 MethodSignature methodSignature = (MethodSignature) point.getSignature(); //方法签名 到被增强方法的方法 Method method = methodSignature.getMethod(); System.out.println("method = > " + method); //方法 名 到被增强方法的方法 上面的注解 SysLog sysLog = method.getAnnotation(SysLog.class); TestAnnotation test = method.getAnnotation(TestAnnotation.class); System.out.println(sysLog.value()); System.out.println(test.value()); Signature signature = point.getSignature(); String name = signature.getName(); Class declaringType = signature.getDeclaringType(); int modifiers = signature.getModifiers(); String declaringTypeName = signature.getDeclaringTypeName(); System.out.println("name= > " + name + " declaringType => " + declaringType + " modifiers => " + modifiers + " declaringTypeName => " + modifiers); System.out.println("signature=> " + signature); //得到目标 Object target = point.getTarget(); System.out.println("target=> " + target); System.out.println("环绕后.....");
- 通知(advice):AOP 的主要作用就是在不侵入原有程序的基础上实现对原有功能的增强, 而增强的方式就是添加通知,就是额外增强一个方法。通知分为前置、后置、异常、最终、环绕通知五类
- 目标对象(target):代理的目标对象
- 织入(weave):将切面应用到目标对象并创建代理对象的过程
- 引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
Advice 的类型
before advice
, 在 join point 前被执行的 advice。 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)after(final) advice
, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.after returning advice
, 在一个 join point 正常返回后执行的 adviceafter throwing advice
, 当一个 join point 抛出异常后执行的 advicearound advice
, 在 join point 前和 joint point 退出后都执行的 advice. 【最常用】introduction
,introduction可以为原有的对象增加新的属性和方法。
Spring对AOP的支持
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB
AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:
1、定义普通业务组件
2、定义切入点,一个切入点可能横切多个业务组件
3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象 的方法。
AOP基本使用
目录结构
主要: 接口、实现类、切面、配置类、测试类
//接口和实现类
public interface Buy {
public void Play(String info);
}
@Component
public class BoyBuy implements Buy {
@Override
public void Play(String info) {
System.out.println(info);
}
}
//配置类
@ComponentScan(basePackages = {"io.renren.test"})
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan(basePackageClasses = {io.renren.test.service.Buy.class})
public class config {
}
//测试
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config.class);
BoyBuy buy = context.getBean(BoyBuy.class);
GirlBuy girl = context.getBean(GirlBuy.class);
girl.Play("around info ");
}
}
AOP
//切面类
@Aspect
@Component
public class Point {
@Before("execution(* io.renren.test.service.Buy.Play(..))")
public void Before() {
System.out.println("男孩女都有自己喜欢的事情");
}
}
开启环绕通知, 以及打印切入点的信息
同 AOP核心概念 切入点 代码
定义了一个切点, 并且把切点配置到通知上面
//定义一个切点
@Pointcut("execution(* io.renren.test.service.Buy.Play(..))")
public void AspectJPoint() {}
@Before("AspectJPoint()")
public void point1() {
System.out.println("前置通知");
}
项目中有没有使用到AOP?
在后台系统中,就是使用aop来记录了系统的操作日志。主要思路是,使用aop中的环绕通知+切点表达式,这个表达式就是要找到要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,比如类信息、方法信息、注解、请求方式等,获取到这些参数以后,保存到数据库
spring 主要模块
spring core:框架的最基础部分,提供 ioc 和依赖注入特性。 spring context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。 spring dao:Data Access Object 提供了JDBC的抽象层。 spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。 spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。 spring Web mvc:spring 中的mvc封装包提供了Web应用的 Model-View-Controller(MVC)的实现。
常用注解
第一类是:声明bean,有@Component、@Service、@Repository、@Controller
第二类是:依赖注入相关的,有@Autowired、@Qualifier、@Resourse
第三类是:设置作用域 @Scope
第四类是:spring配置相关的,比如@Configuration,@ComponentScan 和 @Bean
第五类是:跟aop相关做增强的注解 @Aspect,@Before,@After,@Around,@Pointcut
bean的生命周期
这个步骤还是挺多的,我之前看过一些源码,它大概流程是这样的
首先会通过一个非常重要的类BeanDefinition获取bean的定义信息,这里面封装了bean的所有信息,比如,类的全路径,是否是延迟加载,是否是单例等等
在创建bean的时候,第一步是调用构造函数实例化bean
第二步是bean的依赖注入,比如一些set方法注入,像平时开发用的@Autowire都是这一步完成
第三步是处理Aware接口,如果某一个bean实现了Aware接口就会重写方法执行
第四步是bean的后置处理器BeanPostProcessor,这个是前置处理器
第五步是初始化方法,比如实现了接口InitializingBean或者自定义了方法init-method标签或@PostContruct
第六步是执行了bean的后置处理器BeanPostProcessor,主要是对bean进行增强,有可能在这里产生代理对象
最后一步是销毁bean
spring中的bean线程安全吗?
不是线程安全的
当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单列状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。
Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。
比如:我们通常在项目中使用的Spring bean都是不可可变的状态(比如Service类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。
如果你的bean有多种状态的话(比如 View Model对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用由 singleton 变更为 prototype。
spring支持几种bean的作用域?
spring 支持 5 种作用域,如下:
singleton:spring ioc 容器中只存在一个 bean 实例,bean 以单例模式存在,是系统默认值; prototype:每次从容器调用 bean 时都会创建一个新的示例,既每次 getBean()相当于执行 new Bean()操作; Web 环境下的作用域: request:每次 http 请求都会创建一个 bean; session:同一个 http session 共享一个 bean 实例; global-session:用于 portlet 容器,因为每个 portlet 有单独的 session,globalsession 提供一个全局性的 http session。
注意:慎重使用 prototype 作用域,因为频繁创建和销毁 bean 会带来很大的性能开销。
spring自动装配bean的方式
在应用程序对象之间创建和管理关联的过程叫做装配,这构成了DI的核心。
自动装配就是指 Spring 容器在不使用 和 标签的情况下,可以自动装配(autowire)相互协作的 Bean 之间的关联关系,将一个 Bean 注入其他 Bean 的 Property 中。spring中实现自动装配的方式有两种,一种是通过xml文件、另一种是通过注解。
1、通过xml文件
只需要在xml配置文件中的bean标签中加入一个属性autowire即可
<bean id="people" class="com.kuang.pojo.Peopel" autowire="byName">
<property name="name" value="张三"/>
</bean>
<bean id="person" class="com.qdu.bean.Girl" autowire="constructor">
<property name="name" value="小明" />
</bean>
autowire 属性有五个值,具体说明如下:
- no
默认值,表示没有自动装配,Bean 依赖必须通过 ref 元素定义
- byName
根据 Property 的 name 自动装配,如果一个 Bean 的 name 和另一个 Bean 中的 Property 的 name 相同,则自动装配这个 Bean 到 Property 中。(表示按属性名称自动装配,XML 文件中 Bean 的 id 必须与类中的属性名称相同)
当一个bean带有autowire byName的属性时:
将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
去spring容器中寻找是否有此字符串名称id的对象。
如果有,就取出注入;如果没有,就报空指针异常
- byType
根据 Property 的数据类型(Type)自动装配,如果一个 Bean 的数据类型兼容另一个 Bean 中 Property 的数据类型,则自动装配。(XML 文件中 Bean 的 id 与类中的属性名称可以不同,但必须只有一个类型的 Bean。自动装配跟 id 无关,在配置bean 时不写 id 都不会报错。)
但是 byType 的自动装配存在一个很严重的问题,因为不是通过唯一的 id 来匹配,而是通过类型来匹配,所以容器中不能存在多个相同类型的 bean,否则会抛出NoUniqueBeanDefinitionException异常。如下图:
- constructor
类似于 byType,根据构造方法参数的数据类型,进行自动装配。(类中构造函数的参数必须在配置文件中有相同的类型)
constructor表示使用对应的构造函数实现自动装配,所以需要生成对应的构造函数
- default
装配方式和全局自动装配default-autowire的值一致。
如果autowire和default-autowrie都为default,那么就是不自动装配
- autodetect(3.0版本不支持)
如果 Bean 中有默认的构造方法,则用 constructor 模式,否则用 byType 模式。
2、通过注解
Spring默认不使用注解装配 Bean,因此需要在配置文件中添加context:annotation-config启用注解
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
或者在applicationContext.xml 配置文件中配置:
<!--开启注解扫描-->
<context:component-scan base-package="需要使用注解的包" />
(1)常用注解注入值:
@Component
用来描述Spring中的Bean,仅仅表示一个组件(Bean),并且可以作用在任何层次,使用时只需要将该注解标注在相应类上即可。
默认id标识名:类名首字母小写
自定义id标识名:@Service("studentService")
@Controller
通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,功能与 @Component 相同。
@Service
通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,功能与 @Component 相同。
@Repository
用于将数据访问层(DAO层)的类标识为Spring中的Bean,功能与@Component相同
@Autowired
Spring提供的注解。可以应用到 Bean 的属性变量、属性的 setter 方法、非 setter 方法及构造函数等,配合对应的注解处理器完成 Bean 的自动配置工作。
默认按照 byType进行装配。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功,如果ioc容器中没有任何bean类型和要注入的变量类型匹配则报错。
如果想根据名称装配,需要配合@Qualifier注解一起用。@Qualifier(value = "名称") 根据指定的名称作为 bean 的id进行匹配注入。
注意:在默认情况下使用 @Autowired 注释进行自动注入时,Spring 容器中匹配的候选 Bean 数目必须有且仅有一个。当找不到一个匹配的 Bean 时,Spring 容器将抛出 BeanCreationException 异常,并指出必须至少拥有一个匹配的 Bean。当不能确定 Spring 容器中一定拥有某个类的 Bean 时,可以在需要自动注入该类 Bean 的地方可以使用@Autowired(required= false)。这等于告诉 Spring:在找不到匹配 Bean 时也不报错。
@Qualifier
与 @Autowired 注解配合使用,会将默认的按byType装配修改为按byName装配,Bean 的实例名称由 @Qualifier 注解的参数指定。它在给类成员注入时不能单独使用必须要和@Autowired,但是在给方法参数注入时可以单独使用。
@Resource【不常用】
JDK提供的注解。功能与@Autowired相同,区别在于@Resource可以通过Bean实例名称进行装配,也就是@Resource中的两个重要属性name和type。
默认根据byName进行注入,如果没有name一样的,再根据byType进行注入,再找不到就报错。
@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
@Value
用于注入基本类型和String类型的值用于指定数据的值,它可以使用spring中SpEL;
@ConfigurationProperties
用于注入一些值。请注意以下几点:
① 前缀定义了哪些外部属性将绑定到类的字段上
② 根据 Spring Boot 宽松的绑定规则,类的属性名称必须与外部属性的名称匹配
③ 我们可以简单地用一个值初始化一个字段来定义一个默认值
④ 类本身可以是包私有的
⑤ 类的字段必须有公共 setter 方法
@Configuration
标注在类上,配置spring容器(应用上下文)。指定一个类为配置类,相当于把该类作为spring的xml配置文件,在使用该注解的类中,使用@Bean注解标注的方法,返回的类型都会直接注册为bean。其底层实现使用了@Component 。
@ComponentScan
开启注解扫描,替代<context:component-scan /> 标签实现注解扫描
//标注当前类是配置类,替代applicationContext.xml
@Configuration
//使用@ComponentScan注解,可以配置扫描包,替代<context:component-scan />标签
@ComponentScan(basePackages = {"com.bdqn.dao.impl","com.bdqn.service.impl"})
public class MyConfiguration {}
@PropertySource("classpath:配置文件地址")
加载读取配置文件,替代 <context:property-placeholder />标签
@Bean
用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。
@Value和@ConfigurationProperties区别
二者区别 | @ConfigurationProperties | @Value |
功能 | 批量注入配置文件中的属性 | 一个个指定 |
松散绑定(松散语法) | 支持 | 不支持 |
SpEL | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
(2)改变作用域的常用注解:
@Lazy(true)
表明一个bean 是否延迟加载,可以作用在方法上,表示这个方法被延迟加载;可以作用在@Component (或者由@Component 作为原注解) 注释的类上,表明这个类中所有的bean 都被延迟加载。如果没有@Lazy注释,或者@Lazy 被设置为false,那么该bean 就会急切渴望被加载;除了上面两种作用域,@Lazy 还可以作用在@Autowired和@Inject注释的属性上,在这种情况下,它将为该字段创建一个惰性代理,作为使用ObjectFactory或Provider的默认方法
@Scope
用来给Bean改变作用域,使用时直接在Bean上加@Scope(value = "xxx"),默认值为singleton。
(3)和生命周期相关常用注解:
@PostConstruct
该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet时运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
@PreDestory
@PreDestory修饰的方法会在服务器卸载Servlet时运行,并且之后被服务器调用一次,类似于Servlet中的destory()方法,虽然PreDestory字面意思是在destory之前运行,但是被@PreDestory修饰的方法会在destory方法运行之后运行,在Servlet被彻底卸载之前,PreDestory里的Destory指的是Servlet的销毁,而不是destory()方法。
XML、注解+XML、完全注解配置类 方式管理 Bean的区别
XML IoC方式问题总结:
1. 注入的属性必须添加setter方法、代码结构乱!
2. 配置文件和Java代码分离、编写不是很方便!
3. XML配置文件解析效率低
注解+XML IoC方式问题总结
1. 自定义类可以使用注解方式,但是第三方依赖的类依然使用XML方式!
2. XML格式解析效率低!
Spring 完全注解配置(Fully Annotation-based Configuration)
指通过 Java配置类 代码来配置 Spring 应用程序,
使用注解来替代原本在 XML 配置文件中的配置。
相对于 XML 配置,完全注解配置具有更强的类型安全性和更好的可读性。
自动装配的优缺点
优点:自动装配只需要较少的代码就可以实现依赖注入。
缺点:不能自动装配简单数据类型,比如 int、boolean、String 等。相比较显示装配,自动装配不受程序员控制。
Spring中的循环引用
循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A
spring框架通过三级缓存已经解决了大部分的循环依赖。
缓存 | 源码名称 | 作用 |
---|---|---|
一级缓存 | singletonObjects | 单例池,缓存已经经历了完整的生命周期,已经初始化完成的 bean 对象。一级缓存解决不了循环依赖问题。 |
二级缓存 | earlySingletonObjects | 缓存早期的 bean 对象。(生命周期还没有走完) |
三级缓存 | singletonFactories | 缓存的是 ObjectFactory ,表示对象工厂,用来创建某个对象的。 |
解决循环依赖的步骤:
- 实例化 A,得到原始对象 A,并且同时生成一个原始对象 A 对应的
ObjectFactory
对象。 - 将
ObjectFactory
对象存储到三级缓存中。 - 需要注入 B,发现 B 对象在一级缓存和二级缓存都不存在,并且三级缓存中也不存在 B 对象所对应的
ObjectFactory
对象。 - 实例化 B,得到原始对象 B,并且同时生成一个原始对象 B 对应的
ObjectFactory
对象,然后将该ObjectFactory
对象也存储到三级缓存中。 - 需要注入 A,发现 A 对象在一级缓存和二级缓存都不存在,但是三级缓存中存在 A 对象所对应的
ObjectFactory
对象。 - 通过 A 对象所对应的
ObjectFactory
对象创建 A 对象的代理对象。 - 将 A 对象的代理对象存储到二级缓存中。
- 将 A 对象的代理对象注入给 B,B 对象执行后面的生命周期阶段,最终 B 对象创建成功。
- 将 B 对象存储到一级缓存中。
- 将 B 对象注入给 A,A 对象执行后面的生命周期阶段,最终 A 对象创建成功,将二级缓存的 A 的代理对象存储到一级缓存中。
注意:
- 后面的生命周期阶段会按照本身的逻辑进行 AOP,在进行 AOP 之前会判断是否已经进行了 AOP,如果已经进行了 AOP 就不会进行 AOP 操作了。
singletonFactories
: 缓存的是一个ObjectFactory
,主要用来去生成原始对象进行了 AOP 之后得到的代理对象, 在每个 Bean 的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本 bean,那么这个工厂无用,本 bean 按照自己的生命周期执行,执行完后直接把本 bean 放入singletonObjects
中即可,如果出现了循环依赖了本 bean,则另外那个 bean 执行ObjectFactory
提交得到一个 AOP 之后的代理对象(如果没有 AOP,则直接得到一个原始对象)。
只有一级缓存和三级缓存是否可行?
不行,每次从三级缓存中拿到 ObjectFactory
对象,执行 getObject()
方法又会产生新的代理对象,因为 A 是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了 objectFactory.getObject()
产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍 objectFactory.getObject()
方法再产生一个新的代理对象,保证始终只有一个代理对象。
总结:所以如果没有 AOP 的话确实可以两级缓存就可以解决循环依赖的问题,如果加上 AOP,两级缓存时无法解决的,不可能每次执行 objectFactory.getObject()
方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保护产生的代理对象。
构造方法出现了循环依赖怎么解决?
Spring 中大部分的循环依赖已经帮我们解决掉了,但是有一些循环依赖还需要我们程序员自己进行解决;如果构造方法出现了循环依赖,由于bean的生命周期中构造方法是第一个执行的,spring框架并不能解决构造方法的的依赖注入,可以在构造参数前面使用@Lazy懒加载,就不会真正的注入真实对象(此时注入的是一个代理对象),该注入对象会被延迟加载,什么时候需要对象再进行bean对象的创建。
事务
在⼀个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。这叫做事务。
事务特性(ACID)
- 原⼦性(Atomicity,或称不可分割性)
⼀个事务(transaction)中的所有操作,要么全部完成,要么全部失败,不会结束在中间某个环节。事务在执⾏过程中发⽣错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执⾏过⼀样。
- ⼀致性(Durability)
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写⼊的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以⾃发性地完成预定的⼯作。
- 持久性(Consistency)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
- 隔离性(Isolation,⼜称独⽴性)
数据库允许多个并发事务同时对其数据进⾏读写,隔离性可以防⽌多个事务并发执⾏时由于交叉执⾏⽽导致数据的不⼀致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
事务属性
事务种的重点属性:
- 事务传播行为
- 事务隔离级别
- 事务超时
- 只读事务
- 设置出现哪些异常回滚事务
- 设置出现哪些异常不回滚事务
隔离级别
数据库必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事 务与其他事务隔离的程度称为隔离级别。
SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越高,但并发性能越弱。
MySQL 事务隔离级别:
-
READ_UNCOMMITTED
(读未提交):最低的隔离级别,允许另外一个事务可以看到这个事务未提交的数据。可能会导致脏读、不可重复读、幻读。 -
READ_COMMITTED
(读已提交):保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。可以避免脏读,但是可能会导致不可重复读、幻读(⽐如此级别的事务正在执⾏时,另⼀个事务成功的插⼊了某条数据,但因为它每次查询的结果都是⼀样的,所以会导致查询不到这条数据,⾃⼰重复插⼊时⼜失败 ,因为唯⼀约束的原因) -
REPEATABLE_READ
(可重复读):是 MySQL 的默认事务隔离级别。它能确保同⼀事务对同一字段的多次读取结果⼀致,除非数据是由当前事务自己所修改,可以避免脏读、不可重复读,但是幻读仍有可能。 -
SERIALIZABLE
(串行化):最高的隔离级别,所有的事务都是按顺序逐个执行,可以避免所有的并发问题,但性能最差,真正使⽤的场景并不多
数据库三⼤读问题:
脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。
不可重复读 :⼀个事务两次查询的结果不同,因为在两次查询中间,另⼀个事务把数据修改了。
幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。
各种数据库产品对事务隔离级别的支持程度:
Spring支持的事务隔离级别:
-
Isolation.DEFAULT
:默认值,以连接的数据库的事务隔离级别为主,其他四个隔离级别和数据库的隔离级别一致 -
Isolation.READ_UNCOMMITTED
:读未提交,最低的隔离级别,可以读取到未提交的事务,存在脏读、不可重复读、幻读。 -
Isolation.READ_COMMITTED
:读已提交,只能读取到已经提交的事务,存在不可重复读、幻读。SQL server 的默认事务隔离级别 -
Isolation.REPEATABLE_READ
:可重复读,能确保同⼀事务对同一字段的多次读取结果⼀致,除非数据是由当前事务自己所修改,存在幻读。MySQL 的默认事务隔离级别 -
Isolation.SERIALIZABLE
:串⾏化,最高的隔离级别,所有的事务都是按顺序逐个执行,可以避免所有的并发问题,但性能最差,真正使⽤的场景并不多
Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置:
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
事务的四个处理过程
第⼀步:开启事务 (start transaction)
第⼆步:执⾏核⼼业务代码
第三步:提交事务(如果核⼼业务处理过程中没有出现异常)(commit transaction)
第四步:回滚事务(如果核⼼业务处理过程中出现异常)(rollback transaction)
事务在 MySQL 的操作:
-- 开启事务
start transaction;
-- 业务执⾏
-- 提交事务
commit;
-- 回滚事务
rollback;
-- 查询MySQL事务隔离级别
select @@global.tx_isolation,@@tx_isolation;
-- 全局事务隔离级别 -- 当前连接的事务隔离级别
实现方式
Spring 事务管理分为编码式和声明式的两种方式。
- 编程式事务管理: 编程式事务管理使用 TransactionTemplate 或者直接使用底层的 PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
- 声明式事务管理: 建立在AOP之上的。其本质是对方法前后进行拦截,在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
- 声明式事务管理不需要入侵代码,更快捷而且简单,推荐使用。
声明式事务
声明式事务有两种实现方式:
1、基于 xml 配置文件的方式
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="pooledDataSource"/>
</bean>
<aop:config>
<aop:pointcut expression="execution(* cn.yuanyu.crud.service..*(..))" id="txPoint"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
2、注解方式
依赖:(⼀定要集成Log4j2⽇志框架,在⽇志信息中可以看到更加详细的信息)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个
jar包,导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>
spring配置文件:
<!--扫描组件-->
<context:component-scan base-package="com.demo.spring.tx.annotation">
</context:component-scan>
<!-- 导入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置数据源 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 装配数据源 -->
<property name="dataSource" ref="druidDataSource"/>
</bean>
<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
开启事务的注解驱动
通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就
是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
一般在service层添加 @Transaction 注解,⽆需⼿动开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了没有处理的异常会⾃动回滚事务。
@RequestMapping("/save")
@Transactional
public Object save(User user) {
int result = userService.save(user);
return result;
}
@Transactional 可以⽤来修饰方法或类
- 修饰方法时:需要注意只能应⽤到 public 方法上,否则不生效。推荐此种用法
- 修饰类时:表明该注解对该类中所有的 public 方法都⽣效
@Transactional 参数
@Transactional 工作原理
此注解是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。 @Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途 遇到的异常,则回滚事务。
@Transactional 在异常被捕获不会进⾏事务⾃动回滚
解决方案1:将异常重新抛出
@RequestMapping("/save")
@Transactional(isolation = Isolation.SERIALIZABLE) public Object save(User user) {
// 插⼊数据库
int result = userService.save(user);
try {
// 执⾏了异常代码(0不能做除数)
int i = 10 / 0;
} catch (Exception e) {
System.out.println(e.getMessage());
// 将异常重新抛出去
throw e;
}
return result;
}
解决方案2:⼿动回滚事务
在⽅法中使⽤TransactionAspectSupport.currentTransactionStatus() 可 以得到当前的事务,然后设置回滚⽅法 setRollbackOnly 就可以实现回滚了
@RequestMapping("/save")
@Transactional(isolation = Isolation.SERIALIZABLE)
public Object save(User user) {
// 插⼊数据库
int result = userService.save(user);
try {
// 执⾏了异常代码(0不能做除数)
int i = 10 / 0;
} catch (Exception e) {
System.out.println(e.getMessage());
// ⼿动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnl y();
}
return result;
}
编程式事务
编程式事务管理使用 TransactionTemplate 或者直接使用底层的 PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
步骤:
- 开启事务(获取事务)
- 提交事务
- 回滚事务
@RestController
public class UserController {
@Resource
private UserService userService;
// JDBC 事务管理器
@Resource
private DataSourceTransactionManager dataSourceTransactionManager;
// 定义事务属性
@Resource
private TransactionDefinition transactionDefinition;
@RequestMapping("/sava")
public Object save(User user) {
// 开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
// 插⼊数据库
int result = userService.save(user);
// 提交事务
dataSourceTransactionManager.commit(transactionStatus);
// // 回滚事务
// dataSourceTransactionManager.rollback(transactionStatus);
return result;
}
}
编程式实现方式缺点:
细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码没有复用。
事务实现原理
使用 @EnableTransactionManagement 来开启spring事务功能(SpringBoot中不用显示设置)
使用 @Transactional 注解来标记某个方法使用事务功能,作用在类上则所有方法都使用事务
1、@EnableTransactionManagement做了什么?
开启Spring事务本质上就是增加了一个Advisor,但我们使用@EnableTransactionManagement注解来开启Spring事务是,该注解代理的功能就是向Spring容器中添加了两个Bean:
-
AutoProxyRegistrar
-
ProxyTransactionManagementConfiguration
AutoProxyRegistrar主要的作用是向Spring容器中注册了一个InfrastructureAdvisorAutoProxyCreator的Bean。而InfrastructureAdvisorAutoProxyCreator继承了AbstractAdvisorAutoProxyCreator,所以这个类的主要作用就是开启自动代理的作用,也就是一个BeanPostProcessor,会在初始化后步骤中去寻找Advisor类型的Bean,并判断当前某个Bean是否有匹配的Advisor,是否需要利用动态代理产生一个代理对象。
ProxyTransactionManagementConfiguration是一个配置类,它又定义了另外三个bean:
-
BeanFactoryTransactionAttributeSourceAdvisor:一个Advisor
-
AnnotationTransactionAttributeSource:
相当于BeanFactoryTransactionAttributeSourceAdvisor中的Pointcut
-
TransactionInterceptor:
相当于BeanFactoryTransactionAttributeSourceAdvisor中的Advice
AnnotationTransactionAttributeSource就是用来判断某个类上是否存在@Transactional注解,或者判断某个方法上是否存在@Transactional注解的。
TransactionInterceptor就是代理逻辑,当某个类中存在@Transactional注解时,到时就产生一个代理对象作为Bean,代理对象在执行某个方法时,最终就会进入到TransactionInterceptor的invoke()方法。
2、spring事务的基本执行原理
一个Bean在执行Bean的创建生命周期时,会经过InfrastructureAdvisorAutoProxyCreator的初始化后的方法,会判断当前Bean对象是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,匹配逻辑为判断该Bean的类上是否存在@Transactional注解,或者类中的某个方法上是否存在@Transactional注解,如果存在则表示该Bean需要进行动态代理产生一个代理对象作为Bean对象。
该代理对象在执行某个方法时,会再次判断当前执行的方法是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,如果匹配则执行该Advisor中的TransactionInterceptor的invoke()方法,执行基本流程为(单一事务):
-
1、利用所配置的PlatformTransactionManager事务管理器新建一个数据库连接conn
这个连接保存在一个ThreadLocal<Map<DateSource,conn>>,key是数据源,value就是连接coon
-
2、修改数据库连接的autocommit为false,并设置@Transactional其他属性配置
-
3、执行MethodInvocation.proceed()方法,简单理解就是执行业务方法,其中就会执行sql
-
4、如果没有抛异常,则提交
-
5、如果抛了异常,则回滚
注意:这里不能由JDBCTemplate或Mybatis自己建立连接,否则就无法用到事务了,spring处理多事务也是通过建立不同连接来实现的。
事务的传播机制
在service类中有a()⽅法和b()⽅法,a()⽅法上有事务,b()⽅法上也有事务,当a()⽅法执⾏过程中调⽤了 b()⽅法,事务是如何传递的?合并到⼀个事务⾥?还是开启⼀个新的事务?这就是事务传播⾏为。
@Transactional(propagation = Propagation.REQUIRED)
作用:事务传播机制是保证⼀个事务在多个调⽤⽅法间的可控性的(稳定性的)
Spring 事务7种传播机制:
-
Propagation.REQUIRED
:默认的事务传播级别,如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。【有就加⼊,没有就新建】【常用】 -
Propagation.SUPPORTS
:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。【有就加⼊,没有就不管了】 -
Propagation.MANDATORY
:必须运⾏在⼀个事务中,如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。【有就加⼊,没有就抛异常】 -
Propagation.REQUIRES_NEW
:创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会直接开启⾃⼰的事务,且开启的新事务和原事务相互独⽴,互不⼲扰。【不管有没有,直接开启⼀个新事务】【常用】 -
Propagation.NOT_SUPPORTED:
以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。【不⽀持事务,存在就挂起】 -
Propagation.NEVER:
以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。【不⽀持事务,存在就抛异常】 -
Propagation.NESTED:
如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏,被嵌套 的事务可以独⽴于外层事务进⾏提交或回滚;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。【有事务的话,就在这个事务⾥再嵌套⼀个完全独⽴的事务,嵌套的事务可以独⽴的提交 和回滚。没有事务就和REQUIRED⼀样。】
其中,以非事务方式运行,表示以非Spring事务运行,表示在执行这个方法时,Spring事务管理器不会去建立数据库连接,执行sql时,由Mybatis或JdbcTemplate自己来建立数据库连接来执行sql
事务超时
事务执行过程中,可能因为某些问题导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常 程序可以执行。概括来说就是一句话:超时回滚,释放资源。
timeout 默认值-1,表示没有时间限制
事务的超时时间指的是: 在当前事务当中,最后⼀条DML语句执⾏之前的时间。如果最后⼀条DML语句后面很有很多业务逻辑, 这些业务代码执⾏的时间不被计⼊超时时间。如果想让整个⽅法的所有代码都计⼊超时时间的话,可以在⽅法最后一行添加一行无关紧要的 DML语句
//设置事务超时时间为3秒。 超过3秒如果该事务中所有的DML语句没有执⾏完毕,最终结果会选择回滚
@Transactional(timeout = 3)
public void buyBook(Integer bookId, Integer userId) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId, price);
//System.out.println(1/0);
}
只读事务
@Transactional(readOnly = true)
public User findUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
异常(不)回滚事务
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。 可以通过@Transactional中相关属性设置回滚策略:
-
rollbackFor
:该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。//指定单一异常类: @Transactional(rollbackFor=RuntimeException.class) //指定多个异常类: @Transactional(rollbackFor={RuntimeException.class, Exception.class})
-
noRollbackFor
:该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。//指定单一异常类: @Transactional(noRollbackFor=RuntimeException.class) //指定多个异常类: @Transactional(noRollbackFor={RuntimeException.class, Exception.class})
-
rollbackForClassName
:该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。//指定单一异常类名称: @Transactional(rollbackForClassName=”RuntimeException”) //指定多个异常类名称: @Transactional(rollbackForClassName={“RuntimeException”,”Exception”})
-
noRollbackForClassName
:该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。//指定单一异常类名称: @Transactional(noRollbackForClassName=”RuntimeException”) //指定多个异常类名称: @Transactional(noRollbackForClassName={“RuntimeException”, ”Exception”})
事务失效场景
1、@Transactional作用在非public修饰的方法上
如果Transactional注解应用在非 public 修饰的方法上,Transactional将会失效。
因为在Spring AOP 代理时,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的intercept方法 或 JdkDynamicAopProxy的invoke方法会间接调用AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute方法,获取Transactional 注解的事务配置信息。
protected TransactionAttribute computeTransactionAttribute(Method method,Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
}
此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
2、@Transactional作用在使用final或者static修饰的方法
Spring事务底层使用了AOP,也就是通过JDK动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,从而无法添加事务功能。这种情况事务就会在Spring中失效。
根据这个原理可知,如果某个方法是static的,同样无法通过动态代理将方法声明为事务方法。
3、@Transactional 注解属性propagation设置错误
传播属性设置为非事务方式。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
4、同一个类中方法调用导致@Transactional失效
这里有一个AOP的知识点:如果不用代理对象调,在类中调的话,则是调用的普通对象的b()方法,这是不会走代理逻辑的,所以也就用不到事务。
5、多线程情况
情况一:父线程抛异常,子线程OK
父线程抛出线程,事务回滚,因为子线程是独立存在,和父线程不在同一个事务中,所以子线程的修改并不会被回滚。
情况二:父线程OK,子线程抛异常
由于子线程的异常不会被外部的线程捕获,所以父线程不抛异常,事务回滚没有生效。
6、处理异常
如果方法上异常捕获处理,自己处理了异常,没有抛出,就会导致事务失效,所以一般处理了异常以后,别忘了抛出去就行了
防止事务失效
-
在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。
-
@Transactional 注解应该只被应用在 public 修饰的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 该注解,它也不会报错(IDEA会有提示), 但事务并没有生效。
-
被外部调用的公共方法A有两个进行了数据操作的子方法B和子方法C的事务注解说明:
-
被外部调用的公共方法A 声明 事务@Transactional,无论子方法B和C是不是本类的方法,无论子方法B和C是否声明事务,事务均由公共方法A控制
-
被外部调用的公共方法A 未声明 事务@Transactional,子方法B和C若是其他类的方法且各自声明事务:事务由子方法B和C各自控制
-
被外部调用的公共方法A 未声明 事务@Transactional,子方法B和C若是本类的方法,则即使子方法B和C各自声明事务,事务也不会生效,并且会报错(没有可用的transactional)
-
被外部调用的公共方法A 声明 事务@Transactional,子方法运行异常,但运行异常被子方法自己
try-catch
处理了,则事务回滚是不会生效的!如果想要事务回滚生效,需要将子方法的事务控制交给调用的方法来处理:
- 方案1:子方法中不用
try-catch
处理运行异常 - 方案2:子方法的catch里面将运行异常抛出【throw new RuntimeException();】
- 方案1:子方法中不用
-
-
默认情况下,Spring会对unchecked异常进行事务回滚,也就是默认对 RuntimeException() 异常或是其子类进行事务回滚。
如果是checked异常则不回滚,例如空指针异常、算数异常等会被回滚;文件读写、网络问题Spring就没法回滚。
若想对所有异常(包括自定义异常)都起作用,注解上面需配置异常类型:@Transactional(rollbackFor = Exception.class
-
数据库要支持事务,如果是mysql,要使用innodb引擎,myisam不支持事务
-
事务@Transactional由spring控制时,它会在抛出异常的时候进行回滚。如果自己使用try-catch捕获处理了,是不生效的。如果想事务生效可以进行手动回滚或者在catch里面将异常抛出【throw new RuntimeException();】
-
方案一:手动抛出运行时异常(缺陷是不能在catch代码块自定义返回值)
try{ .... }catch(Exception e){ logger.error("",e); throw new RuntimeException; }
-
方案二:手动进行回滚【 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 】
try{ ... }catch(Exception e){ log.error("fail",e); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return false; }
-
-
@Transactional可以放在Controller下面直接起作用,看到网上好多同学说要放到@Component下面或者@Service下面,经过试验,可以不用放在这两个下面也起作用。
-
@Transactional引入包问题,它有两个包:
import javax.transaction.Transactional; // 和 import org.springframework.transaction.annotation.Transactional; // 推荐
这两个都可以用,对比了一下他们两个的方法和属性,发现后面的比前面的强大。建议使用后面的。
事务提交方式
默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。
对于正常的事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式。不过,这个我们不用担心,spring 会将底层连接的【自动提交特性】设置为 false 。也就是在使用 spring 进行事物管理的时候,spring 会将【是否自动提交】设置为false,等价于JDBC中的 connection.setAutoCommit(false); ,在执行完之后在进行提交 connection.commit(); 。
事务回滚规则
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。
二、SpringMVC
常见注解
Spring MVC 框架中的常用注解主要包括在控制器层(Controller)、服务层(Service)、数据访问层(Repository)、实体类(Entity)、请求参数(Request Parameters)等方面。
1、在控制器层(Controller)使用的注解:
@Controller:标识一个类为 Spring MVC 控制器。
@RequestMapping:映射 HTTP 请求的 URL 到一个具体的处理方法。
@RequestParam: 用于提取请求中的参数值。
// 客户端发送请求 /example/greet?name=John
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/greet")
public String greet(@RequestParam("name") String name) {
return "Hello, " + name + "!";
}
}
@PathVariable:从请求路径下中获取请求参数(/user/{id}),传递给方法的形式参数
// 客户端发送请求 /example/user/123
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/user/{id}")
public String getUserById(@PathVariable("id") Long userId) {
// Retrieve user with the specified ID
return "userDetails";
}
}
2、CURD
@GetMapping:处理 HTTP GET 请求。(查询)
@Controller
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public String getUser(@PathVariable Long id) {
// ...
}
}
@PostMapping:处理 HTTP POST 请求。 (新增)
@Controller
@RequestMapping("/users")
public class UserController {
@PostMapping
public String createUser(@ModelAttribute User user) {
// ...
}
}
@PutMapping:处理 HTTP PUT 请求。(更新)
@Controller
@RequestMapping("/users")
public class UserController {
@PutMapping("/{id}")
public String updateUser(@PathVariable Long id, @ModelAttribute User user) {
// ...
}
}
@DeleteMapping:处理 HTTP DELETE 请求。(删除)
@Controller
@RequestMapping("/users")
public class UserController {
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id) {
// ...
}
}
@PatchMapping:用于映射PATCH请求到控制器方法。@PatchMapping是一个用于映射HTTP PATCH请求到控制器方法的注解,在SpringMVC中也可以使用。它可以用于方法级别,用于指定处理PATCH请求的方法。
@Controller
@RequestMapping("/users")
public class UserController {
@PatchMapping("/{id}")
public String updateUser(@PathVariable Long id, @RequestBody User user) {
// ...
}
}
3、在服务层(Service)使用的注解
@Service 是 Spring Framework 中的一个注解,用于标识一个类为服务层(Service Layer)的组件。服务层通常包含应用程序的业务逻辑,负责处理业务规则、调用数据访问层(Repository 或 DAO)执行数据库操作,并协调应用程序的不同部分
- 组件扫描: @Service 是 Spring 的组件扫描机制的一部分,标识带有该注解的类为一个服务层组件。在应用程序启动时,Spring 会扫描包路径下的所有组件,并注册为 Spring 容器中的 Bean。
- 依赖注入: 通过将 @Service 注解添加到类上,Spring IoC 容器会自动将该类的实例注入到其他需要依赖的组件中,例如控制器(Controller)或其他服务层组件。
- 事务管理: 在服务层执行的方法通常涉及数据库操作,@Service 注解通常与 @Transactional 注解一起使用,以启用事务管理。这确保了在业务方法中的一系列操作要么全部成功,要么全部失败(回滚)。
4、在数据访问层(Repository)使用的注解
@Repository:标识一个类为数据访问层的组件,通常与 Spring 的数据访问异常转换一起使用。
5、在实体类(Entity)使用的注解
@Entity 注解是 Java Persistence API (JPA) 的一部分,用于标识一个类为 JPA 实体类。JPA 是一种规范,用于描述如何通过 Java 对象与关系型数据库进行映射。@Entity 注解告诉 JPA,被注解的类将映射到数据库中的一个表。
- 数据库映射: @Entity 注解告诉 JPA 这个类与数据库中的表存在映射关系。类中的字段(成员变量)通常与表中的列相对应。
- 主键标识: 实体类通常需要一个主键,用于唯一标识每个实体对象。通过 @Entity 注解,JPA 可以识别实体类中的主键。
- 实体类识别: 当应用程序使用 JPA 进行持久化操作时,JPA 需要知道哪些类是实体类。@Entity 注解是 JPA 识别实体类的标志。
- 持久性操作: 通过实体类,可以执行 CRUD(Create, Read, Update, Delete)操作。JPA 提供了 EntityManager 接口,可以用于执行这些操作。
- 关系映射: 实体类之间的关系可以通过 JPA 进行映射,包括一对一、一对多、多对一、多对多等关系。
6、请求参数相关注解
@RequestBody:注解实现接收http请求的json数据,将json转换为java对象;
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/processJson")
public String processJson(@RequestBody MyJsonModel jsonModel) {
// Process JSON data
return "result";
}
}
@ResponseBody:注解实现将controller方法返回对象转化为json对象响应给客户端。表示方法的返回值直接作为响应体,而不是视图名称。
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/getJson")
@ResponseBody
public MyJsonModel getJson() {
// Return JSON data directly
}
}
7、其它
@Autowired:用于自动装配,将指定类型的 Bean 注入到属性、构造函数或方法参数中。使用此注解来消除了 set/get 方法。
@RequestHeader:获取指定的请求头数据,还有像@PostMapping、@GetMapping这些。
@ComponentScan:扫描指定包路径,寻找标有 @Component、@Service、@Repository、@Controller 注解的类,并将其注册为 Spring Bean。
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// Configuration content
}
@Configuration:声明当前类是一个配置类,通常与 @Bean 注解一起使用,用于配置 Spring 应用上下文。
@Configuration
public class AppConfig {
// Bean declarations using @Bean
}
@Bean:在配置类中使用,用于声明一个 Bean。
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService(myRepository());
}
@Bean
public MyRepository myRepository() {
return new MyRepository();
}
}
组件
Spring MVC 的核心组件是构成整个框架的基础,它采用了经典的 MVC(Model-View-Controller)设计模式,将应用程序分解为模型(Model)、视图(View)和控制器(Controller)三个核心组件,以提高应用程序的可维护性、可测试性和可扩展性。
1、DispatcherServlet(前置控制器)
DispatcherServlet 是 Spring MVC 的核心控制器,是整个请求处理流程的入口。当请求到达应用程序时,DispatcherServlet 负责接收接收所有的 HTTP请求,并根据请求的 URL 将其分派给适当的处理程序(Handler)。它充当了前端控制器(Front Controller)的角色,负责协调请求的处理,以及将处理结果发送回客户端。
在web project的web.xml中配置:
<!-- 配置前端控制器DispatcherServlet -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--springmvc.xml 是自己创建的SpringMVC全局配置文件,用contextConfigLocation作为参数名来加载
如果不配置 contextConfigLocation,那么默认加载的是/WEB-INF/servlet名称-servlet.xml,在这里也就是
springmvc-servlet.xml 参数多个值使用逗号隔开,如:a.xml,b.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--第一种配置:*.do,还可以写*.action等等,表示以.do结尾的或者以.action结尾的URL都由前端控制器DispatcherServlet来解析
第二种配置:/,所有访问的 URL 都由DispatcherServlet来解析,但是这里最好配置静态文件不由DispatcherServlet来解析,需要对静态资源单独处理
错误配置:/*,注意这里是不能这样配置的,因为如果这样写,最后转发到 jsp 页面的时候,仍然会由DispatcherServlet进行解析, 而这时候会找不到对应的Handler,从而报404!!! -->
<url-pattern>/</url-pattern>
</servlet-mapping>
2、Controller(控制器)
Controller 是 Spring MVC 中的核心组件之一,负责处理客户端请求,调用业务逻辑,并返回模型数据和视图。它通常包含了一些处理方法,这些方法被称为处理程序(Handler),用于处理特定类型的请求。在 Spring MVC 中,控制器可以是任何被 Spring 管理的 Bean,并且可以通过注解(如 @Controller、@RestController)或实现特定接口(如 Controller、HttpRequestHandler)来声明。
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/list", method = RequestMethod.GET)
public String list(Model model) {
// 获取用户列表并添加到模型
List<User> users = userService.listUsers();
model.addAttribute("users", users);
return "userList";
}
}
3、HandlerMapping(处理程序映射器)
HandlerMapping 用于将客户端请求映射到处理程序(Controller)。它根据请求的 URL、请求的类型或其他标准确定应该由哪个处理程序来处理请求。Spring MVC 提供了多种 HandlerMapping 的实现,包括注解驱动的 RequestMappingHandlerMapping、基于路径的 SimpleUrlHandlerMapping 等。
在 springmvc.xml 文件中配置,是请求的 URL 怎么能被 SpringMVC 识别,从而去执行我们编写好的 Handler:
//方法一:这样配置的话,那么请求的 URL,必须为 http://ip:port/hello2
<!-- 配置Handler 通过name的值为url来请求-->
<bean name="/hello2" class="com.beiyou.controller.HelloController2" />
//Handler
//我们的这个controller 就是一个Handler(执行器)
public class HelloWord2 implements Controller {
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
ModelAndView mv=new ModelAndView();
//如果xml统一配置路径,可以简写程文件名
mv.setViewName("success");
mv.addObject("msg","今天天气很冷");
return mv;
}
}
//方法二:这种配置请求的 URL可以为 http://ip:prot/项目名/hello,或者http://ip:port/项目名/hello2
<!-- 配置Handler 通过key值做为url来访问对应的bean-->
<bean id="hello2" class="com.beiyou.controller.HelloController2" />
<bean id="hello3" class="com.beiyou.controller.HelloController3" />
<!-- 简单URL配置处理器映射器 -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/hello2">hello2</prop>
<prop key="/hello3">hello3</prop>
</props>
</property>
</bean>
4、HandlerAdapter(处理程序适配器)
HandlerAdapter 负责执行实际的处理程序(Controller)并处理其输出。Spring MVC 支持多种处理程序适配器,用于适配不同类型的处理程序。例如,RequestMappingHandlerAdapter 用于调用带有 @RequestMapping 注解的方法,而 HttpRequestHandlerAdapter 用于调用实现了 HttpRequestHandler 接口的处理程序。
在 springmvc.xml 文件中配置,用来约束我们所需要编码的 Handler类:
//第一种:编写 Handler 时必须要实现 Controller,否则不能被适配器解析。
<!-- 配置处理器适配器,所有适配器都得实现 HandlerAdapter接口 处理器必须实现controller接口的handler-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
//第二种:编写 Handler 时必须要实现 HttpRequestHandler
<!-- 配置处理器适配器第二种方法,这样配置所有Handler都必须实现 HttpRequestHandler接口-->
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" />
//1、如果没有指明,则可以同时处理实现Controller接口和HttpRequestHandler接口的handler。
//2、如果指明其中一种,则要求所有的hander类都必须实现对应的接口,否则无法访问。
5、HandlerInterceptor(处理程序拦截器)
HandlerInterceptor 是一种拦截器,用于在请求处理过程中执行预处理和后处理操作。它允许开发人员在请求到达处理程序之前和处理程序生成响应之后执行自定义逻辑,如日志记录、权限检查等。HandlerInterceptor 可以被配置为应用于特定处理程序、特定 URL 模式或全局范围。
6、ViewResolver(视图解析器)
ViewResolver 负责解析视图,将逻辑视图名称解析为实际的视图对象。它根据视图名称查找对应的视图实现,并将其返回给 DispatcherServlet 以便呈现给客户端。Spring MVC 提供了多种 ViewResolver 的实现,如 InternalResourceViewResolver(用于解析 JSP 视图)、FreeMarkerViewResolver(用于解析 FreeMarker 模板)、ThymeleafViewResolver(用于解析 Thymeleaf 模板)等。
//这样配,在 Handler 中只需要返回在 pages 文件夹下的jsp 页面名称就可以了
//1.简单配置:
<!-- 配置视图解析器 进行jsp解析,默认使用jstl标签,classpath下得有jstl的包-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" />
<!--如果这样配,那么在 Handler 中返回的必须是完整路径(含完整文件名)。-->
//2.完整配置:
<!--配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 返回视图页面的前缀 -->
<property name="prefix" value="/WEB-INF/jsp/"></property>
<!-- 返回页面的后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
7、View(视图)
View 是用于呈现响应的组件,它负责将模型数据渲染为最终的输出。在 Spring MVC 中,视图通常是一个 JSP 页面、FreeMarker 模板、Thymeleaf 模板或其他类型的模板文件。当 DispatcherServlet 将处理程序的处理结果传递给视图时,视图负责将模型数据填充到模板中,并生成最终的 HTML、XML、JSON 等格式的响应。
8、ModelAndView(模型和视图)
ModelAndView 是一个持有模型数据和视图信息的容器对象。它将处理方法的执行结果(模型数据)和视图名称封装在一起,以便将其传递给 DispatcherServlet。处理方法可以通过返回 ModelAndView 对象来指定要渲染的视图以及需要传递给视图的模型数据。
public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView modelAndView = new ModelAndView("userList");
modelAndView.addObject("users", userService.listUsers());
return modelAndView;
}
9、HandlerExceptionResolver(异常处理器)
HandlerExceptionResolver 用于处理在请求处理过程中发生的异常。它允许开发人员在全局范围内定义异常处理逻辑,以便捕获并处理应用程序中抛出的异常。Spring MVC 提供了多种异常处理器的实现,如 SimpleMappingExceptionResolver(用于简单的异常映射)、DefaultHandlerExceptionResolver(用于处理一些常见的异常情况)等。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public String handleException(Exception e) {
// 处理异常并返回错误页面
return "error";
}
}
10、WebDataBinder(数据绑定器)
WebDataBinder 负责将请求参数绑定到处理方法的参数上。它根据处理方法的参数类型和名称,将请求中的参数值转换为适当的类型,并将其绑定到方法的参数上。WebDataBinder 还支持数据验证和数据转换等功能,以确保绑定的数据符合预期的格式和约束。
@RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(@ModelAttribute("user") User user) {
// 保存用户信息
userService.saveUser(user);
return "redirect:/user/list";
}
11、Validation(数据验证)
Spring MVC 提供了强大的数据验证功能,允许开发人员在处理方法的参数上应用验证注解(如 @Valid、@NotBlank、@NotNull 等),以验证请求参数的有效性。在处理方法执行之前,Spring MVC 会自动对请求参数进行验证,并将验证结果存储在 BindingResult 对象中,开发人员可以根据需要对验证结果进行处理。
12、MultipartResolver(文件上传解析器)
MultipartResolver 是用于处理文件上传的解析器,它负责将客户端上传的文件数据解析为 MultipartFile 对象,并将其绑定到处理方法的参数上。Spring MVC 提供了多种 MultipartResolver 的实现,如 CommonsMultipartResolver(基于 Apache Commons FileUpload)、StandardServletMultipartResolver(基于 Servlet 3.0 的 multipart 支持)等。
13、LocaleResolver(区域解析器)
LocaleResolver 用于解析客户端的区域设置信息,以确定应该使用哪种语言和区域的资源。它允许开发人员根据请求的语言偏好来选择适当的国际化资源,以便呈现给用户。Spring MVC 提供了多种 LocaleResolver 的实现,如 AcceptHeaderLocaleResolver(根据请求的 Accept-Language 头部确定区域设置)、SessionLocaleResolver(将区域设置存储在会话中)等。
14、ThemeResolver(主题解析器)
ThemeResolver 用于解析客户端的主题信息,以确定应该使用哪种主题样式来呈现视图。它允许开发人员根据用户的偏好选择适当的视觉样式,以提供更好的用户体验。Spring MVC 提供了多种 ThemeResolver 的实现,如 FixedThemeResolver(固定使用指定的主题)等。
15、FlashMapManager(Flash 属性管理器)
FlashMapManager 用于管理 Flash 属性,即一种在重定向请求之间传递数据的机制。它允许开发人员将数据暂时存储在 Flash 属性中,并在下一个请求中将其取出并使用。Spring MVC 提供了多种 FlashMapManager 的实现,如 SessionFlashMapManager(将 Flash 属性存储在会话中)等。
16、Message Converter(消息转换器)
将请求体或响应体转换为 Java 对象或从 Java 对象转换为请求体或响应体。
//配置添加了一个字符串消息转换器,用于处理字符串类型的 HTTP 请求和响应。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
}
}
17、RequestToViewNameTranslator(ViewName翻译器)
RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName。因为 ViewResolver 根据ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName,便要通过这个组件从请求中查找 ViewName。
执行流程
- 用户发送出请求到前端控制器DispatcherServlet,这是一个调度中心
- DispatcherServlet收到请求调用HandlerMapping(处理器映射器)
- HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdapter(处理器适配器)
- HandlerAdapter经过适配调用具体的处理器(Handler/Controller)
- Controller执行完成返回ModelAndView对象。
- HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)
- ViewReslover解析后返回具体View(视图)
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)
- DispatcherServlet响应用户。
当然现在的开发,基本都是前后端分离的开发的,并没有视图这些,一般都是handler中使用Response直接结果返回
三、Spring Boot
常见注解
SpringBoot提供了很多注解,可以帮助我们快速构建应用程序。以下是SpringBoot最常用的50个注解:
1、@SpringBootApplication
通常被用于Spring Boot应用程序的入口类上,用于启动Spring Boot应用程序。可以简化Spring应用程序的配置和启动过程。这是一个组合注解,由三个注解组成 :
- @SpringBootConfiguration:组合了@Configuration注解,指示这个类是一个配置类,它定义了一个或多个@Bean方法,用于创建和配置Spring应用程序上下文中的Bean。
- @EnableAutoConfiguration:启用Spring Boot的自动配置功能,它会自动添加所需的依赖项和配置,以使应用程序能够运行。
- @ComponentScan:指示Spring Boot扫描当前包及其子包中的所有@Component、@Service、@Repository和@Controller注解的类,并将它们注册为Spring Bean。
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
2、@RestController
与@Controller类似,但是@RestController会自动将返回值转换为JSON格式。
@RestController是Spring Framework 4.0版本引入的一个注解,它是@Controller和@ResponseBody的组合注解。它用于标注一个类,表示这个类是一个RESTful风格的控制器,可以处理HTTP请求并返回JSON/XML格式的响应。
@RestController注解用于替代原来的@Controller注解,它默认情况下会将控制器方法的返回值转换为JSON格式,并以HTTP响应的方式返回给客户端。如果需要返回XML格式的响应,可以使用其他注解,如@Produces和@Consumes。
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, World!";
}
}
3、@RequestMapping
用于映射请求URL和处理方法。@RequestMapping是Spring MVC框架中的一个核心注解,它用于映射HTTP请求和控制器方法之间的关系。它可以用于类级别和方法级别,用于指定请求URL和HTTP方法(GET、POST、PUT、DELETE等)。
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/users")
public List<User> getUsers() {
// 获取用户列表
}
@PostMapping("/users")
public void createUser(@RequestBody User user) {
// 创建新用户
}
@GetMapping("/users/{id}")
public User getUserById(@PathVariable Long id) {
// 根据ID获取用户信息
}
@PutMapping("/users/{id}")
public void updateUser(@PathVariable Long id, @RequestBody User user) {
// 更新用户信息
}
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
// 根据ID删除用户
}
}
4、@GetMapping
用于映射HTTP GET请求。
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/users")
public List<User> getUsers() {
// 获取用户列表
}
@GetMapping("/users/{id}")
public User getUserById(@PathVariable Long id) {
// 根据ID获取用户信息
}
}
5、@PostMapping
用于映射HTTP POST请求。
@RestController
@RequestMapping("/api")
public class UserController {
@PostMapping("/users")
public void createUser(@RequestBody User user) {
// 创建新用户
}
}
6、@PutMapping
用于映射HTTP PUT请求。
@RestController
@RequestMapping("/api")
public class UserController {
@PutMapping("/users/{id}")
public void updateUser(@PathVariable Long id, @RequestBody User user) {
// 更新用户信息
}
}
7、@DeleteMapping
用于映射HTTP DELETE请求。
@RestController
@RequestMapping("/api")
public class UserController {
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
// 根据ID删除用户
}
}
8、@RequestParam
用于获取请求参数的值。
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/users")
public List<User> getUsers(@RequestParam("page") int page, @RequestParam("size") int size) {
// 分页获取用户列表
}
}
9、@PathVariable
用于获取URL中的参数值。@PathVariable是Spring MVC框架中的一个注解,用于将HTTP请求路径中的变量绑定到控制器方法的参数上。
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
// 根据ID获取用户信息
}
}
10、@RequestBody
用于将HTTP请求的主体转换为方法的参数。@RequestBody是Spring MVC框架中的一个注解,用于将HTTP请求体中的数据绑定到控制器方法的参数上。
@RestController
@RequestMapping("/api")
public class UserController {
@PostMapping("/users")
public User createUser(@RequestBody User user) {
// 创建用户
}
}
11、@ResponseBody
用于将方法的返回值转换为HTTP响应的主体。@ResponseBody是Spring MVC框架中的一个注解,用于将控制器方法的返回值转换为HTTP响应体中的数据。
@RestController
public class UserController {
@GetMapping("/users/{id}")
@ResponseBody
public User getUser(@PathVariable int id) {
// 从数据库或其他地方获取用户数据
User user = userService.getUserById(id);
return user;
}
}
12、@Autowired
用于自动装配Spring容器中的Bean。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
// 实现UserService接口中的方法
}
13、@Component
用于标识一个类是Spring容器中的组件。@Component是Spring框架中的一个通用注解,用于标注一个类作为Spring Bean。
@Component
public class UserServiceImpl implements UserService {
// 实现UserService接口中的方法
}
14、@Service
用于标识一个类是Spring容器中的服务组件。@Service是Spring框架中的一个注解,用于标注一个类作为服务类(Service)。
@Service
public class UserServiceImpl implements UserService {
// 实现UserService接口中的方法
}
15、@Repository
用于标识一个类是Spring容器中的数据访问组件。@Repository是Spring框架中的一个注解,用于标注一个类作为数据访问对象(DAO)。
@Repository
public class UserRepositoryImpl implements UserRepository {
// 实现UserRepository接口中的方法
}
16、@Configuration
用于标识一个类是Spring的配置类。@Configuration是Spring框架中的一个注解,用于标注一个类作为配置类。
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl();
}
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
}
17、@Value
用于获取配置文件中的属性值。@Value是Spring框架中的一个注解,用于将配置文件中的属性值注入到Bean对象中。
@Component
public class MyComponent {
@Value("${my.property}")
private String myProperty;
// 其他方法
}
这个类使用@Component注解标注,表示这个类是一个Spring Bean,可以被其他的Spring Bean自动装配。
在属性级别上,@Value注解指定了需要注入的属性值,这个属性值可以通过${...}的方式引用配置文件中的属性值。
在这个例子中,MyComponent类中的myProperty属性使用@Value注解指定了需要注入的属性值,Spring会自动将配置文件中名为my.property的属性值注入到这个属性中。
@Value注解用于注入配置文件中的属性值,使得开发者可以方便地从配置文件中获取属性值,并将其注入到Bean对象中。同时,使用@Value注解还可以方便地处理不同环境下的配置文件,如开发环境和生产环境的配置文件。
@Value注解是Spring框架中比较常用的注解之一,可以让开发者更加专注于业务逻辑的实现,而不必关心属性值的获取和注入细节。
18、@Bean
用于将一个方法返回的对象注册到Spring容器中。@Bean是Spring框架中的一个注解,用于将一个方法返回的对象注册为一个Spring Bean。
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl();
}
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
}
19、@Import
用于导入其他配置类或Bean。
@Configuration
@Import({AppConfig1.class, AppConfig2.class})
public class AppConfig {
// 其他方法
}
20、@Conditional
用于根据条件判断是否创建Bean或执行配置。
@Configuration
public class AppConfig {
@Bean
@Conditional(DatabaseTypeCondition.class)
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
// 其他方法
}
21、@Profile
用于指定配置的环境,如开发环境、测试环境或生产环境。
@Configuration
public class AppConfig {
@Bean
@Profile("dev")
public UserService userServiceDev() {
return new UserServiceDevImpl();
}
@Bean
@Profile("prod")
public UserService userServiceProd() {
return new UserServiceProdImpl();
}
// 其他方法
}
22、@PropertySource
用于指定配置文件的位置。@PropertySource是Spring框架中的一个注解,用于指定一组属性文件的位置,从而可以在Spring应用程序中使用这些属性。
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig {
@Autowired
private Environment environment;
@Bean
public UserService userService() {
return new UserServiceImpl(environment.getProperty("userService.name"));
}
// 其他方法
}
这个类使用@Configuration注解标注,表示这个类是一个配置类,用于配置应用程序的Bean对象。
在类级别上,使用@PropertySource注解可以指定一个属性文件的位置。在这个例子中,使用@PropertySource注解指定了一个名为application.properties的属性文件,它位于classpath下。
在方法级别上,使用@Bean注解标注方法,表示这个方法返回一个Bean对象。在这个例子中,使用Environment对象从属性文件中读取属性值,并将这些属性值传递给UserService实例的构造方法。
@PropertySource注解用于指定一组属性文件的位置,使得开发者可以在Spring应用程序中使用这些属性。同时,使用Environment对象可以方便地读取属性文件中的属性值,并将这些属性值传递给Bean对象的构造方法或属性。
@PropertySource注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地管理和配置Spring Bean。
23、@Qualifier
用于指定注入的Bean的名称。
@Component
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier("userRepositoryImpl")
private UserRepository userRepository;
// 其他方法
}
24、@ExceptionHandler
用于处理异常。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView handleException(Exception ex) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("errorMessage", ex.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
这个类使用@ControllerAdvice注解标注,表示这个类是一个全局异常处理器。在方法级别上,使用@ExceptionHandler注解可以指定一个方法来处理控制器中抛出的异常。
在这个例子中,使用@ExceptionHandler注解指定了一个名为handleException的方法,它处理所有类型的异常。当控制器中抛出异常时,会调用这个方法,并将异常对象作为参数传递给这个方法。
在这个方法中,使用ModelAndView对象来封装错误信息,并将视图名称设置为error。最后,返回这个ModelAndView对象,将错误信息显示到用户界面上。
@ExceptionHandler注解用于处理控制器中抛出的异常,使得开发者可以根据需要灵活地处理异常。同时,使用@ControllerAdvice注解可以将这个异常处理器应用于所有的控制器中。
@ExceptionHandler注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地处理控制器中的异常。
25、@ResponseStatus
用于指定异常的HTTP响应状态码。
@Controller
public class UserController {
@GetMapping("/user/{id}")
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public UserDetails getUserDetails(@PathVariable("id") Long id) {
// 查询用户信息
UserDetails userDetails = userService.getUserDetails(id);
if (userDetails == null) {
throw new UserNotFoundException("User not found");
}
return userDetails;
}
@ExceptionHandler(UserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
@ResponseBody
public String handleUserNotFoundException(UserNotFoundException ex) {
return ex.getMessage();
}
}
26、@ControllerAdvice
用于全局处理异常。@ControllerAdvice是Spring框架中的一个注解,用于定义全局控制器通知。
在Spring MVC框架中,控制器通知是一些特殊的组件,它们可以在控制器方法执行前、执行后或抛出异常时执行一些额外的逻辑处理。使用@ControllerAdvice注解可以定义全局控制器通知,它可以应用于所有的控制器。
@ControllerAdvice
public class GlobalControllerAdvice {
@ModelAttribute("currentUser")
public User getCurrentUser() {
// 获取当前登录用户信息
User currentUser = userService.getCurrentUser();
return currentUser;
}
@InitBinder
public void initBinder(WebDataBinder binder) {
// 注册自定义的属性编辑器
binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
}
@ExceptionHandler(Exception.class)
public ModelAndView handleException(Exception ex) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("errorMessage", ex.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
这个类使用@ControllerAdvice注解标注,表示这个类是一个全局控制器通知。在方法级别上,使用@ModelAttribute注解标注方法,表示这个方法会在所有控制器方法执行前执行,用于将当前登录用户信息添加到模型中。
使用@InitBinder注解标注方法,表示这个方法会在所有控制器方法执行前执行,用于注册自定义的属性编辑器。
使用@ExceptionHandler注解标注方法,表示这个方法会在控制器中抛出异常时执行,用于处理控制器方法中抛出的异常。
@ControllerAdvice注解用于定义全局控制器通知,使得开发者可以在所有控制器方法执行前、执行后或抛出异常时执行一些额外的逻辑处理。同时,使用@ModelAttribute注解可以将一些公共的模型数据添加到模型中,使用@InitBinder注解可以注册自定义的属性编辑器,使用@ExceptionHandler注解可以处理控制器方法中抛出的异常。
@ControllerAdvice注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地定义全局控制器通知。
27、@CrossOrigin
用于解决跨域问题。@CrossOrigin是Spring框架中的一个注解,用于解决跨域资源共享(CORS)问题。
跨域资源共享是浏览器安全策略的一部分,它限制了浏览器在不同域名之间发送和接收HTTP请求。使用@CrossOrigin注解可以指定允许跨域访问的域名和HTTP方法。
@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "http://localhost:8080", methods = {RequestMethod.GET, RequestMethod.POST})
public class ApiController {
@GetMapping("/users")
public List<User> getUsers() {
// 查询用户信息
List<User> users = userService.getUsers();
return users;
}
}
这个类使用@RestController注解标注,表示这个类是一个RESTful风格的控制器。在类级别上,使用@RequestMapping注解指定控制器处理的请求路径为/api。同时,使用@CrossOrigin注解可以指定允许跨域访问的域名和HTTP方法。
在这个例子中,使用@CrossOrigin注解指定允许来自http://localhost:8080域名的GET和POST请求访问该控制器中的方法。这意味着,在http://localhost:8080域名下的网页可以通过XMLHttpRequest对象发送GET和POST请求,访问该控制器中的方法。
@CrossOrigin注解用于解决跨域资源共享(CORS)问题,使得开发者可以更加灵活地控制允许跨域访问的域名和HTTP方法。它是一种简单但非常有效的解决方案,可以使得前端开发者更加轻松地开发跨域应用程序。
@CrossOrigin注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地解决跨域资源共享(CORS)问题。
28、@Async
用于将方法标记为异步执行。
在Spring框架中,如果一个方法需要执行一些比较耗时的操作,如果这个方法是在主线程中执行,就会导致主线程被阻塞,用户界面无法响应用户的操作。使用@Async注解可以将这个方法的执行异步化,让主线程继续执行其他任务,提高应用程序的响应性能。
@Service
public class UserService {
@Async
public CompletableFuture<UserDetails> getUserDetailsAsync(Long id) {
// 查询用户信息
UserDetails userDetails = userRepository.getUserDetails(id);
return CompletableFuture.completedFuture(userDetails);
}
}
这个类使用@Service注解标注,表示这个类是一个服务。在方法级别上,使用@Async注解标注方法,表示这个方法需要异步执行。
在这个例子中,getUserDetailsAsync方法使用@Async注解标注,表示这个方法需要异步执行。查询用户信息的操作在异步线程中执行,不会阻塞主线程。同时,这个方法返回一个CompletableFuture对象,表示异步执行的结果。
@Async注解用于异步执行方法,可以提高应用程序的响应性能。它是一种简单但非常有效的解决方案,可以使得开发者更加轻松地编写并发应用程序。
@Async注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地异步执行方法。需要注意的是,异步执行的方法必须在一个独立的线程中执行,因此需要使用线程池来管理异步线程的执行。
29、@Cacheable
用于缓存方法的返回值。
在Spring框架中,如果一个方法的返回结果是固定的,而且这个方法的执行比较耗时,我们可以使用@Cacheable注解将这个方法的返回结果缓存起来,下次执行这个方法时直接从缓存中获取结果,避免重复执行。
@Service
public class UserService {
@Cacheable("userCache")
public User getUser(Long id) {
// 查询用户信息
User user = userRepository.getUser(id);
return user;
}
}
这个类使用@Service注解标注,表示这个类是一个服务。在方法级别上,使用@Cacheable注解标注方法,表示这个方法返回的结果可以被缓存起来。
在这个例子中,getUser方法使用@Cacheable注解标注,表示这个方法的返回结果可以被缓存起来。查询用户信息的操作在第一次执行时会被执行,返回结果会被缓存到名为"userCache"的缓存中。下次执行getUser方法时,如果缓存中已经存在这个结果,就直接从缓存中获取结果,不需要再次执行查询操作。
@Cacheable注解用于缓存方法的返回结果,可以提高应用程序的执行效率。它是一种简单但非常有效的解决方案,可以使得开发者更加灵活地使用缓存来优化应用程序的性能。
@Cacheable注解是Spring框架中比较常用的注解之一,可以让开发者更加轻松地使用缓存来提高应用程序的性能。需要注意的是,使用缓存需要考虑缓存的生命周期和缓存的一致性,必要时需要使用缓存失效机制和缓存更新机制来维护缓存的一致性。
30、@CacheEvict
用于清除缓存。@CacheEvict是Spring框架中的一个注解,用于清空缓存中的数据。
在Spring框架中,如果一个方法的执行会导致缓存数据的失效,我们可以使用@CacheEvict注解将这个方法的缓存数据清空,这样下次执行这个方法时就会重新查询数据并缓存起来。
@Service
public class UserService {
@Cacheable("userCache")
public User getUser(Long id) {
// 查询用户信息
User user = userRepository.getUser(id);
return user;
}
@CacheEvict("userCache")
public void clearCache() {
// 清空缓存
}
}
这个类使用@Service注解标注,表示这个类是一个服务。在方法级别上,使用@Cacheable注解标注getUser方法,表示这个方法的返回结果可以被缓存起来。同时,使用@CacheEvict注解标注clearCache方法,表示这个方法会清空名为"userCache"的缓存。
在这个例子中,getUser方法使用@Cacheable注解标注,表示这个方法的返回结果可以被缓存起来。查询用户信息的操作在第一次执行时会被执行,返回结果会被缓存到名为"userCache"的缓存中。下次执行getUser方法时,如果缓存中已经存在这个结果,就直接从缓存中获取结果,不需要再次执行查询操作。
当调用clearCache方法时,@CacheEvict注解会清空名为"userCache"的缓存,下次执行getUser方法时,就需要重新查询数据并缓存起来。
@CacheEvict注解用于清空缓存中的数据,可以使得开发者更加灵活地控制缓存的生命周期和缓存的一致性。它是一种简单但非常有效的解决方案,可以使得开发者更加轻松地使用缓存来提高应用程序的性能。
@CacheEvict注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地控制缓存的生命周期和缓存的一致性。需要注意的是,清空缓存需要谨慎操作,必要时需要考虑缓存的失效机制和缓存更新机制来维护缓存的一致性。
31、@CachePut
用于更新缓存中的数据。@CachePut是Spring框架中的一个注解,用于更新或添加缓存中的数据。
在Spring框架中,如果一个方法的执行会导致缓存数据的更新或添加,我们可以使用@CachePut注解将这个方法的返回结果更新或添加到缓存中。
@Service
public class UserService {
@Cacheable("userCache")
public User getUser(Long id) {
// 查询用户信息
User user = userRepository.getUser(id);
return user;
}
@CachePut("userCache")
public User updateUser(Long id, User user) {
// 更新用户信息
User updatedUser = userRepository.updateUser(id, user);
return updatedUser;
}
}
这个类使用@Service注解标注,表示这个类是一个服务。在方法级别上,使用@Cacheable注解标注getUser方法,表示这个方法的返回结果可以被缓存起来。同时,使用@CachePut注解标注updateUser方法,表示这个方法会更新或添加名为"userCache"的缓存。
在这个例子中,getUser方法使用@Cacheable注解标注,表示这个方法的返回结果可以被缓存起来。查询用户信息的操作在第一次执行时会被执行,返回结果会被缓存到名为"userCache"的缓存中。下次执行getUser方法时,如果缓存中已经存在这个结果,就直接从缓存中获取结果,不需要再次执行查询操作。
当调用updateUser方法时,@CachePut注解会更新或添加名为"userCache"的缓存,下次执行getUser方法时,就可以从缓存中获取更新后的用户信息。
@CachePut注解用于更新或添加缓存中的数据,可以使得开发者更加灵活地控制缓存的生命周期和缓存的一致性。它是一种简单但非常有效的解决方案,可以使得开发者更加轻松地使用缓存来提高应用程序的性能。
@CachePut注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地控制缓存的生命周期和缓存的一致性。需要注意的是,更新或添加缓存需要谨慎操作,必要时需要考虑缓存的失效机制和缓存更新机制来维护缓存的一致性。
32、@Transactional
用于指定事务的范围。既可以在方法上使用,也可以直接在类上使用。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserRoleRepository userRoleRepository;
@Transactional
public void registerUser(String username, String password, String role) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
userRepository.save(user);
UserRole userRole = new UserRole();
userRole.setUserId(user.getId());
userRole.setRole(role);
userRoleRepository.save(userRole);
}
}
33、@EnableTransactionManagement
用于启用事务管理功能。@Transactional是Spring框架中的一个注解,用于标识一个方法或类需要使用事务进行操作。
在Spring框架中,如果一个方法需要对数据库进行操作,我们可以使用@Transactional注解来确保这个操作在一个事务中进行,从而保证操作的原子性、一致性、隔离性和持久性。
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
public void createUser(User user) {
userRepository.save(user);
}
public void updateUser(Long id, User user) {
User existingUser = userRepository.findById(id);
if (existingUser != null) {
existingUser.setName(user.getName());
existingUser.setEmail(user.getEmail());
userRepository.save(existingUser);
}
}
}
这个类使用@Service注解标注,表示这个类是一个服务。同时,在类级别上使用@Transactional注解标注,表示这个类中的所有方法都需要使用事务进行操作。
在这个例子中,createUser和updateUser方法都需要对数据库进行操作,因此使用userRepository来保存或更新用户信息。由于这个类使用了@Transactional注解来标识,因此userRepository的操作都在一个事务中进行,从而保证操作的原子性、一致性、隔离性和持久性。
@Transactional注解用于标识一个方法或类需要使用事务进行操作,可以使得开发者更加灵活地控制事务的使用。它是一种简单但非常有效的解决方案,可以使得开发者更加轻松地使用事务来提高应用程序的性能和数据一致性。
@Transactional注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地控制事务的使用。需要注意的是,事务的使用需要谨慎操作,必要时需要考虑事务的隔离级别、超时时间和回滚机制等来维护数据的一致性和应用程序的性能。
34、@EnableAspectJAutoProxy
用于启用AOP功能。@EnableAspectJAutoProxy是Spring框架中的一个注解,用于启用自动代理功能,以便使用AOP(面向切面编程)进行编程。
在Spring框架中,如果需要使用AOP来实现某些功能,我们可以使用@EnableAspectJAutoProxy注解来启用自动代理功能,从而在运行时自动为我们生成代理对象,以便进行切面编程。
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
@Bean
public MyAspect myAspect() {
return new MyAspect();
}
@Bean
public UserService userService() {
return new UserService();
}
}
这个类使用@Configuration注解标注,表示这个类是一个配置类。同时,在类级别上使用@EnableAspectJAutoProxy注解标注,表示这个配置类需要启用自动代理功能。
在这个例子中,我们定义了一个MyAspect类来实现某些功能的切面编程。为了让Spring框架能够自动为我们生成代理对象,我们需要将MyAspect类加入到Spring容器中,并且使用@Bean注解标注。另外,我们还定义了一个UserService类来实现某些业务功能。
@EnableAspectJAutoProxy注解用于启用自动代理功能,可以使得开发者更加方便地使用AOP来实现某些功能。它是一种简单但非常有效的解决方案,可以让开发者更加轻松地使用切面编程来提高应用程序的性能和可维护性。
@EnableAspectJAutoProxy注解是Spring框架中比较常用的注解之一,可以让开发者更加方便地使用AOP来实现某些功能。需要注意的是,AOP的使用需要谨慎操作,必要时需要考虑AOP的切面逻辑、切入点和通知类型等来维护应用程序的性能和可维护性。
35、@Aspect
用于定义切面。@Aspect是Spring框架中的一个注解,用于标识一个类为切面类,从而可以在该类中定义切面逻辑以实现AOP(面向切面编程)。
在Spring框架中,如果需要使用AOP来实现某些功能,我们可以使用@Aspect注解来标识一个类为切面类。在切面类中,我们可以定义切面逻辑,包括切入点、通知类型和切面顺序等,从而实现AOP编程的功能。
@Aspect
@Component
public class MyAspect {
@Before("execution(* com.example.UserService.*(..))")
public void beforeAdvice() {
System.out.println("Before advice is executed.");
}
@After("execution(* com.example.UserService.*(..))")
public void afterAdvice() {
System.out.println("After advice is executed.");
}
}
这个类使用@Aspect注解标识,表示这个类是一个切面类。同时,我们还使用@Component注解标识这个类,以便Spring框架能够自动将它加入到Spring容器中。
在这个例子中,我们定义了一个MyAspect类来实现某些功能的切面编程。在这个类中,我们定义了两个通知类型,即@Before和@After,分别表示在目标方法执行前和执行后执行某些操作。这些通知类型的执行条件是通过切入点表达式来定义的。
@Aspect注解用于标识一个类为切面类,可以使得开发者更加方便地使用AOP来实现某些功能。它是一种简单但非常有效的解决方案,可以让开发者更加轻松地使用切面编程来提高应用程序的性能和可维护性。
@Aspect注解是Spring框架中比较常用的注解之一,用于标识一个类为切面类。需要注意的是,AOP的使用需要谨慎操作,必要时需要考虑切入点、通知类型和切面顺序等来维护应用程序的性能和可维护性。
36、@Pointcut
用于定义切点。@Pointcut是Spring框架中的一个注解,用于定义一个切入点,从而可以在该切入点上定义通知类型以实现AOP(面向切面编程)。
在Spring框架中,如果需要使用AOP来实现某些功能,我们可以使用@Pointcut注解来定义一个切入点。在切入点上,我们可以定义切面逻辑,包括通知类型和切面顺序等,从而实现AOP编程的功能。
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* com.example.UserService.*(..))")
public void userServicePointcut() {}
@Before("userServicePointcut()")
public void beforeAdvice() {
System.out.println("Before advice is executed.");
}
@After("userServicePointcut()")
public void afterAdvice() {
System.out.println("After advice is executed.");
}
}
这个类使用@Aspect注解标识,表示这个类是一个切面类。同时,我们还使用@Component注解标识这个类,以便Spring框架能够自动将它加入到Spring容器中。
在这个例子中,我们定义了一个MyAspect类来实现某些功能的切面编程。在这个类中,我们使用@Pointcut注解定义了一个切入点,即userServicePointcut()方法。在这个切入点上,我们定义了两个通知类型,即@Before和@After,分别表示在目标方法执行前和执行后执行某些操作。
@Pointcut注解用于定义一个切入点,可以使得开发者更加方便地使用AOP来实现某些功能。它是一种简单但非常有效的解决方案,可以让开发者更加轻松地使用切面编程来提高应用程序的性能和可维护性。
@Pointcut注解是Spring框架中比较常用的注解之一,用于定义一个切入点。需要注意的是,AOP的使用需要谨慎操作,必要时需要考虑切入点、通知类型和切面顺序等来维护应用程序的性能和可维护性。
37、@Before
用于在方法执行前执行通知。@Before是Spring框架中的一个注解,用于定义在目标方法执行前执行的通知类型,以实现AOP(面向切面编程)。
在Spring框架中,如果需要在目标方法执行前执行某些操作,我们可以使用@Before注解来定义一个通知类型。在这个通知类型中,我们可以编写自己的逻辑代码,从而实现AOP编程的功能。
@Aspect
@Component
public class MyAspect {
@Before("execution(* com.example.UserService.*(..))")
public void beforeAdvice() {
System.out.println("Before advice is executed.");
}
}
38、@After
用于在方法执行后执行通知。@After是Spring框架中的一个注解,用于定义在目标方法执行后执行的通知类型,以实现AOP(面向切面编程)。
在Spring框架中,如果需要在目标方法执行后执行某些操作,我们可以使用@After注解来定义一个通知类型。在这个通知类型中,我们可以编写自己的逻辑代码,从而实现AOP编程的功能。
@Aspect
@Component
public class MyAspect {
@After("execution(* com.example.UserService.*(..))")
public void afterAdvice() {
System.out.println("After advice is executed.");
}
}
39、@Around
用于在方法执行前后执行通知。@Around是Spring框架中的一个注解,用于定义在目标方法执行前后执行的通知类型,以实现AOP(面向切面编程)。
在Spring框架中,如果需要在目标方法执行前后执行某些操作,我们可以使用@Around注解来定义一个通知类型。在这个通知类型中,我们可以编写自己的逻辑代码,从而实现AOP编程的功能。
@Aspect
@Component
public class MyAspect {
@Around("execution(* com.example.UserService.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before advice is executed.");
Object result = joinPoint.proceed();
System.out.println("After advice is executed.");
return result;
}
}
40、@AfterReturning
用于在方法返回结果后执行通知。@AfterReturning是Spring框架中的一个注解,用于定义在目标方法返回结果后执行的通知类型,以实现AOP(面向切面编程)。
在Spring框架中,如果需要在目标方法返回结果后执行某些操作,我们可以使用@AfterReturning注解来定义一个通知类型。在这个通知类型中,我们可以编写自己的逻辑代码,从而实现AOP编程的功能。
@Aspect
@Component
public class MyAspect {
@AfterReturning(pointcut = "execution(* com.example.UserService.*(..))", returning = "result")
public void afterReturningAdvice(Object result) {
System.out.println("After returning advice is executed. Result is " + result);
}
}
41、@AfterThrowing
用于在方法抛出异常后执行通知。@AfterThrowing是Spring框架中的一个注解,用于定义在目标方法抛出异常后执行的通知类型,以实现AOP(面向切面编程)。
在Spring框架中,如果需要在目标方法抛出异常后执行某些操作,我们可以使用@AfterThrowing注解来定义一个通知类型。在这个通知类型中,我们可以编写自己的逻辑代码,从而实现AOP编程的功能。
@Aspect
@Component
public class MyAspect {
@AfterThrowing(pointcut = "execution(* com.example.UserService.*(..))", throwing = "ex")
public void afterThrowingAdvice(Exception ex) {
System.out.println("After throwing advice is executed. Exception is " + ex);
}
}
42、@Order
用于指定切面的执行顺序。@Order是Spring框架中的一个注解,用于定义切面的执行顺序。
在Spring框架中,如果有多个切面类需要对同一个方法进行切面处理,那么这些切面类的执行顺序可能会影响到最终的结果。为了控制这些切面类的执行顺序,我们可以使用@Order注解来定义它们的执行顺序。
@Order注解可以应用在切面类上,用于指定切面执行的顺序。它的参数为一个整数,数值越小表示优先级越高,数值相同时按照类名的自然顺序进行排序。
@Aspect
@Component
@Order(1)
public class MyAspect1 {
@Before("execution(* com.example.UserService.*(..))")
public void beforeAdvice() {
System.out.println("Before advice from MyAspect1 is executed.");
}
}
@Aspect
@Component
@Order(2)
public class MyAspect2 {
@Before("execution(* com.example.UserService.*(..))")
public void beforeAdvice() {
System.out.println("Before advice from MyAspect2 is executed.");
}
}
43、@Slf4j
用于简化日志记录。@Slf4j是Lombok框架中的一个注解,用于在Java类中自动生成日志记录器。
在Java开发中,日志记录是非常重要的一环,可以帮助我们更好地了解程序的运行情况,从而更好地进行调试和优化。通常情况下,我们需要手动引入日志框架(如Log4j、SLF4J等)并编写相应的日志记录代码。这些代码可能会比较繁琐,而且容易出现错误。为了简化这个过程,Lombok框架提供了一个@Slf4j注解,可以在Java类中自动生成日志记录器。
使用@Slf4j注解非常简单,只需要在Java类中添加这个注解即可。在使用时,我们可以直接使用log变量来记录日志,而不需要再引入其他的日志框架
@Slf4j
public class MyService {
public void doSomething() {
log.debug("This is a debug message.");
log.info("This is an info message.");
log.error("This is an error message.");
}
}
在这个例子中,我们定义了一个MyService类,并使用@Slf4j注解来自动生成日志记录器。然后,在doSomething()方法中,我们直接使用log变量来记录日志,而不需要再引入其他的日志框架。
需要注意的是,使用@Slf4j注解需要在编译器中安装Lombok插件,否则可能会出现编译错误。另外,虽然@Slf4j注解非常方便,但在实际应用中,我们还需要根据实际情况选择合适的日志框架,并编写相应的日志记录代码。
总之,@Slf4j是Lombok框架中的一个注解,可以在Java类中自动生成日志记录器,从而简化日志记录的过程。它是一种极为方便的解决方案,可以提高应用程序的可维护性和可读性。
44、@Data
用于自动生成JavaBean的getters、setters、toString、hashCode和equals方法。
@Data是Lombok框架中的一个注解,可以自动生成Java类的getter、setter、equals、hashCode和toString等方法。
在Java开发中,我们经常需要编写一些POJO类来表示数据结构。这些类通常包含一些成员变量,并且需要编写相应的getter、setter、equals、hashCode和toString等方法。这些方法通常是相似的,而且比较繁琐。为了简化这个过程,Lombok框架提供了一个@Data注解,可以自动生成这些方法。
使用@Data注解非常简单,只需要在Java类上添加这个注解即可。在使用时,我们可以直接访问类的成员变量,并且可以自动生成相应的getter、setter、equals、hashCode和toString等方法。
@Data
public class User {
private Long id;
private String name;
private Integer age;
}
45、@NoArgsConstructor
用于生成无参构造函数。@NoArgsConstructor是Lombok框架中的一个注解,用于自动生成一个无参构造方法。
在Java开发中,我们经常需要编写一些POJO类来表示数据结构。这些类通常包含一些成员变量,并且需要编写相应的构造方法。在某些情况下,我们可能需要编写一个无参构造方法,用于创建一个对象的实例。这个构造方法通常是简单的、无需参数的。为了简化这个过程,Lombok框架提供了一个@NoArgsConstructor注解,可以自动生成一个无参构造方法。
使用@NoArgsConstructor注解非常简单,只需要在Java类上添加这个注解即可。在使用时,我们可以直接创建对象的实例,而不需要手动编写无参构造方法。
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
}
在这个例子中,我们定义了一个User类,并使用@NoArgsConstructor注解来自动生成一个无参构造方法。然后,在其他的Java类中,我们可以直接创建User对象的实例,而不需要手动编写无参构造方法。
需要注意的是,使用@NoArgsConstructor注解需要在编译器中安装Lombok插件,否则可能会出现编译错误。另外,虽然@NoArgsConstructor注解非常方便,但在实际应用中,我们还需要根据实际情况选择合适的构造方法,并编写相应的代码。
总之,@NoArgsConstructor是Lombok框架中的一个注解,用于自动生成一个无参构造方法,从而简化Java开发的过程。它是一种极为方便的解决方案,可以提高应用程序的可维护性和可读性。
46、@AllArgsConstructor
用于生成全参构造函数。@AllArgsConstructor是Lombok框架中的一个注解,用于自动生成一个全参构造方法。
在Java开发中,我们经常需要编写一些POJO类来表示数据结构。这些类通常包含一些成员变量,并且需要编写相应的构造方法。在某些情况下,我们可能需要编写一个全参构造方法,用于初始化所有成员变量。这个构造方法通常包含所有成员变量作为参数。为了简化这个过程,Lombok框架提供了一个@AllArgsConstructor注解,可以自动生成一个全参构造方法。
使用@AllArgsConstructor注解非常简单,只需要在Java类上添加这个注解即可。在使用时,我们可以直接创建对象的实例,并传入相应的参数,而不需要手动编写全参构造方法。
@AllArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
}
在这个例子中,我们定义了一个User类,并使用@AllArgsConstructor注解来自动生成一个全参构造方法。然后,在其他的Java类中,我们可以直接创建User对象的实例,并传入相应的参数,而不需要手动编写全参构造方法。
需要注意的是,使用@AllArgsConstructor注解需要在编译器中安装Lombok插件,否则可能会出现编译错误。另外,虽然@AllArgsConstructor注解非常方便,但在实际应用中,我们还需要根据实际情况选择合适的构造方法,并编写相应的代码。
总之,@AllArgsConstructor是Lombok框架中的一个注解,用于自动生成一个全参构造方法,从而简化Java开发的过程。它是一种极为方便的解决方案,可以提高应用程序的可维护性和可读性。
47、@Builder
用于生成Builder模式的构造函数。@Builder是Lombok框架中的一个注解,用于自动生成一个Builder模式的构造器。
在Java开发中,我们经常需要编写一些POJO类来表示数据结构。这些类通常包含一些成员变量,并且需要编写相应的构造方法。在某些情况下,我们可能需要编写一个Builder模式的构造器,用于方便地创建对象实例。Builder模式是一种创建对象的设计模式,它可以通过链式调用的方式设置对象的属性,并最终创建一个不可变的对象。为了简化这个过程,Lombok框架提供了一个@Builder注解,可以自动生成一个Builder模式的构造器。
使用@Builder注解非常简单,只需要在Java类上添加这个注解即可。在使用时,我们可以使用链式调用的方式设置对象的属性,并最终创建一个不可变的对象。
@Builder
public class User {
private Long id;
private String name;
private Integer age;
}
在这个例子中,我们定义了一个User类,并使用@Builder注解来自动生成一个Builder模式的构造器。然后,在其他的Java类中,我们可以使用链式调用的方式设置User对象的属性,并最终创建一个不可变的对象。
需要注意的是,使用@Builder注解需要在编译器中安装Lombok插件,否则可能会出现编译错误。另外,虽然@Builder注解非常方便,但在实际应用中,我们还需要根据实际情况选择合适的构造方法,并编写相应的代码。
总之,@Builder是Lombok框架中的一个注解,用于自动生成一个Builder模式的构造器,从而简化Java开发的过程。它是一种极为方便的解决方案,可以提高应用程序的可维护性和可读性。
48、@EqualsAndHashCode
用于生成hashCode和equals方法。@EqualsAndHashCode是Lombok框架中的一个注解,用于自动生成equals()和hashCode()方法。
在Java开发中,我们经常需要比较两个对象是否相等,并且需要根据对象的属性生成一个hashCode值。为了简化这个过程,Lombok框架提供了一个@EqualsAndHashCode注解,可以自动生成equals()和hashCode()方法。
使用@EqualsAndHashCode注解非常简单,只需要在Java类上添加这个注解即可。在使用时,Lombok会根据类的属性自动生成equals()和hashCode()方法。如果两个对象的所有属性都相等,那么它们的equals()方法返回true,并且它们的hashCode()方法返回相同的值。
@EqualsAndHashCode
public class User {
private Long id;
private String name;
private Integer age;
}
49、@ToString
用于生成toString方法。@ToString是Lombok框架中的一个注解,用于自动生成toString()方法。
在Java开发中,我们经常需要将对象转换为字符串,以便于输出或日志记录。为了简化这个过程,Lombok框架提供了一个@ToString注解,可以自动生成toString()方法。
使用@ToString注解非常简单,只需要在Java类上添加这个注解即可。在使用时,Lombok会根据类的属性自动生成toString()方法,这个方法将输出类的名称和所有属性的名称和值。如果需要排除某些属性,可以使用exclude属性来指定排除的属性。
@ToString(exclude = "password")
public class User {
private Long id;
private String name;
private String password;
}
50、@Getter
用于生成getters方法。@Getter是Lombok框架中的一个注解,用于自动生成getter方法。
在Java开发中,我们经常需要为类的属性编写getter和setter方法。为了简化这个过程,Lombok框架提供了一个@Getter注解,可以自动生成getter方法。
使用@Getter注解非常简单,只需要在Java类上添加这个注解即可。在使用时,Lombok会根据类的属性自动生成对应的getter方法。如果需要生成setter方法,可以使用@Setter注解。
@Getter
public class User {
private Long id;
private String name;
private Integer age;
}
自动配置
自动配置,是指在springboot应用启动时,可以把一些配置类自动注入到spring的ioc容器中,项目运行时可以直接使用这些配置类的属性。
在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:
-
@SpringBootConfiguration
-
@EnableAutoConfiguration
-
@ComponentScan
其中@EnableAutoConfiguration
是实现自动化配置的核心注解。该注解通过@Import
注解导入对应的配置选择器。Spring Boot 在启动时会扫描 META-INF/spring.factories 文件中的所配置的类的全类名。在这些配置类中所定义的Bean会根据 条件注解,决定是否加载对应的配置类或 Bean。
条件注解
@Conditional:扩展注解作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean;
@ConditionalOnMissingBean:当容器中不存在指定的 Bean 时,才会实例化相应的 Bean。
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass:当类路径上存在指定的类时,才会实例化相应的 Bean。
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty:当指定的配置属性存在并有指定的值时,才会实例化相应的 Bean。
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi:JNDI存在指定项
核心配置文件
SpringBoot是约定优于配置,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties 或 application.yml(application.yaml)进行配置(默认配置文件名称:application)
application.properties 语法结构 :key=value
application.yml 语法结构 :key:空格 value (冒号后面必须要有空格)
在同一级目录,如果三类文件共存,优先级为:properties > yml > yaml
使用建议:
尽量使用 application.yml,因为它更加灵活、易读、易维护;对于简单的配置项,可以使用 application.properties;在编写配置文件时,注意缩进和空格的使用,这是 YAML 格式的重要特点;在配置文件中,可以使用 @Value 注解来注入配置属性,也可以使用 @ConfigurationProperties 来批量注入配置属性。
YAML基础语法
1、要点
① 大小写敏感
② 数据值前边必须有空格,作为分隔符
③ 使用缩进表示层级关系
④ 缩进时不允许使用Tab键,只允许使用空格(各个系统 Tab对应的 空格数目可能不同,导致层次混乱)。
⑤ 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
⑥ "#"表示注释,从这个字符一直到行尾,都会被解析器忽略。
2、YAML:数据格式
字面量:(空格)普通的值 [ 数字,布尔值,字符串 ]
boolean: TRUE #TRUE、true、True、FALSE、false、False均可
float: 3.14 #6.8523015e+5 #支持科学计数法
int: 123 #0b1010_0111_0100_1010_1110 #支持二进制、八进制、十六进制
string: HelloWorld #字符串可以直接书写
数组:一组按次序排列的值( List、set )
address:
- beijing
- shanghai
# 行内写法
commpany: [阿里巴巴,华为,腾讯,字节跳动]
对象、Map(键值对)
person:
name: wangzhuo
# 行内写法
person: {name: wangzhuo}
#对象数组格式一
users:
- name: Tom
age: 4
- name: Jerry
age: 5
#对象数组格式二
users:
-
name: Tom
age: 4
-
name: Jerry
age: 5
#对象数组缩略格式
users2: [ { name:Tom,age:4 },{ name:Jerry,age:5 } ]
参数引用
name: tom
person:
name: women
pet: ${name} #引用上面定义的name值
3、读取配置内容
yaml文件更强大的地方在于,可以给我们的实体类直接注入匹配值
逐个注入:@Value
批量注入:@ConfigurationProperties、Environment类
//逐个注入1:@Value("")
@Component //注册bean
public class Dog {
@Value("阿黄")
private String name;
@Value("18")
private Integer age;
}
//逐个注入2:@Value("${}")
@PropertySource(value = "classpath:dog.yml") //@PropertySource:加载指定的配置文件
@Component //注册bean
public class Dog{
@Value("${name}")
private String name;
@Value("${age}")
private Integer age;
}
//批量注入1:@ConfigurationProperties
@Component //注册bean到容器中
public class Person {
private String name;
private integer age;
private boolean marry;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
//有参无参构造、get、set方法、toString()方法
}
//编写yaml文件内容:
person1:
name: wangls
age: 18
marry: true
birth: 1990/10/19
maps: {k1: v1,k2: v2}
lists:
- code
- bodybuilding
- music
dog:
name: summer
age: 1
//批量注入2:Environment类
@Autowired
private Environment env;
@Test
public void test(){
System.out.println(env.getProperty("person1.name"));
System.out.println(env.getProperty("person1.age"));
System.out.println(env.getProperty("person1.dog.name"));
System.out.println(env.getProperty("person1.lists[0]"));
}
@ConfigurationProperties、@Value()对比
对比 | @ConfigurationProperties | @Value() |
功能 | 批量注入配置文件中的属性 | 一个个指定 |
松散语法 | 支持 | 不支持 |
SqEL | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
1、@ConfigurationProperties只需要写一次即可,@Value则需要每个字段都添加
2、松散绑定: 比如我的yml中写的last-name,这个和lastName是一样的, -后面跟着的字母默认是大写的。
3、JSR303数据校验,这个就是我们可以在字段是增加一层过滤器验证 ,可以保证数据的合法性
4、复杂类型封装,yml中可以封装对象 , 使用value就不支持
多环境切换profile
我们在开发Spring Boot应用时,通常同一套程序会被安装到不同环境,比如:开发、测试、生产等。其中数据库地址、服务 器端口等等配置都不同,如果每次打包时,都要修改配置文件,那么非常麻烦。profile功能就是来进行动态配置切换的。
1) profile配置方式多profile文件方式
//例如:application-环境简称.properties/yml
application-dev.properties/yml 开发环境
application-test.properties/yml 测试环境
application-pro.properties/yml 生产环境
2) profile激活方式
配置文件 :
spring:
profiles:
active: 环境简称
虚拟机参数 :在VM options 指定:-Dspring.profiles.active=dev
命令行参数:java –jar xxx.jar --spring.profiles.active=dev
事务
Spirng Boot 默认开启事务,无需做任何事情,直接使用@Transactional即可
Spring Boot各版本对事务管理的开启和配置方式大体上保持了一致性,但具体实现细节和默认行为可能会随版本的更新而略有不同。下面是一些关键点,说明Spring Boot不同版本中事务管理的主要区别:
Spring Boot1.X版本
- 此版本中,事务管理主要通过Spring的@EnableTransactionManagement注解来显式地启用。
- 需要在配置类中添加@EnableTransactionManagement注解,以启动Spring的事务管理功能。
- @Transactional注解用于标记需要事务支持的类或方法。
- 通常还需要显式配置事务管理器,例如DataSourceTransactionManager,并将其注册到Spring容器中。
//1、启动类配置:
/**
* 启动服务
*/
@SpringBootApplication
@RestController
@EnableTransactionManagement //开启事务
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication .class, args);
}
}
//2、开启全局配置:
package com.config;
import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import javax.persistence.Transient;
import javax.sql.DataSource;
import java.util.Properties;
/**
* Title: 事务配置类
*/
@Configuration
public class TxConfigBeanName {
@Autowired(required=true)
@Transient
private DataSourceTransactionManager transactionManager;
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// 创建事务通知
@Bean(name = "txAdvice")
public TransactionInterceptor getAdvisor() {
Properties properties = new Properties();
properties.setProperty("add*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("save*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("modify*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("update*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("delete*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("*Delete*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("disable*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("enable*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("handle*", "PROPAGATION_REQUIRED,-ServiceException");
// properties.setProperty("*", "PROPAGATION_REQUIRED,-ServiceException,readOnly");
TransactionInterceptor tsi = new TransactionInterceptor(transactionManager,properties);
return tsi;
}
@Bean
public BeanNameAutoProxyCreator txProxy() {
BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator();
creator.setInterceptorNames("txAdvice");
creator.setBeanNames("*Service", "*ServiceImpl");
creator.setProxyTargetClass(true);
return creator;
}
}
//3、@Transactional应用
@Transactional(rollbackFor = Exception.class)
public void save(test vo) {
saveTest(vo);//保存主表
saveItem(vo.getItem());//保存子表
}
Spring Boot 2.x 版本
- Spring Boot 2.x系列进一步增强了自动配置能力,对于事务管理也是如此。
- 默认情况下,如果应用程序使用了Spring Data JPA或其他Spring Data模块,Spring Boot会自动配置JpaTransactionManager或相应的事务管理器。
- @EnableTransactionManagement注解在大多数情况下不再是必需的,因为事务管理的自动配置会自动包含它。
- 开发者仍然可以在需要的地方使用@Transactional注解来控制事务的范围和行为。
//1、开启全局配置:
package com.config;
import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import javax.persistence.Transient;
import javax.sql.DataSource;
import java.util.Properties;
/**
* Title: 事务配置类
*/
@Configuration
public class TxConfigBeanName {
@Autowired(required=true)
@Transient
private DataSourceTransactionManager transactionManager;
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// 创建事务通知
@Bean(name = "txAdvice")
public TransactionInterceptor getAdvisor() {
Properties properties = new Properties();
properties.setProperty("add*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("save*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("modify*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("update*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("delete*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("*Delete*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("disable*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("enable*", "PROPAGATION_REQUIRED,-ServiceException");
properties.setProperty("handle*", "PROPAGATION_REQUIRED,-ServiceException");
// properties.setProperty("*", "PROPAGATION_REQUIRED,-ServiceException,readOnly");
TransactionInterceptor tsi = new TransactionInterceptor(transactionManager,properties);
return tsi;
}
@Bean
public BeanNameAutoProxyCreator txProxy() {
BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator();
creator.setInterceptorNames("txAdvice");
creator.setBeanNames("*Service", "*ServiceImpl");
creator.setProxyTargetClass(true);
return creator;
}
}
//2、@Transactional应用
@Transactional(rollbackFor = Exception.class)
public void save(test vo) {
saveTest(vo);//保存主表
saveItem(vo.getItem());//保存子表
}
四、Spring Cloud
核心组件
- Eureka:注册中心。服务注册与发现。
- Feign:远程调用。基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。
- Ribbon:负载均衡。从一个服务的多台机器中选择一台。
- Hystrix:服务熔断。提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩问题。
- Zuul/Gateway:网关管理,由 Zuul 网关转发请求给对应的服务。
随着SpringCloudAlibba在国内兴起 , 我们项目中使用了一些阿里巴巴的组件
-
注册中心/配置中心 Nacos
-
负载均衡 Ribbon
-
服务调用 Feign
-
服务保护 sentinel
-
服务网关 Gateway
流程:
注册中心
常见的注册中心有:Eureka、Nocas、Zookeeper
1、Eureka
Eureka是Netflix开发的一个用于实现服务注册和发现的服务。Eureka主要由两部分组成:Eureka服务器和Eureka客户端。
(1)服务注册
服务方需要把自己的信息注册到eureka,由eureka来保持这些信息(如服务名称、IP、端口等)。
(2)服务发现
消费者向eureka拉取服务列表信息,如果服务方有集群,则消费方会利用负载均衡算法,选择一个发起调用。
(3)服务监控
服务方会每隔30秒向eureka发送心跳,报告健康状态,如果eureka服务90秒没收到心跳,从eureka中剔除。
2、Nacos
Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它支持多种服务治理能力,包括服务注册与发现、动态配置管理、动态DNS服务等。
(1)注册服务
Nacos作为注册中心,接收客户端(服务实例)发起的注册请求,并将注册信息存放到注册中心进行管理。注册请求的处理包括客户端组装注册请求、随机选择集群中的一个Nacos节点发起注册、实现负载均衡、路由转发、处理请求、保证数据最终一致性等步骤。
(2)服务发现
Nacos通过维护服务实例的列表,使得服务消费者可以通过Nacos查询到可用服务的列表,进而进行服务调用。
(3)临时实例与永久实例
Nacos中区分了临时实例和永久实例。
临时实例在注册到注册中心后仅保存在服务端内部缓存中,不会持久化到磁盘。当服务实例异常或下线时,会从服务注册表中剔除。
而永久实例不仅存在于服务注册表中,还会被持久化到磁盘文件中。即使服务实例异常或下线,Nacos也不会将其从服务注册表中剔除,而是将其健康状态设置为不健康。
nacos、eureka区别
在都作为注册中心的前提下
共同点
- 都支持服务注册和服务拉取
- 都支持服务方心跳方式做健康检测
不同点
- nacos支持“服务端”主动检测“服务方”状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;eureka采用AP方式。AP模式(Availability Priority Mode)高可用性,CP模式(Consistency Priority Mode)一致性
- 特别地,nacos还支持了配置中心,eureka则只有注册中心。
负载均衡
负载均衡ribbon,发起远程调用feign
Ribbon负载均衡策略:
- RoundRobinRule:简单轮询服务列表来选择服务器
- WeightedResponseTimeRule:按照权重来选择服务器,响应时间越长,权重越小
- RandomRule:随机选择一个可用的服务器
- BestAvailableRunle:忽略那些短路的服务器,并选择并发数较低的服务器
- RetryRule:重试机制的选择逻辑
- AvailabilityFilteringRule:可用性敏感策略,先过滤非健康的,再选择连接数较小的实例
- ZoneAvoidanceRule:以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后对Zone内的多个服务做轮询。
远程调用
Feign
Feign是一个声明式的Web服务客户端,用来简化HTTP远程调用。在Spring Cloud中,Feign可以用来封装HTTP调用的接口,使得调用远程服务就像调用本地方法一样简单。通过创建一个接口并注解,Feign可以生成实现该接口的动态代理,从而允许调用其他服务。
(1)Feign支持HTTP的各种方法
包括GET、POST、PUT、DELETE和PATCH等。通过使用@RequestMapping注解及其变种(如@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping)来指定HTTP方法。
(2)如何配置Feign的超时时间
可以通过在Feign客户端的配置文件中设置超时时间来配置Feign的超时时间。例如,将连接超时时间和读取超时时间都设置为5000毫秒(5秒),可以在application.properties或application.yml文件中进行如下配置:
feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000
这将确保如果请求在5秒内没有连接成功或没有返回结果,Feign将会超时。
(3)Feign如何处理服务降级
Feign可以通过集成Hystrix来实现服务降级。要启用服务降级,可以执行以下步骤:首先,添加Hystrix依赖到项目中;其次,在Feign客户端的方法上使用@HystrixCommand注解;最后,配置Hystrix的属性以实现降级逻辑,如设置回退方法等。
(4)Feign与Ribbon的关系
Feign与Ribbon结合使用可以实现客户端负载均衡。Feign本身不直接支持负载均衡策略,而是通过集成Ribbon来实现。因此,Feign可以使用Ribbon支持的各种负载均衡策略,包括轮询、随机、权重、最佳可用等。要配置Feign使用特定的负载均衡策略,可以在Feign客户端的配置文件中设置Ribbon的负载均衡策略
Feign远程调用原理
首先通过@EnableFeignClients注解开启FeignClient 的功能。只有这个注解存在,才会在程序启动时开启对@FeignClient注解的包扫描。
根据Feign的规则实现接口,并在接口上面加上@FeignClient注解。
程序启动后,会进行包扫描,扫描所有的@ FeignClient 的注解的类,并将这些信息注入IoC容器中。
当接口的方法被调用时,通过JDK的代理来生成具体的RequestTemplate模板对象。
根据RequestTemplate再生成Http请求的Request对象。
Request 对象交给Client去处理,其中Client的网络请求框架可以是HtpURLConnection、HttpClient和OkHttp。
最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。
服务熔断
(1)服务雪崩
一个服务失败,导致整条链路的服务都失败的情形
(2)服务降级
服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突多影响变得不可用,确保服务不会崩溃,一般在实际开发中与feign接口整合,编写降级逻辑。
降级其实就相当于,当我们向一个服务发起请求,当请求超时了,就会把这次请求记录到服务中,然后就会尝试向其他服务发请求,如果还没成功,就对这次请求进行处理(怎么处理取决于业务需求如)就相当于try catch一样的逻辑,当然hystrix底层使用aop来实现的。
(3)服务熔断
默认关闭,需要手动打开,如果检测到10秒内接口失败率超过了50%,就触发熔断机制。之后每隔5秒重新尝试请求微服务,如果微服务不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,恢复正常请求。
sentinel、hystrix区别
Sentinel和Hystrix都是用于微服务架构中的熔断降级框架,但它们在设计理念、功能实现和应用场景上存在显著差异。具体如下:
隔离策略和动态调节: Sentinel提供了基于线程池和信号量的隔离方式,并且能够根据系统负载情况动态调整资源的并发度,这使得它更加灵活且适应性强。相比之下,Hystrix主要采用线程池隔离,虽然也支持信号量隔离,但在动态调节方面相对静态,需要通过配置进行调整。
功能和适用场景: Sentinel不仅提供熔断降级功能,还包括流量控制、实时监控和动态规则配置等,使其适用于需要流量控制和系统负载保护的复杂场景。Hystrix则专注于熔断和降级功能,更适用于需要快速响应和高并发控制的场景。
实时指标统计: 两者都基于滑动窗口进行实时指标统计,但Sentinel的默认实现是基于LeapArray的高性能滑动窗口,而Hystrix在1.5版本后对实时指标统计的实现进行了重构,采用了基于RxJava的事件驱动模式。
总结来说,选择Sentinel还是Hystrix应根据项目的具体需求和技术栈来决定,如果项目需要高度的灵活性和动态调节能力,以及流量控制功能,Sentinel可能是更好的选择;如果项目主要关注快速失败和资源隔离,以及对配置的精细控制,Hystrix可能更适合。
网关
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
Zuul 网关主要由以下几个组件构成:
Filter:过滤器,可以在请求被路由前或者之后添加一些处理逻辑。
Route:路由,将请求路由到不同的后端服务上。
Ribbon:负载均衡器,Zuul 默认使用 Ribbon 进行负载均衡。
Hystrix:容错处理器,可以实现限流和熔断机制。
Zuul 的过滤器链是整个网关的核心部分,它由多个过滤器构成,每个过滤器都负责不同的处理逻辑,比如请求的鉴权、转发等操作。过滤器链在处理请求的过程中,会依次执行这些过滤器,从而实现对请求的全生命周期管理
Zuul 网关可以应用于各种场景中,主要包括以下几个方面:
负载均衡:Zuul 可以将请求分发到不同的后端服务上,实现负载均衡的功能。
路由转发:Zuul 可以根据请求的 URL,将请求转发到不同的后端服务上,实现路由转发的功能。
鉴权和安全:Zuul 可以对请求进行鉴权和认证,保障系统的安全性。
限流和熔断:Zuul 可以在高并发的情况下,通过限流和熔断机制,保障后端服务的可用性。
如何实现服务注册发现?
我理解的是主要三块大功能,分别是服务注册 、服务发现、服务状态监控
我们当时项目采用的eureka作为注册中心,这个也是spring cloud体系中的一个核心组件
服务注册:服务提供者需要把自己的信息注册到eureka,由eureka来保存这些信息,比如服务名称、ip、端口等等
服务发现:消费者向eureka拉取服务列表信息,如果服务提供者有集群,则消费者会利用负载均衡算法,选择一个发起调用
服务监控:服务提供者会每隔30秒向eureka发送心跳,报告健康状态,如果eureka服务90秒没接收到心跳,从eureka中剔除