spring 官方文档整合
Spring
spring 使每个人更轻松,更安全的进行 java 编程
核心
控制反转 ioc
控制反转是一种设计模式,计算机框架的自定义代码从通用框架接收控制流。区别于传统模式的代码调用库处理任务,它是框架调用自定义代码。
依赖注入 di
依赖注入也是一种设计模式,一个对象接收它所依赖的其他对象,是控制反转的一种形式,旨在分离对象,达到松散耦合的目的,使用者不必知道代码如何实现服务,注入直接使用即可。根本上说,依赖注入就是将参数传递给方法。客户端不用自己构建服务,只需要声明它所使用服务的接口,而不是具体实现。
有趣的是官方给出的例子帮助我们理解:五岁儿童的依赖注入,儿童自己去冰箱拿东西吃,这并不合理,最好是儿童向父母直接要就好了,不必在意东西本身。
依赖注入将对象的构造方式和对象的使用方式分开,导致它降低了 new 关键字的重要性
优点:1 减少了代码样板,所有依赖创建由单个组件处理 2 支持并发开发,两个开发人员可以独立开发相互使用的类 3 依赖注入可以直接用于测试
依赖注入类型:1 接口的注入 2 构造器的注入 3 Setter 的注入
概述
注意:从 Spring 6.0 开始,Spring 需要 JDK 17+
企业中,应用程序生命周期较长,必须运行在JDK和应用服务器上,开发者无法控制程序升级;另外一些应用以jar包嵌入服务器运行;还有的则是独立的应用程序,不需要服务器。
理解
翻译为春天,一切的开始,随着时间推移,其他spring项目建立在spring框架上。广义的spring指的是spring全家桶(mvc, boot, security ... ),这里指的是spring框架本身。spring框架也被划分成多个模块。其中core是核心模块,包含配置模型和依赖注入机制,此外,spring框架为不同的应用架构提供支持,包括消息传递,事务,持久性和web。它包括基于servlet的spring mvc web框架。
历史
03年spring诞生,传统的javaee和继承者jakartaee和spring有竞争且互补的关系,spring从传统javaee挑选规范进行整合,spring支持依赖注入和注解,开发人员可以选择这些规范代替spring专用机制。spring6升级到jakartaee9级别,基于jakarta命名空间,为jakartaee提供开箱即用支持。spring6和tomcat10兼容,和Hibernate orm 6.1兼容。
j2ee和spring早期,应用程序是为了部署到服务器而创建,今天,在springboot的支持下,应用程序对开发者和云计算极为友好,servlet容器是嵌入式的,易于改变,spring5开始web应用可以实现在非servlet容器的服务器运行
spring是一个全家桶,除了spring框架,还有springboot,springsecurity,springdata,springcloud,springbatch等,每个项目有自己的源代码库,版本
设计理念
spring框架的指导原则:
- 通过配置切换供应商,不需要改变代码
- 向后兼容,不同版本不存在破坏性变化,支持JDK和第三方库,方便维护依赖spring的应用程序和库
- 代码结构干净,包于包之间没有依赖关系
入门
使用spring,可以创建一个基于springboot的应用程序来使用spring框架
framework
IOC 容器
springioc容器和bean简介
控制反转也被称为依赖注入,是一个过程,首先对象定义依赖关系,然后容器在创建bean时注入这些依赖关系,bean本身直接构建类来控制依赖关系的实例化的逆过程
beanfactory提供了配置框架和基本功能,applicationcontext增加企业功能,applicationcontext是beanfactory的超集
构成应用程序的骨干并由springioc容器管理的对象称为bean,是ioc容器实例化,组装,管理的对象
容器概述
ioc容器是org.springframework.context.applicationcontext接口,通过读取配置来确定实例化,配置和组装那些bean。配置由xml,注解和java代码组成。
容器的实例化需要短短几行xml代码就够了
配置元数据
配置是你告诉容器在你应用中实例化,配置,组装对象
Bean的配置为
xml的配置元数据基本结构
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="..."> (1) (2)
<!-- 这个bean的合作者和配置在这里 -->
</bean>
<bean id="..." class="...">
<!-- 这个bean的合作者和配置在这里 -->
</bean>
<!-- 更多bean 定义在这里 -->
</beans>
- id是识别单个bean的字符串
- class定义了bean的类型,使用类的全路径名
实例化容器
提供给容器构造函数资源字符串
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
构建基于xml的配置元数据
让bean的定义跨越多个xml文件
<beans>
<import resource="services.xml"/> # 这里导入了其他的xml配置文件
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
外部bean从三个文件加载:service.xml messageSource.xml themeSource.xml ,所有配置文件应该在同一路径下,这也是为了减少耦合
构建基于groovy的配置元数据
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
使用容器
applicationcontext 是一个高级工厂的接口,能维护bean及注册表,通过getBean方法可以找到bean实例
// 创建和配置bean
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// 检索配置的实例
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// 使用配置的实例
List<String> userList = service.getUsernameList();
其实,getBean是不可取的,你可以@Autowiring直接注入bean
Bean概述
ioc容器管理多个bean,这些bean是你提供给容器的配置元数据创建的
Bean命名
一个bean可以有一个或多个标识符,这些标识符在容器中不能重名,一个bean一般有一个标识符,假如有多个标识符多余的标识符是别名
基于xml配置,bean标识符用id,name来指定
容器自动为bean命名一个唯一名称,默认就是小驼峰命名,然而你如果使用ref元素来查找这个bean,必须提供一个名称
bean的命名规则,小驼峰命名,统一命名bean让你的程序更容易阅读理解,对使用aop很有帮助
定义bean的别名
在
<alias name="fromName" alias="toName"/>
实例化Bean
bean本质是创建一个或多个对象。容器被要求查看命名的bean,使用bean定义所封装的配置来创建对象
xml 中用
用构造函数实例化
用构造函数创建bean,所有普通类被spring使用并兼容。
用静态工厂实例化
用class属性指定包含static工厂方法的类,factory-method属性指定工厂方法名称,这个bean的用途是在遗留代码中调用static工厂
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
上面定义一个和bean定义一起工作的类
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
这里class定义没有返回指定返回类型,指定了包含工厂方法的类
用实例工厂方法进行实例化
实例工厂方法实例化从容器调用现有的bean的非静态方法创建一个新的bean,把class留空,在factory-bean属性指定当前容器的bean名称,factory-method指定方法名称
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类可以容纳多个工厂方法,工厂bean本身可以通过依赖注入进行管理和配置
确定bean的运行时类型
在bean元数据定义中指定的只是一个初始的类引用,可能于工厂方法结合,导致bean运行时类型不同,aop代理可能会基于接口的代理包装bean实例,对目标bean的实际类型显示不全
推荐的方法是指定bean名称进行getType调用
依赖
企业应用程序不是单一的bean对象组成,即使最简单的应用也有一些对象,他们一起工作,呈现给用户一个应用。如何从定义一些单独的bean定义一个完全实现的应用,这个应用中各对象协作实现目标
依赖注入
依赖注入DI是一个过程,对象通过构造参数,工厂方法返回设置的属性定义它们的依赖。然后,在容器创建bean时注入这些依赖。这个过程根本上是bean本身通过使用类的直接构造或服务定位模式控制其依赖的实例化或位置的逆过程。
采用DI,代码会更干净,对象提供依赖时,体现出解耦的有效,对象不会查找其依赖,不知道依赖位置或类别,类更容易测试,特别是依赖在接口或抽象类是,允许单元测试使用stub或mock实现
DI有两个类别,基于构造器注入依赖和基于setter注入依赖
基于构造器注入依赖
调用很多参数的构造函数完成,每个参数代表一个依赖,和调用带有特定参数的static工厂方法构造bean几乎等价
下面的例子是只用构造函数注入的依赖注入类
public class SimpleMovieLister {
private final MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder){
this.movieFinder = movieFinder;
}
}
这个类是一个POJO,普通java类,对容器的特定接口,基类或注解没有依赖
构造函数参数解析
构造函数参数解析匹配是通过参数类型解析。bean定义的参数定义顺序是构造器参数顺序
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
前提是ThingTwo和ThingThree没有继承关系,不存在潜在歧义。不需要在
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/> <!--这里ref引用了下面的类-->
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
ref引用其他bean,类型已知时,可以匹配。当使用一个简单的类型时,比如
package examples;
public class ExampleBean {
private final int years;
private final String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
前面的情况,使用type显式指定构造函数参数类型,容器使用简单类型的类型匹配
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="100"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
可以使用 index 明确指定构造函数参数的索引
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="100"/>
<constructor-arg index="1" value="42"/>
</bean>
指定索引可以解决构造函数有两个相同类型参数的歧义,索引下标从0开始
构造函数参数名
我们可以用构造函数的参数名称进行消歧
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="100"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
使用这一方案需要在POJO类中加一个@ConstructorProperties注解明确命名你的构造函数参数
package examples;
public class ExampleBean {
private final int years;
private final String ultimateAnswer;
@ConstructorProperties({"years"}, {"ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于Setter的依赖注入
通过容器在调用无参数的构造函数或无参数的static工厂方法实例化bean后调用setter方法实现
基于构造器的DI还是基于setter的DI
对强制依赖使用构造函数,对可选依赖使用setter方法或配置方法。在setter方法使用@Autowired可以使属性成为必须的依赖;官方提倡使用构造器依赖,
依赖的解析过程
容器按如下方式执行bean依赖解析
- applicationcontext 描述所有bean的配置创建和初始化,配置元数据可以有xml java代码 注解指定
- bean的依赖以属性,构造函数,静态工厂方法参数形式表达,创建bean时,这些依赖被提供给bean
- 每个属性或构造函数参数都要设置的值的实际定义,或对容器中另一个bean的引用
- 每个作为值的属性或构造函数参数从指定格式转换为该属性或构造函数参数的实际类型
循环依赖
使用构造函数注入,产生一个无法解决的循环依赖情况;类A注入B的实例,类B注入A实例,这样相互注入,springioc容器在运行时,检测到这种循环引用,并报错BeanCurrentlyInCreationException
解决方案是使用setter注入,这样,两个bean会其中一个会在完全初始化之前被注入到另一个bean中,典型的鸡生蛋蛋生鸡场景
┌─────┐
| movieFinder defined in file [D:\ideaProject\demoSpringBoot\target\classes\com\example\entity\MovieFinder.class]
↑ ↓
| simpleMovieLister defined in file [D:\ideaProject\demoSpringBoot\target\classes\com\example\entity\SimpleMovieLister.class]
└─────┘
# spring 报错如上
如果不存在循环依赖关系,bean实例化其依赖被设置,相关生命周期方法按顺序被调用,构造器调用在setter之前
依赖和配置细节
spring支持xml的配置元数据支持
字面值(基本类型,string等)
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="misterkaoli"/>
</bean>
使用p-namespace实现简洁的xml配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="misterkaoli"/>
</beans>
可以配置一个 properties 实例
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
对于其他bean的引用
ref是
parent属性指定目标bean,可以创建对当前容器的父容器中bean的引用。parent属性的值可以于目标bean的id 或name属性的一个值相同。
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required here -->
</bean>
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
内部bean
在
<bean id="outer" class="...">
<!-- 而不是使用对目标Bean的引用,只需在行内定义目标Bean即可 -->
<property name="target">
<bean class="com.example.Person"> <!-- 这是内部Bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部bean定义不用定义id或name,内部bean总是匿名的,与外部bean一同创建
懒加载的bean
applicationcontext实现会机器创建和配置所有的单例bean,作为初始化的一部分。当这种行为不可取时,通过将bean定义标记为懒加载阻止单例bean的预实例化。懒加载的bean告诉ioc容器在被请求时创建bean实例,而不是启动时
xml中这种行为是bean元素lazy-init属性控制的
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
如果懒加载的bean是未被懒加载的单例bean依赖关系时,applicationcontext在启动时必须创建懒加载bean,以满足单例的依赖关系
如果一意孤行,可以配置容器级别的懒加载
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
自动注入 Autowiring Collaborators
自动注入bean可以大大减少对指定属性或构造函数参数的需要。自动注入随着你的对象发展而更新配置,如果你给类加一个依赖,这个依赖可以自动满足,不需要修改配置
用
no默认没有自动注入 ,byName通过属性名进行自动注入,byType如果容器有property类型的bean存在可以自动注入,constructor适用于构造函数参数
自动注入在整个项目中一致使用时,效果最好
从自动注入排除Bean
可以设置一个bean不能自动注入,把
Bean Scope
从特定bean定义创建的对象的scope,通过配置选择你创建的对象的scope,而不是java类级别上创建出一个对象的scope。bean可以定义为部署在若干scope的一个。spring框架支持六个scope,其中四个只有你使用web的applicationcontext时可用
singleton scope
spring单例的范围描述为每个容器和每个bean。如果你在一个容器为一个特定类定义一个bean,spring容器为该bean定义的类创建一个且只有一个实例。singleton scope是spring默认的scope,在xml把一个bean定义为singleton,加一个scope属性 scope="singleton"
prototype scope
prototype scope 导致每次对该特定bean的请求都会创建一个新的bean实例。也就是说,该bean被注入到另一个bean,或者getBean方法调用请求它,作为一项规则,对有状态的bean使用prototype scope,对无状态的bean使用singleton scope
数据访问对象dao通常不被配置为prototype,因为典型的dao不持有对话状态
与其他scope相比,spring不会管理prototype bean的完整生命周期。容器对prototype对象进行实例化,配置和其他方面的组装将其交给客户端,而对prototype实例没有进一步的记录
在某些方面,spring容器在prototype scope bean方面作用是替代java的new操作。所有超过该点的生命周期管理必须由客户端处理
singleton bean和prototype bean依赖
如果将一个prototype scope的bean依赖注入到singleton scope的bean中,一个新的prototype bean被实例化,然后被依赖注入singleton bean中。注意prototype的注入只发生一次,如果你想让singleton scope的bean在运行时反复得到prototype scope的bean新实例,这是不可取的。
Request Session Application WebSocket Scope
这些只在你使用web感知spring applicationcontext实现时才可用。如果你将这些scope与常规的spring ioc容器一起使用,抛出异常 IllegalStateException
初始web配置
为了支持bean在request session application websocket级别的scope,在你定义bean之前,需要一些小的初始配置。
这里只探究注解的使用
Request scope
使用注解驱动的组件配置,@RequestScope注解将一个组件分配给request scope
@RequestScope
@Component
public class LoginAction {
// ...
}
Session scope
使用注解驱动的组件配置,@SessionScope注解将一个组件分配给session scope
@SessionScope
@Component
public class UserPerferences {
// ...
}
Application Scope
使用注解驱动的组件配置,@Application注解将一个组件分配给application scope
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
WebServlet Scope
websocket scope于websocket会话的生命周期相关,适用于通过websocket实现的 STOMP 应用程序
作为依赖的scope bean
ioc容器不仅管理对象的实例化,而且还管理依赖的连接。如果你把一个http request scope的bean注入到另一个bean,可以选择注入一个aop代理代替这个scope的bean
自定义bean的性质
生命周期回调
@PostConstruct 和 @PreDestroy 注解通常被认为是现代spring应用程序中接收生命周期回调的最佳实践。使用注解意味着你的bean不会被耦合到spring特定接口。
初始化回调
建议使用@PostConstruct注解,xml数据在
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// ...
}
}
销毁回调
建议使用@PreDestroy注解,xml数据在
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// ...
}
}
默认初始化和销毁方法
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
顶层的
结合生命周期机制
你可以有三种方法控制bean生命周期
- 实现
InitializingBean和DisposableBean接口 - 自定义init 和 destroy 方法
- @PostConstroy 和 @PreDestroy 注解
调用顺序如下:
- 注解 2. 实现接口的方法 3. 自定义方法
基于注解的容器配置
在配置spring时,注解是否比xml更好?
视情况而定
注提供大量的上下文,导致更短更简洁的配置。xml擅长在不触及源码或重新编译情况下对组件注入
人们认为带注解的类不是POJO,难以控制
spring 适应两种风格,可以混用
通过javaconfig 可以非侵入性方式使用注解,不触及目标组件的源代码
你可以将xml配置来隐式注册
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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>
使用@Autowired
注意:JSR 330中的@Inject可以代替@Autowired
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
spring4.3 后此处构造器不需要添加@Autowired,如果有多个构造器则必须添加
可以把注解添加到bean字段上,同样适用于泛型方法注入
只要预期key类型是String,@Autowired适用于Map实例的自动注入
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默认将注解的方法和字段视为必须的依赖关系,定义注解内的属性可以改变这种行为
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
可以对常见的可解析依赖的接口自动注入,如自动注入一个 applicationcontext 对象
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {}
// ...
}
用@Resource注入
spring支持在字段方法使用 JSR-250 基于Jakartaee的 @Resource 注解,spring支持这种模式,IDEA也推荐用它
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
使用@Value
@Value通常用于外部化properties
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
catalog.name=MovieCatalog
使用@PostConstruct和@PreDestroy
这两个注解是初始化回调和销毁回调中描述生命周期回调机制提供替代方案。缓存在初始化时被预先填充,在销毁时被清除
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
Classpath扫描和管理组件
@Component和进一步的steretype注解
@Repository是一个标记,用途包括异常翻译
spring有很多stereotype注解 @Component @Service @Controller 。@Component是一个通用的steretype,适用于任何Spring管理的组件。@Repository @Service @Controller 是 @Component的特殊化,用于更具体的使用情况(持久层,服务层,表现层)。
使用原注解和组合注解
可以用通用的@Component注解组件类,通过用@Repository,@Service或@Controller 注解,你的类更适合被工具处理或与切面关联。如果你在服务层@Component 和@Service做选择,显然@Service是更优解;同理,@Repository 在持久层中自动异常翻译的标记是更优解
这几个注解可以当作元注解,与之对应的有组合注解@RestController=@Controller+@Responsebody
自动检测类和注册bean定义
spring自动检测stereotype的类,并在ApplicationContext中注册实例
以下两个类符合自动检测条件
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
为了自动检测并注册对应的类,你需要在@Configuration类加一个@ComponentScan注解,basePackages指的是上面两个类的共同父包
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
使用Filter自定义扫描
默认情况,用@Component@Repository@Service@Controller@Configuration注解的类是唯一被检测到的候选组件。可以通过自定义过滤器来修改这种行为,将他们作为@Component的includeFilters和excludeFilters属性,每个过滤器需要type和expression属性。
下表描述了过滤选项:
| 拦截器类型 | 示例表达式 | 说明 |
|---|---|---|
| 默认 | org.example.SomAnnotion | 一个注解目标组件的类型级别是present或meta-present |
| 可指定 | org.example.SomeClass | 目标组件分配给一个类 |
| aspectj | org.example..*Service+ | 目标匹配的一个表达式 |
| regex | org \ .example \ .Default* | 一个与目标类名匹配的正则表达式 |
| 自定义 | org.example.MyTypeFilter | 接口的自定义实现 |
下面的例子显示了配置忽略了所有 @Repository 注解,而使用 “stub” repository。
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
在组件定义bean元数据
spring可以向容器定义bean元数据
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
对静态的@Bean方法调用永远不会被容器拦截
@Configuration中的@Bean需要可重写的,@Bean不能被声明为private或final
Naming Autodetected Components
当一个组件作为扫描的一部分被检测到,它的bean名称自动生成小驼峰名称
@Service("simpleMovieLister")
public class SimpleMovieLister {
// ...
}
为自动检测的组件提供一个scope
和一般的spring组件一样,自动检测的组件默认和最常用的scope是singleton。然而,有时你需要不同的scope,通过@Scope注解指定
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
生成一个候选组件的索引
classpath扫描很快,通过在编译时创建一个静态的候选列表,可以提高大型应用程序的启动性能。在这种模式下,所有作为组件扫描目标的模块必须使用这种机制
maven依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>6.0.7-SNAPSHOT</version>
<optional>true</optional>
</dependency>
</dependencies>
spring使用 jsr 330标准注解
spring想要使用这类注解,需要在classpath中拥有相关的jar
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
基于java的容器配置
如何在你的java代码使用注解配置容器
@Bean 和 @Configuration
@Bean注解表示一个方法的实例化,配置和初始化一个新的对象,由容器管理,@Bean 注解的作用和
用@Configuration 注解一个类,表明他的目的是作为Bean定义的来源。允许通过调用同一个类的其他@Bean方法定义bean之间的关系
最简单的@Configuration
@Comfiguration
public class AppConfig {
@Bean
public MyServiceImpl myService() {
return new MyServiceImpl();
}
}
通过使用 AnnotationConfigApplicationContext 实例化Spring容器
这个多功能的实现不仅可以接受@Configuration类作为输入,能接受普遍@Component类和用jsr 330元数据的注解的类。当@Configuration类被提供输入,@Configuration类本身被注册为bean定义,该类所有声明的@Bean方法被注册为bean定义。当@Component 和 jsr330类被提供,他们被注册为bean定义,假定DI元数据如@Autowired或@Inject在必要时被用于这些类
简单构造
实例化 AnnotationConfigApplicationContext 用 @Configuration 类作为输入,使spring容器完全不用xml
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); // 这里AppConfig 是@Configuration 类
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
任何 @Component 和 JSR330的类都可以作为输入给构造函数
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
+++
用 scan 启动组件扫描
@Configuration
@ComponentScan(basePackages = "com.example.entity")
public class AppConfig {
// ...
}
使用@Bean注解
@Bean是一个方法级注解,支持
声明一个Bean
可以用@Bean注解来注解一个方法,用这个方法在ApplicationContext中注册一个Bean定义,该类型被指定为方法返回值。默认Bean的名字和方法的名字相同(小驼峰)
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
+++
Bean 依赖
一个@Bean注解的方法可以有任意数量的参数,描述该bean所需的依赖关系。如果TransferService需要一个AccountRepository,我们可以用一个方法参数将这种依赖关系具体化
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
接收生命周期的回调
@Bean 注解支持任意的初始化和销毁回调方法,向xml在bean元素上的init-method和destroy-method属性一样
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
//第二种方案,在构造过程中直接调用 init() 方法
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
+++
自定义bean的名称
默认用方法名称作为bean的名称,通过@Bean 的name 属性可以自定义
@Configuration
public class AppConfig {
@Bean("myThing")
public Thing thing() {
return new Thing();
}
}
+++
bean的别名
给bean起多个名字,如果你乐意
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
+++
bean描述
你可以给bean做一个解释,类似注释,用于监控目的
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
使用 @Configuration注解
这是一个类级注解(定义在类上),表示一个对象是bean定义的来源。@Configuration通过@Bean注解声明bean
注入bean间的依赖
beanOne通过构造器注入beanTwo的引用
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
如果你用@Component类,则不能实现声明bean之间的引用关系
构建基于java的配置
使用@Import注解
@Import允许从另一个配置类加载@Bean定义
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
这种方案简化容器的实例化,只需要处理一个类,不是要求记住大量的@Configuration类
你可以直接自动注入这个配置类本身,这样会是两个config造成耦合
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
+++
混合使用注解和xml
在@Configuration类是配置容器的主要机制应用中,仍然可能需要至少使用xml。你可以使用@ImportResource只定义需要的xml。以java注解为中心配置容器,是xml保持最低限度;而把xml再转向properties配置,更加简便;springboot中没有xml,就是一个解释
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
Environment 抽象
Environment 接口是一个集成在容器中的抽象,对application environment 两个方面进行建模:配置文件 profile,属性 properties
配置文件是一个命名的,逻辑的bean定义组,无论是用xml还是注解定义,bean都可以分配给一个profile。environment 对象在配置文件作用是确定哪些配置文件是当前活动的,哪些是默认活动的
与属性有关的Environment对象作用是为用户提供方便的接口,用于配置属性元并在他们那里解析属性
Bean定义配置
核心容器提供的一种机制,允许不同环境注册不同的bean。环境对不同用户意味不同的东西,这个功能帮助许多用例:
- 在开发中针对内存数据源工作
- 将应用程序部署到performance环境中时才注册监控基础设施
- 为customer A和customer B的部署注册定制的bean实现
例如 配置Database
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
+++
使用@Profile
@Profile注解让你表明当一个或多个配置文件活动时,另一个组件有资格注册,我们可以重写datasource配置
开发环境
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
生产环境
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod = "") (1)
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Profile可以在方法层面声明,包括一个配置类的一个特定bean
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development") (1)
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production") (2)
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
Resources 资源
Application Context 和 资源路径
如何用资源创建应用 application context,包括和xml一起使用的快捷方式,如何使用通配符
构建 applicationcontext
构造函数通常需要一个字符串和字符串数组作为资源的位置路径,构成context定义的xml文件
当这些路径没有前缀,从该路径建立并用于加载bean定义特定resource类型取决于适合于特定的应用环境
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
由于使用ClassPathResource,所以bean定义是从classpath加载的
加载同一目录多个文件,根据文件类型加载
com/
example/
services.xml
repositories.xml
MessengerService.class
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"services.xml", "repositories.xml"}, MessengerService.class);
applicationcontext 构造器资源路径的通配符
applicationcontext构造函数的资源路径是简单的路径,每个路径和目标一对一映射,可以包含特殊的classpath*:前缀或内部Ant风格模式,后者是有效的通配符。
classpath*:前缀
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
spring 进行面向切面编程
虽然ioc容器不依赖于aop,aop补充了ioc,提供中间件解决方案
aop在springframework的作用:
- 提供声明式的企业服务,最重要的是声明式事务
- 让用户实现自定义切面,aop补充oop的使用
AOP 概念
-
aspect 切面
-
join point 切点
-
advice 一个切面在切点的动作
-
pointcut 连接点
-
introduction 一个类型声明额外的方法或字段
-
target object 被一个或多个切面改动后的对象
-
aop proxy aop框架创建的对象
-
weaving 将切面和其他对象连接起来
aop有如下advice
- before advice 连接点前运行的advice
- after returning advice 一个连接点正常完成后运行的advice
- after advice 完成连接点后运行的advice
- around advice 围绕一个连接点的advice
around advice 是最常用的 advice
如果你需要一个方法返回值更新缓存,实现一个after returning advice,建议使用功能最少的advice类型实现所需的行为
声明一个切面 aspect
在类上加 @Aspect即可
package com.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
声明一个切点 pointcut
在方法上加 @Pointcut
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature
声明Advice
advice和一个切点表达式关联,在切点匹配方法执行前后以及环绕运行
before advice
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("execution(* com.xyz.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
after returning advice
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("execution(* com.xyz.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
afterthrowingadvice
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing("execution(* com.xyz.dao.*.*(..))")
public void doRecoveryActions() {
// ...
}
}
After (Finally) Advice
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
@After("execution(* com.xyz.dao.*.*(..))")
public void doReleaseLock() {
// ...
}
}
Around Advice
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("execution(* com.xyz..service.*.*(..))")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
advice 参数
访问jointpoint 这个接口提供很多有用的方法
- getArgs 返回方法的参数
- getThis 返回代理对象
- getTarget 返回目标对象
- toString 打印
web mvc
spring web mvc是构建servlet api上的web框架,一开始嵌入在spring framework中,后来命名springwebmvc,通常称为springmvc
spring webflux 与 springmvc是并行关系,主流应该还是 springmvc
DispatcherServlet
和许多其他web框架一样,mvc围绕前端控制器设计的,中央servlet为dispatcherServlet请求处理提供共享算法,实际工作由可配置的组件执行
和dispatcherservlet一样的servlet,需要根据servlet规范使用java配置或在web.xml,反过来,使用spring配置发现请求映射,视图解析,异常处理等dispatcherservlet,由servlet容器自动检测
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
拦截器
@HandlerMapping 实现支持处理程序拦截器,使用三种方法从 org.springframework.web.servlet 包实现,三种方法提供足够的灵活性进行预处理和后处理:
- preHandle 运行实际处理程序前
- postHandle 处理程序运行后
- afterConpletion 完整请求完成后
preHandle 返回一个布尔值,返回true,处理程序执行链继续,返回false,假定拦截器处理了请求,并且不会继续执行其他拦截器和执行链中的实际处理程序
三种方法中preHandle 应该是最有用的,拦截的最早;而存在则合理,不同的方法使用场景是复杂的postHandler 对于before 中编写和提交相应的方法@ResponseBody来说,已经拦截的太晚了
容器错误页面
如果异常没有被解决,或者响应状态设置为错误状态 4xx 5xx ,servlet 容器会在html呈现默认的错误页面,要自定义容器默认错误页面,可以在web.xml配置
<error-page>
<location>/error</location>
</error-page>
如果异常冒泡或响应具有错误状态,servlet 容器在容器内配置url发出错误分派。然后由dispatcherservlet 映射成 @Controller ,实现返回带有模型的错误视图名称或呈现json响应
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", request.getAttribute("jakarta.servlet.error.status_code"));
map.put("reason", request.getAttribute("jakarta.servlet.error.message"));
return map;
}
}
重定向
视图名称中特殊 redirect: 前缀可让您执行重定向,识别成需要重定向的指令,视图名称的其余部分是重定向URL。最终效果与控制器返回效果相同的RedirectView,现在控制器本身可以根据逻辑视图名称名称操作。逻辑视图名称(例如redirect:/myapp/some/resource)相对于当前servlet上下文重定向,而名称 redirect:https://myhost.com/some/arbitrary/path 会重定向到绝对URL
请求转发
forward:以为最终由子类解析的视图名称使用特殊前缀urlbasedviewresolver,创建一个internalresourceview,它执行一个 requestdispatcher.forward() 前缀对JSP不起作用,如果你使用另一种视图技术强制转发资源,他会很有用
语言环境
时区
TimeZone 可用时,使用RequestContext.getTimeZone() 方法获取用户时区
Cookie 解析器
语言环境解析器 检查 Cookie 客户端,通过使用此区域设置解析器的属性,指定cookie名称以及最长期限
Session 解析器
sessionLocalResolver 使你可以从与用户请求关联的会话检索,本地区域设置存储在Servlet容器的HttpSession,这些设置时临时的,在每个会话结束后丢失
注释控制器
mvc 提供基于注解的模型 @Controller 组件 @RestController 使用注解表达请求映射,请求输入,异常处理等。注释控制器有灵活的方法签名,不必扩展基类或实现接口
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model){
model.addAttribute("message", "hello world");
return "index";
}
}
declaration
通过servlet的WebApplicationContext,构造@Contoller自动检测,与spring检测@Component类路径的类和自动注册bean定义一样
要启动这类bean的自动检测@Controller,可以将组件扫描添加到java配置中
@Configuration
@ComponentScan("org.example.web")
public class WebConfig {
// ...
}
@RestController 是一个组合注解,本身用元注解@Controller和@ResponseBody指示一个控制器,每个方法继承@ResponseBody,直接写入相应主题,而不是视图解析和使用html模板呈现
AOP 代理
运行时使用 aop代理装饰控制器,如果控制器实现一个接口,需要aop代理,需要显式配置基于类的代理,@EnableTransactionManagement可以改成@EnableTransactionManagement(proxyTargetClass = true)
请求映射
使用 @RequestMapping 注释将请求映射到控制器方法,具有各种属性,通过URL,HTTP方法请求参数,标头匹配
@RequestMapping 的变体有 @GetMapping @PostMapping @PutMapping @DeleteMapping @PatchMapping 大多数控制器应该映射到HTTP方法的@RequestMapping,默认和所有http方法匹配
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
URI 模式
@RequestMapping 使用URL模式映射方法,有两种选择
- PathPattern 与URL路径匹配,有效处理编码和路径参数
- AntPathMatcher 字符串模式和字符串路径匹配,效率低下
PathPattern 是web应用的推荐方案,spring6默认启用
支持通配符,{*spring},?,** 等
- "/resources/ima?e.png" 匹配路径?处可以是任意一个字符
- “/resources/*.png” *匹配路径段零个或多个字符
- “/resources/{project}/versions" 匹配路径并将其捕获为变量
- "/projects/{project:[a-z]+}/versions" 可以使用正则匹配
你可以在类和方法级别声明URI变量
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GetMapping("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
URI 变量会自动转换为适当的类型,默认支持基本类型, 可以省去 @ParhVariable
后缀匹配
5.3 后,mvc不在使用.*匹配,而是默认映射,如/person 默认映射 /person. * ,路径扩展不再用于解释相应的请求内容类型
当浏览器发送难以一致解释的标头时,这种方式使用文件扩展名是必要的
控制请求映射范围
使用 produces 缩小映射范围
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
根据参数params 条件缩小请求映射范围,测试 请求参数是否存在 myParam ,请求参数是否存在 !myparam 或特定值 myParam=value
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") (1)
public void findPet(@PathVariable String petId) {
// ...
}
使用请求标头条件 headers
@GetMapping(path="/pets", headers="myHeader=myValue")
public void findPet(@PathVariable String petId){
// ...
}
http 头,选项
@GetMapping (@RequestMapping(method=HttpMethod.GET)) 支持HTTP HEAD进行请求映射,控制器方法不需要改变
@RequestMapping 没有HTTP方法声明,Allow 标头设置 GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS ,控制器方法声明支持HTTP方法
显式注册
使用注解处理程序,用于动态注册或高级情况
@Configuration
public class MyConfig {
@Autowired
public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) // 为控制器注入目标处理程序和处理程序映射
throws NoSuchMethodException {
RequestMappingInfo info = RequestMappingInfo
.path("/user/{id}").methods(RequestMethod.GET).build(); // 准备请求映射元数据
Method method = UserHandler.class.getMethod("getUser", Long.class);// 获取处理程序方法
mapping.registerMapping(info, handler, method); // 添加注册
}
}
处理程序方法
@RequestParam
使用 @RequestParam 注释把 servlet 请求参数(查询参数或表单数据)绑定到控制器的方法参数
@Controller
@RequestMapping("/pets")
public class EditPetForm {
// ...
@GetMapping
public String setupForm(@RequestParam("petId") int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
}
@RequestParam 默认使用此注释的参数是必需的,你可以通过将注释的required 标志设置为 false,通过使用包装类声明参数指定方法参数可选
如果没有这个注解,任何其他参数解析器解析会被视为带有注释 @RequestParam
@RequestHeader
用@RequestHeader注释将请求标头绑定到控制器的方法参数
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding, (1)获取标头Accept-Encoding的值
@RequestHeader("Keep-Alive") long keepAlive) { (2)获取标头Keep-Alive的值
//...
}
@CookieValue
把cookie的值绑定到控制器的方法参数
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
// ...
}
如果目标方法参数类型不是String ,会自动转换
@ModelAttribute
使用 @ModelAttribute 方法参数注释访问模型属性,在不存在时将其实例化。模型属性覆盖servlet 请求参数的值,这些参数的名称和字段名称匹配,这称为 数据绑定,使你不必处理解析和转换单个查询参数和表单字段的问题
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { (1)绑定一个实例 Pet
// method logic...
}
@SessionAttributes
用于在请求之间的Servlet 会话中存储模型属性。是一个类型级别的注释,用于声明特定控制器使用的会话属性
@Controller
@SessionAttributes("pet")
public class EditPerform {
//...
}
这个请求的模型属性添加到模型,自动提升并保存在http servlet会话中。一直保留,直到另一个控制器方法使用sessionstatus方法参数清除存储
@Controller
@SessionAttributes("pet") (1) 存储Pet值到会话
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors) {
// ...
}
status.setComplete(); (2)在会话清除Pet值
// ...
}
}
@SessionAttribute
如果你需要访问全局会话属性,在方法参数使用注解
@RequestMapping("/")
public String handle(@SessionAttribute User user) { (1)
// ...
}
@`RequestAttribute 和 @SessionAttribute 等价
重定向属性
默认所有属性在重定向url公开为uri的模板变量,其余属性中,基本类型或基本类型的集合或数组自动附加为查询参数
模型专门为重定向准备,将原始类型属性附加为查询参数可能时所需的结果。带注释的控制器中,模型可以包含为渲染目的添加的附加属性。避免此类属性出现在url中,@RequestMapping声明参数RedirectAttributes并使用他指定提供给RedirectView,RedirectAttributes,如果方法重定向,使用内容,否则使用模型内容
@PostMapping("/files/{path}")
public String upload(...) {
// ...
return "redirect:files/{path}";
}
@RequestBody
使用 @ReqeustBody 注解让请求主体 Object 通过HttpMessageConverter
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
//...
}
使用mvc配置或自定义消息转换
@ResponseBody
使用 @ResponseBody 方法注释将返回序列化到响应主体
@GetMapping("/accounts/{id}")
@ReponseBody
public Account handle(){
// ...
}
可以把 @ResponseBody 和jSON序列化视图结合使用
JSON
mvc 支持 JSON序列化,允许只呈现 Object,@ResponseBody 将他和控制器方法一起使用 ResponseEntity
模型
使用 @ModelAttribute 注释:
- 在方法中把方法参数从模型@RequestMapping 创建或绑定到请求
- @Controller 作为类中方法级注释,@ControllerAdvice 有助于任何 @RequestMapping 方法调用之前初始化模型
- 一个 @RequestMapping 方法标记返回值是一个模型属性
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
}
拦截器
注册拦截器以应用于传入请求
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleChangeInterceptor());
registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
}
}
Security
spring security 是一个提供身份验证,授权,针对常见攻击的保护的框架。凭借对保护命令式应用程序和反应式应用程序的一流支持,它是保护基于spring应用程序的事实标准
先决条件
由于springsecurity以自包含的方式运行,无需在java运行环境放置任何特殊的配置文件,不需要把特殊的java身份验证和授权服务策略文件或将spring security放入公共路径位置
所有必须的文件都包含在你的文件中,这种设计提供了最大的部署时间灵活性,您可以将目标文件(jar,war,ear)从一个系统复制到另一个系统,他会立即运行
security 6.0 新特性
注意:需要jdk17
spring boot 3.0 搭配 spring 6.0
将javax导入更改为jakarta导入
servlet 迁移
配置类这样配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception {
http
// ...
.rememberMe((remember) -> remember
.rememberMeServices(rememberMeServices)
);
return http.build();
}
@Bean
RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
return new TokenBasedRememberMeServices(myKey, userDetailsService);
}
}
获取
springboot提供了starter聚合了springsecurity相关依赖项的启动器
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
如果不是springboot项目,也有对应的依赖
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
</dependencies>
验证
security提供了全面的身份验证支持,是我们验证访问特定资源的身份的方式。验证用户的身份的常用方法是要求用户输入用户名和密码,一旦执行身份验证,我们知道身份并可以执行授权
密码存储
springsecurity 引进了DelegatingPasswordEncoder通过以下方式解决所有问题:
- 确保使用当前密码存储建议对密码进行编码
- 允许验证现代和传统格式的密码
- 允许将来升级编码
Spring Boot
springboot 是一套开源框架,简化spring应用的创建和部署。提供了丰富的spring模块化支持,帮助开发者轻松快捷构建企业级应用,springboot自动配置功能,降低了复杂性,支持基于jvm的多种开源框架,缩短开发时间,开发更简单高效
入门
介绍
springboot帮助你创建可以运行的独立的基于spring的生产级应用程序。对spring平台和第三方库采取主见的观点,以最少的麻烦开始工作。大多数springboot只会使用极少的spring配置。使用boot创建java应用程序,使用jar和war部署启动,我们的目标是:
- 所有spring开发提供一个根本性的更快更广泛的入门体验
- 开箱即用,随着需求开始偏离默认值,迅速摆脱困境
- 提供一系列大类项目常见的非功能特性
- 没有代码生成,不要求xml配置
系统要求
java 17 springframework 6.0.5 及以上版本
servlet容器:tomcat 10
安装 boot
java sdk 17
boot不需要工具集成,可以使用任何ide和文本编译器,可以像运行其他java程序一样运行和调试boot应用程序,建议使用maven
开发第一个boot应用程序
创建pom
创建maven的pom.xml文件,pom.xml 用于项目构建的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<!-- 将在此添加其他行... -->
<!-- ((只有当你使用 milestone 或 snapshot 版本时,你才需要这个。)) -->
<repositories>
<repository>
<id>spring-snapshots</id>
<url>https://repo.spring.io/snapshot</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<url>https://repo.spring.io/snapshot</url>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
</project>
添加依赖
在parent添加web依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
编写代码
添加一个src/main/java/MyApplication.java文件
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class MyApplication {
@RequestMapping("/")
String home() {
return "Hello World!";
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
main 方法
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
这是一个标准的java入口方法,是应用的启动方法,main下的run方法,把应用委托给boot的springapplication类,引导我们的应用程序启动spring,而spring会启动自动配置的tomcat网络服务器,将MyApplication.class作为参数传递给run方法,告诉springapplication哪个是spring组件,args数组也被传入,这是命令行参数
运行 Example
由于使用了 spring-boot-starter-parent 的 POM,所以你有一个有用的 run target,可以用它启动该应用程序。
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.0-SNAPSHOT)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.906 seconds (process running for 6.514)
打开浏览器,访问 localhost:8080 输出
Hello World!
可以 ctrl - c 优雅的退出程序
创建一个jar
在pom
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
用maven打包即可得到jar包,java -jar 可以运行
$ java -jar target/myproject-0.0.1-SNAPSHOT.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.0-SNAPSHOT)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 2.536 seconds (process running for 2.864)
可以 ctrl - c 优雅的退出程序.
使用boot开发
虽然boot没有什么特别之处,遵循一些官方建议,可以使你开发过程更容易
构建系统
starter 是一系列开箱即用的依赖,可以在你的应用程序导入,通过你的starter,可以获得你需要的spring和相关技术的一站式服务,免去了需要到处大量复制粘贴依赖的烦恼,例如你想用spring和jpa进行数据库访问,那么可以直接在你的项目中导入spring-boot-starter-data-jpa 依赖
starter包含很多你需要的依赖,使得项目快速启动,拥有一套一致的,受支持的可管理的过渡性依赖
关于Starter的命名
所有官方的starter遵循一个类似的命名模式;spring-boot-starter-*,其中 * 是一个特定类型的应用程序。第三方启动器不能用spring-boot开头,留给springboot官方组件,相反第三方启动器通常以项目名称开始的
开发结构
导入 Configuration 类,不必把所有 @Configuration 放在一个类中,@Import可以把配置导入其他配置类;可以用@ComponentScan自动扫描加载所有的spring组件,包括@Configuration类
自动装配
boot的自动装配机制会试图根据你所添加的依赖自动配置你的spring应用程序,例如如果你添加了HSQLDB依赖,而且你没有手动配置任何DataSource Bean,那么boot就会自动配置内存数据库
如果你将@EnableAutoConfiguration和@SpringBootApplication加到@Configuration类上,开启自动配置功能
禁用自动装配类
在@SpringBootApplication注解的exclude属性中指定
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication {
// ...
}
bean 和依赖导入
使用spring技术定义bean以及依赖注入关系,建议使用构造函数注入,使用@ComponentScan 注解来扫描bean
在启动类添加 @ComponentScan 注解,不需要定义它的任何参数,你的所有应用组件(@Component @Service @Repository @Controller 其他)会自动注册为spring bean
@Service
public class MyAccountService implements AccountService {
private final RiskAssessor riskAssessor;
public MyAccountService(RiskAssessor riskAssessor) {
this.riskAssessor = riskAssessor;
}
// ...
}
如果bean有多个构造函数,你需要用@Autowired注解告诉spring用哪一个构造函数注入
import java.io.PrintStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MyAccountService implements AccountService {
private final RiskAssessor riskAssessor;
private final PrintStream out;
@Autowired
public MyAccountService(RiskAssessor riskAssessor) {
this.riskAssessor = riskAssessor;
this.out = System.out;
}
public MyAccountService(RiskAssessor riskAssessor, PrintStream out) {
this.riskAssessor = riskAssessor;
this.out = out;
}
// ...
}
使用 @SpringBootApplication
boot开发者希望应用程序使用自动配置,组件扫描,并定义额外的配置
类似 lombok 的 @Data,@SpringBootApplication 启用了3个功能:
- @EnableAutoConfiguration 启用boot自动配置机制
- @ComponentScan 对应程序包启用@Component 扫描
- @SpringBootConfiguration 允许 Context 注册额外的bean或导入额外的配置类
这是@Configuration的替代方案
@SpringBootApplication
public class MyApplication {
public void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
运行
把应用打包可执行jar,使用嵌入式HTTP服务器最大优势,就是可以像其他程序一样运行程序
开发者工具
热部署,系统代理运行
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
web
静态内容
默认情况,boot从classpath中的/static 目录提供静态内容,它使用了mvc的ResourceHttpRequestHandler,可以通过添加自己的WebMvcConfigurer和覆盖addResourceHandlers方法修改行为,独立的web应用程序中,来自容器默认servlet没有被启用,使用server.servlet.register-default-servlet 属性来启用
默认情况,资源映射到 /** 可以通过 spring.mvc.static-path-pattern 属性调整,将所有资源重新定位到 /resources/** 可以通过以下方式实现
spring.mvc.static-path-pattern=/resources/**
欢迎页面
boot支持静态和模板化的欢迎页面,首先在配置的静态内容位置寻找一个index.html文件,没有找到,寻找index模块,如果找到了其中之一,它会自动作为应用程序的欢迎页面使用
自定义 Favicon
与其他静态资源一样,boot检查配置的静态内容位置是否有 favicon.ico 如果存在这样的文件,就会自动作为应用程序的favicon
路径匹配的内容协商
mvc通过查看请求路径并将其与你的应用程序定义的映射匹配,将传入的http请求映射到处理程序
boot默认选择禁用后缀模式匹配,意味着"GET/project/spring-boot.json"这样的请求不会被匹配到@GetMapping("/projects/spring-boot")映射
security
security 在 classpath上,那么web应用程序默认是安全的,boot依靠security内容协商策略决定是使用httpBasic还是formLogin,给web应用添加方法级安全,添加 @EnableGlobalMethodSecurity 加上你想要的设置
默认的 UserDetailsService 有一个用户,用户名 user,密码随机
Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35
This generated password is for development use only. Your security configuration must be updated before running your application in production.
可以提供 spring.security.user.name 和 spring.security.user.password 改变用户名和密码
web应用默认提供特性:
- UserDetailsService bean,具有内存存储和一个生成密码的单一用户
- 整个应用程序的基于表单登录和HTTP Basic安全
- 一个DefaultAuthenticationEventPublisher用于发布认证事件
Data
boot 集成了许多data技术,包括SQL和NoSQL
SQL数据库
配置DataSource
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
数据库配置
在application.properties中声明
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
支持的连接池
boot有以下算法选择特定的实现
- HikariCP 的性能和并发性是最优解
- 如果 Tomcat DataSource可用,也可以用它
- 考虑 Commons DBCP2
- 考虑Oracle UCP
如果你使用 sping-boot-starter-jdbc 或 spring-boot-starter-data-jpa starter 自动依赖 HikariCP
JPA 和 Spring Data JPA
spring-boot-starter-data-jpa pom提供一个快速入门的方法,提供以下依赖:
- Hibernate:最流行的JPA实现
- Spring Data JPA:帮助你实现基于JPA的Repository
- Spring ORM:Spring框架核心ORM支持
创建和删除JPA数据库
默认情况,当你使用嵌入式数据库时,才会自动创建JPA数据库
NOSQL技术
springdata提供额外项目,帮你访问如下的NOSQL技术
Redis
redis 是一个缓存消息代理功能丰富的kv数据库,boot为redis提供基本的自动配置
连接 Redis
@Component
public class MyBean {
private final StringRedisTemplate template;
public MyBean(StringRedisTemple template) {
this.template = template;
}
// ...
}
默认情况,实例尝试连接 localhost:6379 的 Redis 服务器,使用 spring.data.redis.* 属性指定自定义的连接细节
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.database=0
spring.data.redis.username=user
spring.data.redis.password=secret
本文来自博客园,作者:fgj456,转载请注明原文链接:https://www.cnblogs.com/ffgj/p/17259482.html

浙公网安备 33010602011771号