Spring基础
Spring 常见注解
声明Bean的注解
@Component
:该注解是一个泛华的概念,仅仅表示一个组件对象(Bean),可以作用在任何层次上,没有明确角色@Repository
:该注解用于将数据访问层(DAO)的类表示为Bean。即数据访问层Bean,功能与@Component相同@Service
:该注解用于标注一个业务逻辑组件类(Service层),功能与@Component相同@Controller
:该注解用于标注一个控制其组件类(Spring MVC的Controller)功能与@Component相同
注入Bean的注解
-
@Autowired
:该注解可以对类成员变量,方法及构造方法进行标注,完成自动装配工作。通过@Autowired的使用来消除 setter 和 getter 方法。默认按照Bean的类型进行装配 -
@Resource
:该注解与@Autowired 功能一样。区别在于,该注解默认是按照名称来装配注入的,只有找不到与名称匹配的Bean才会按照类型来装配注入;而@Autowired默认按照Bean的类型进行装配,如果按照名称来装配注入,需要结合@Qualifier注解一起使用@Resourece 注解有两个属性:name和type。name属性指定Bean的实例名称,即按照名称来装配注入;type属性指定Bean类型,即按照Bean的类型进行装配
-
@Qualifier
该注解与@Autowired注解配合使用。当@Autowired注解需要按照名称来装配注入,则需要结合该注解一起使用,Bean的实例名称由@Qualify注解的参数指定
Spring部分
Javaweb层次结构
在 Java Web 开发中,通常会将应用程序划分为三个层次:表示层
、业务逻辑层
和数据访问层
。其中,数据访问层负责与数据库进行交互,执行数据的增删改查等操作。而在数据访问层中,通常会使用一些 ORM 框架,如 MyBatis、Hibernate 等,来简化与数据库的交互操作。
一、三层结构
控制器层(Controller)
控制器层是应用程序的第一层,它负责处理用户请求并作出相应的响应。它通常是与Web框架紧密集成的,并将请求转发给服务层来处理。控制器层的主要职责是:
- 接收和验证用户请求
- 调用服务层来处理业务逻辑
- 将结果返回给用户
服务层(Service)
服务层通常包含应用程序的业务逻辑和数据处理。它是控制器层和数据访问层之间的中间层。它的主要职责是:
- 处理业务逻辑
- 访问数据访问层来获取和存储数据
- 提供数据访问层和控制器层之间的接口
服务层还可以包括一些其他的功能,例如验证和授权等。
数据访问层(Dao)
数据访问层是应用程序与数据库之间的接口。它负责执行所有与数据库相关的操作。数据访问层的主要职责是:
- 提供数据库的CRUD操作
- 将数据存储到数据库中或从数据库中检索数据
- 处理所有与数据库相关的异常和错误
二、Spring FrameWork系统架构
核心容器概念
若是使用new来创建对象当数据层发生改变的时候,业务层的new也要重新书写。
- 代码书写现状:
- 耦合度高
- 解决方案:
- 使用对象时,在程序中不要主动使用 new 创建对象,转换为由外部提供对象
-
I
O
C
(
I
n
v
e
r
s
i
o
n
o
f
C
o
n
t
r
o
l
)
IOC(Inversion of Control)
IOC(InversionofControl)控制反转
- 对象的创建控制权由程序转移到外部,这种思想称为控制反转
IOC控制反转
使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转
Spring技术对IOC思想进行了实现
Spring提供了一个容器,称为IOC容器,用来充当IOC思想中的外部
D I ( D e p e n d e n c y I n j e c t i o n ) DI(Dependency\ Injection) DI(Dependency Injection)依赖注入
在容器中建立bean和bean之间的依赖关系的整个过程,称为依赖注入
- 目标:充分解耦
- 使用 IOC 容器管理 b e a n ( I O C ) bean(IOC) bean(IOC)
- 在IoC容器内将有依赖关系的bean进行关系绑定 ( D I ) (DI) (DI)
- 最终效果:
- 使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有依赖关系
容器基本操作
Ioc入门案例问题分析
- 管理什么?【Service和Dao】
- 如何将被管理的对象告知IoC容器?【配置】
- 被管理的对象交给IoC容器,如何获取IoC容器? 【接口】
- IoC容器得到后,如何从容器中获取Bean?【接口方法】
- 想要使用Spring技术,需要导入什么坐标?【pom.xml文件】
IOC入门案例
- 导入Spring坐标【导入坐标才能使用Spring技术】
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
- 定义Spring管理的类(接口)
public interface BookService{
public void save();
}
public class BookServiceImpl implements BookService{
private BookDao bookDao = new BookDaoImpl();
public void save(){
bookDao.save();
}
}
- 创建Spring配置文件,配置对应类作为Spring管理的Bean
<?xml version="1.0" encoding="UTF-8">
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns="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标签表示配置bean -->
<!-- id属性表示给bean起名字 -->
<!-- class属性表示给bean定义类型 -->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"></bean>
</beans>
bean定义id属性在同一个上下文不能重复
- 初始化IOC容器 ( S p r i g 核心容器 / S p r i n g 容器 ) ,通过容器获取 B e a n (Sprig 核心容器/Spring 容器),通过容器获取Bean (Sprig核心容器/Spring容器),通过容器获取Bean
public class App{
public static void main(String[] args){
//加载配置文件得到上下文对象,也就是容器对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取资源
BookService bookService = (BookService) ctx.getBean("bookService"); bookService.save();
}
}
DI入门案例问题分析
- 基于IoC管理bean
- Service中使用new形式创建Dao对象是否保留?【否】
- Service中需要的Dao对象如何进入到Service中?【提供方法】
- Service与Dao间的关系如何描述?【配置】
DI入门案例
- 删除使用new的形式创建对象的代码
public class BookServiceImpl implements BookService{
private BookDao bookDao; // = new BookDaoImpl()
public void save(){
bookDao.save();
}
}
- 提供依赖对象对应的setter方法
public class BookServiceImpl implements BookService{
private BookDao bookDao;
public void save(){
bookDao.save();
}
public void setBookDao(BookDao bookDao){
this.bookDao = bookDao;
}
}
- 配置service与dao之间的关系
<?xml version="1.0" encoding="UTF-8">
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- property标签表示配置当前bean的属性 -->
<!-- name属性表示配置哪一个具体的属性 -->
<!-- ref属性表示参照哪一个bean -->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" class="com.itheima.di.dao.impl.BookDaoIml"/>
</beans>
bean基础配置
在XML文件的Spring配置中,标签用于定义一个Bean对象,其中的标签用于指定Bean对象的属性。通过标签可以为Bean对象的属性注入值或者其他Bean对象的引用,从而实现对象之间的依赖关系。
<bean>
<property name="" ref=""></property> //形成bean-bean依赖关系使用
<property name="" value=""></property> //形成bean-基本变量的关系使用
</bean>
标签的属性包括name和value(或ref)。其中,name属性用于指定要注入的属性名称,value属性用于指定属性的值,ref属性用于指定要注入的对象引用的名称。在注入对象引用时,需要在XML文件中定义被引用的Bean对象,并使用其ID作为ref属性的值。
类别 | 描述 |
---|---|
名称 | bean |
类别 | 标签 |
所属 | beans标签 |
功能 | 定义Spring核心容器管理的对象 |
格式 | |
属性列表 | id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一 class:bean的类型,即配置的bean的全路径类名 |
范例 |
bean的别名配置
类别 | 描述 |
---|---|
名称 | name |
类型 | 属性 |
所属 | bean标签 |
功能 | 定义bean的别名,可以定义多个,使用逗号(,)分号(;)空格( )分割 |
范例 |
注意事项:
获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常
NoSuchBeanDefinitionException
bean 作用范围配置
类别 | 描述 |
---|---|
名称 | scope |
类型 | 属性 |
所属 | bean标签 |
功能 | 定义bean的作用范围可选范围如下: singleton:单例(默认) prototype:非单例 |
范例 |
bean作用范围说明
- 为什么 bean 默认为单例(即使用的对象为同一个)
- 适合交给容器进行管理的bean
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
- 不适合交给容器进行管理的bean
- 封装实体的域对象
bean的实例化(实例化bean的三种方式)
bean的本质就是对象,创建bean使用构造方法完成
第一种:构造方法【常用】
- 提供可访问的构造方法
public class BookDaoImpl implements BookDao{
public BookDaoImpl(){
System.out.println("book constructor is running....");
}
public void save(){
System.out.println("book dao save...");
}
}
- 配置
<bean
id="bookDao"
class="com.itheima.dao.impl.BookDaoImpl"
/>
- 无参构造如果不存在,将抛出异常
BeanCreateException
第二种:静态工厂【了解】
- 使用静态工厂实例化bean
-
静态工厂
public class OrderDaoFactory{ public static OrderDao getOrderDao(){ return new OrderDaoImpl; } }
-
配置
<bean id="orderDao" factory-method="getOrderDao" class="com.itheima.factory.OrderDaoFactory"/>
第三种:实例工厂【了解】
- 使用实例化工厂实例化bean
-
实例工厂
public class UserDaoFactory{ public UserDao getUserDao(){ return new UserDaoImpl(); } }
-
配置
<!-- 因为工厂不是使用static了需要创建工厂对象因此先创建工厂的bean --> <bean id="userDaoFactory" class="com.itheima.factory.UserDaoFactory" /> <bean id="userDao" factory-method="getUserDao" factory-bean="userDaoFactroy" />
第四种:第三种的变式(升级改进)【实用】
- 使用
FactoryBean
实例化bean
//在UserDaoFactoryBean.java文件中
public class UserDaoFactoryBean implements FactoryBean<UserDao>{
public UserDao getObject() throws Exception{
return new UserDaoImpl();
}
public Class<?> getObjectType(){
return UserDao.class;
}
}
- 配置
<bean
id="userDao"
class="com.itheima.factory.UserDaoFactoryBean"
/>
bean的生命周期
第一种(自定义初始以及销毁的函数名称)
- 提供生命周期控制方法
public class BookDaoImpl implements BookDao{
public void save(){
System.out.println("book dao save...");
}
public void init(){
System.out.println("book init...");
}
public void destory(){
System.out.println("book destory ...");
}
}
- 配置生命周期的方法
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destory-method="destory"></bean>
第二种(使用spring提供的方法接口)
public class BookServiceImpl implements BookService, InitializingBean,DisposableBean{
public void save(){
System.out.println("book dao save...");
}
public void afterPropertiesSet() throws Exception{
System.out.println("spring 自带的初始函数接口实现");
}
public void destory() throws Exception{
System.out.println("spring 自带的销毁函数接口实现");
}
}
完整生命周期
- 初始化容器
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
- 使用bean
- 执行业务操作
- 关闭/销毁容器
- 执行bean销毁方法
bean的销毁时机
-
容器关闭前触发bean的销毁
-
关闭容器的方式:
-
手工关闭容器
ConfigurableApplicationContext接口close()
操作 -
注册关闭钩子,在虚拟机退出之前先关闭容器在退出虚拟机
ConfigurableApplicationContext接口
registerShutdownHook()
操作
public class AppForLifeCycle{ public static void main(String[] args){ ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); ctx.close(); } }
-
三、依赖注入
- 思考:向一个类中传递数据的方式有几种?
- 普通方法(set方法)
- 构造方法
- 思考:依赖注入描述了在容器中建立bean和bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?
- 引用类型
- 简单类型(基本数据类型与String)
- 依赖注入方式
- setter注入
- 简单类型
- 引用类型
- 构造器注入
- 简单类型
- 引用类型
- setter注入
setter注入————引用类型(bean类)
- 在bean中定义引用类型属性并提供可访问的set方法
public class BookServiceImpl implements BookService{
private BookDao bookDao;
public void setBookDao(BookDao bookDao){
this.bookDao = bookDao;
}
}
- 配置中使用property标签ref属性注入引用类型对象【前提:该应用对象的bean也创建了】
<bean id="bookDao" class="com.itheima.dap.impl.BookDaoImpl" />
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao" />
</bean>
setter注入————简单类型(基本变量)
- 在bean中定义引用类型属性并提供可访问的set方法
public class BookDaoImpl implements BookDao{
private int connectionNumber;
public void setConnectionNumber(int connectionNumber){
this.connectionNumber = connectionNumber;
}
}
- 配置中使用property标签value属性注入简单类型数据
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<property name="connectionNumber" value="10" />
</bean>
构造器注入————引用类型(bean类)
- 在bean中定义引用类型属性并提供可访问的构造方法
public class BookServiceImpl implements BookService{
private BookDao bookDao;
public BookServiceImpl(BookDao bookDao){ //含参的构造函数
this.bookDao = bookDao;
}
}
- 配置中使用constructor-arg标签ref属性注入引用类型对象
<bean id="bookDao" class="com.itheima.dap.impl.BookDaoImpl" />
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao" /> //此处的name是构造函数中参数的名字
</bean>
构造器注入————简单类型(基本变量)
- 在bean中定义简单类型属性并提供可访问的构造方法
public class BookServiceImpl implements BookService{
private BookDao bookDao;
String prop_string;
int prop_int;
public BookServiceImpl(String prop_string,int prop_int){
this.prop_string = prop_string;
this.prop_int = prop_int;
}
}
配置中使用constructor-arg标签value属性注入引用类型对象
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="prop_string" value="string类型参数传值" />
<constructor-arg name="prop_int" value="int类型参数传值" />
</bean>
构造器注入————参数适配
- 配置中使用**constructor-arg标签name属性设置按形参名字**注入
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="prop_string" value="string类型参数传值" />
<constructor-arg name="prop_int" value="int类型参数传值" />
</bean>
- 配置中使用**constructor-arg标签type属性设置按形参类型**注入
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg type="java.lang.String" value="string类型参数传值" />
<constructor-arg type="int" value="int类型参数传值" />
</bean>
- 配置中使用**constructor-arg标签index属性设置按形参位置**注入
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg index="0" value="int类型参数传值" />
<constructor-arg index="1" value="String类型参数传值" />
</bean>
依赖注入方式选择
- 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
- 可选依赖使用setter注入进行,灵活性强
- Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
- 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
- 实际开发过程中根据实际情况分析,若受控对象没有提供setter方法就必须使用构造器注入
- 自己开发的模块推荐使用setter注入
依赖自动装配
-
IOC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
-
自动装配方式
- 按类型
- 配置中使用bean标签autowired属性设置自动装配的类型
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" /> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowired:"byType" />
- 按名称
- 配置中使用bean标签autowired属性设置自动装配的名字
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" /> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowired:"byName" />
- 按构造方法
- 不推荐
依赖自动装配特征
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时 ( b y T y p e ) (byType) (byType)必须保障容器中具有相同类型的bean唯一,推荐使用
- 使用按名称装配时 ( b y N a m e ) (byName) (byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失败
集合注入
通用格式:注入集合对象
可使用类型:{array,List,Set,Map,properties}
<property name="properties">
<类型>
<value></value> //传递简单数据类型使用value
<value></value>
<value></value>
<ref bean="" /> //复杂类型还是使用ref
<ref bean="" />
</类型>
</property>
<!-- 比如 -->
<property name="array">
<array>
<value>100</value>
<value>200</value>
</array>
</property>
<!-- map特殊一点 -->
<property name="map">
<map>
<entry key="" value=""/>
</map>
</property>
案例:数据源对象管理
-
在pom.xml中添加依赖项,导入坐标【druid + mysql】
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>版本号</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>版本号</version> </dependency>
-
配置数据源对象作为spring管理的bean
<!-- 管理DruidDataSource对象 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/数据库name"/> <property name="username" value="root" /> <property name="password" value="123456" /> </bean>
加载properties文件
在.properties
文件中
jdbc.driver=com.mysql.jdbcDriver
jdbc.url=jdbc.mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=123456
- 开启context命名空间
- 使用context命名空间(自定义:不一定叫context),加载指定的
properties
文件
<context:property-placeholder location="jdbc.properties"/>
- 使用**$${}$**读取加载的属性值
<property name="username" value="${jdbc.username}"/>
- 不加载系统属性
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
- 加载多个properties文件(在location属性中用逗号分隔)
<context:property-placeholder location="jdbc.properties, msg.properties"/>
- 加载所有properties文件
<context:property-placeholder location="*.properties"/>
- 加载properties文件的标准格式
<context:property-placeholder location="classpath:*.properties"/>
- 从类路径或jar包中搜索并加载properties文件
<context:property-placeholder location="classpath*:*.properties"/>
四、回顾与细节
创建容器
- 方式一:类路径加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
- 方式二:文件路径加载配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");
- 加载多个配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml","bean2.xml");
获取bean
- 方式一:使用bean名称查找
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
- 方式二:使用bean名称并且指定类型
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
- 方式三:使用bean类型获取
BookDao bookDao = ctx.getBean(BookDao.class);
使用第三种方式按类型查找的时候,配置中查找的类型必须唯一否则报错。
bean的管理
-
默认情况下,Spring项目启动时,会把bean都创建好放在IOC容器中,如果想要主动获取这些bean,可以通过如下方式
-
根据name获取bean
Object getBean(String name);
-
根据类型获取bean
<T> T getBean(Class<T> requiredType)
-
根据name和类型一起获取bean
<T> T getBean(String name,Class<T> requiredType)
-
上述所说的【Spring项目启动时,会把其中的bean都创建好】还会收到作用域及延迟初始化影响,这里主要针对默认的单例非延迟加载的bean而言
bean的作用域
- Spring支持五种作用域,后三种在Web环境才生效
- 可以通过
@Scope
注解来进行配置作用域
作用域 | 说明 |
---|---|
singleton | 容器内同名称的bean只会有一个实例(单例)【默认】 |
prototype | 每次使用该bean时都会创建新的实例(非单例) |
request | 每个请求范围内都会创建新的实例 |
session | 每个会话范围内都会创建新的实例 |
application | 每个应用范围内都会创建新的实例 |
注意事项:
- 默认single通的bean,在容器启动时被创建,可以使用@Lazy注解来延迟初始化(延迟到第一次使用)
- prototype的bean,每一次使用该bean的时候都会创建一个新的实例
- 实际开发过程中,绝大多数的bean都是单例的,也就是说绝大部分的Bean不需要配置scope属性
第三方bean
- 如果需要管理的bean对象来自于第三方(不是自定义的),是无法使用@Component以及衍生注解声明bean的,就需要使用到
@Bean
注解 - 若要管理的第三方bean对象,建议对这些bean进行集中分类配置,可以通过@Configuration注解声明一个配置类
- 在启动类中管理第三方 bean 对象(不建议)
@SpringBootApplication
public class SpringbootWebConfig2Application{
@Bean //将方法返回值交给IOC容器管理,成为IOC容器的Bean对象
public SAXReader saxReader(){
return new SAXReader();
}
}.
- 新建一个配置类,对第三方bean进行几种统一管理
@Configuration //声明为一个配置类
public class CommonConfig{
@Bean //声明为bean,交给IOC容器管理
public SAXReader saxReader(){
return new SAXReader();
}
}
早期容器创建(BeanFactory)
- 类路径加载配置文件
Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);
BookDao bookDao = bf.getBean("bookDao",BookDao.class);
bookDao.save();
- BeanFactory创建完毕后,所有bean均为延迟加载
容器相关
- BeanFactory 是IoC容器的顶层接口,初始化BeanFactory对象时,加载Bean延迟加载
- ApplicationContext接口是Spring容器的核心接口,初始化bean立即加载
- ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能
- ApplicationContext接口常用初始化类
- ClassPathXmlApplicationContext
- FileSystemXmlApplicationContext
bean相关
<bean
id="bookDao" bean的Id
name="dao bookDaoImpl daoImpl" bean的别名
class="som.itheima.dao.impl.BookDaoImpl" bean类型,静态工厂类,FactoryBean类
scope="singleton" 控制bean实例数量
init-method="init" 生命周期初始化方法
destory-method="destory" 生命周期销毁方法
autowire="byType" 自动装配类型
factory-method="getInstance" bean工厂方法,用于静态工厂或实例工厂
factory-bean="com.itheima.factory.BookDaoFactory" 实例工厂bean
lazy-init="true" 控制bean延迟加载
/>
依赖注入相关
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/> 构造器注入引用类型
<constructor-arg name="prop_String" ref="WARN"/> 构造器注入简单类型
<constructor-arg type="java.lang.String" index="3" value="WARN" />类型匹配和索引匹配
<property name="bookDao" ref="bookDao"/> setter注入引用类型【ref标签】
<property name="prop_int" value="10"/> setter注入简单类型【value标签】
<property name="prop_int" > 集合注入
<list>
<value>itcast</value> 集合注入简单类型
<ref bean="dataSource"/> 集合注入引用类型
</list?
</property>
五、注解开发
什么是注解?
- Annotation的作用:
- 不是程序本身,可以对程序做出解释
- 可以被其他程序 ( 比如:编译器等 ) (比如:编译器等) (比如:编译器等)读取
- Annotation的格式:
- 注解是以**"@注释名"在代码中存在的,还可以添加一些参数值,例如:@SuppressWarning(value=“unchecked”)**
- Annotation在哪里使用?
- 可以附加在package,class,method,field等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问
- Java代码中的特殊标记,比如:@Ovreride,@Test等,作用是:让其他程序根据注解信息来决定怎么执行程序
- 注解可以使用在类,构造器,方法,成员变量,参数等位置上
内置注解
**@Override:**定义在java.lang.Override 中,此注释只适用于修饰方法,表示一个方法生命打算重写超类(基类)中另一个方法声明
**@Deprecated:**定义在java.lang.Deprecated 中,此注释可以用于修饰方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择
**@SuppressWarnings:**定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息。与前两个注释有所不同,需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了
- @SuppressWarnings(“all”)
- @SuppressWarnings(“unchecked”)
- @SuppressWarnings(“value={“unchecked”,“deprecation”}”)
- 等等…
自定义注解
- 就是自己定义注解
public @interface 注解名称{
public 属性类型 属性名() default 默认值;
}
- 每一个方法生命了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(返回值只能是基本类型,Class,String,enum)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 注解元素必须有值,我们定义注解元素时,经常使用空字符串,0作为默认值
注解的原理
- 注解本质是一个接口,Java中所有注解都是继承了Annotation接口的
- @注解 ( . . . ) (...) (...):其实就是一个实现类对象,实现了该注解以及Annotation接口
元注解
- 元注解的作用就是负责**注解其他注解**,Java定义了4个标准的meta-annotation类型,它们被用来提供对其它annotation类型做说明
- 这些类型和它们所支持的类在Java.lang.annotation包中可以找到。(@Target,@Retention,@Document,@Inherited)
- @Target: 用于描述注解使用范围(即:被描述的注解可以用在说明地方)
- @Retention: 表示需要在说明级别保存该注解信息,用于描述注解的生命周期
- @Document: 说明该注解将被包含在Javadoc中
- **@Inherited: ** 说明子类可以继承父类中的该注解
注解的解析
什么是注解的解析
- 判断类上,方法上,成员变量上是否存在注解,并把注解的内容解析出来
如何解析注解
- 指导思想:要解析谁上面的注解,就应该先拿到谁
- 比如要解析类上面的注解,则应该先获取该类的Class对象,再通过Class对象解析上面的注解
- 比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析上面的注解
- Class、Method、Field、Constructor、都实现了AnnotationElement接口,它们都拥有解析注解的能力。
AnnotationElement接口提供了解析注解的方法 | 说明 |
---|---|
public Annotation[] getDeclaredAnnotations() | 获取当前对象上面的注解 |
public T getDeclareAnnotation(Class annotationClass) | 获取指定的注解对象 |
public boolean isAnnotationPresent(Class annotationClass) | 判断当前对象上是否存在某个注解 |
注解开发定义bean
- 使用**
@Component
**定义bean
@Component("bookDao") //传入的参数bookDao为 给bean起的名字
public class BookDaoImpl implements BookDao{}
@Component
public class BookServiceImpl implements BookService{}
- 核心配置文件中通过
组件扫描
加载bean
<context:component-scan base-package="com.itheima" />
Spring提供@Component注解三个衍生注解
- @Controller: 用于表现层bean定义
- @Service: 用于业务层bean定义
- @Repository: 用于数据层bean定义
纯注解开发 ( 舍弃配置文件 ) (舍弃配置文件) (舍弃配置文件)
- Spring3.0开启了纯注解开发模式,使用Java类代替配置文件,开启了Spring快速开发赛道
- Java类代替Spring核心配置文件
首先看看原来配置的配置文件
<?xml version="1.0" encoding="UTF-8"> //bean的配置项
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<context:component-scan base-package='com.itheima' /> //bean的扫描配置
</beans>
可以看到原来的配置文件复杂繁余,所以使用纯注解开发可以省略上述代码,使程序简化
//文件名:SpringConfig
@Configuration //使用该注解后可以令该类代替上述的配置文件成为一个配置类
@ComponentScan("com.itheima") //bean扫描注解
public class SpringConfig{
}
- @Configuration 注解用于设定当前类为配置类
- @ComponentScan 注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式
@ComponentScan({"com.itheima.service","com.itheima.dao"})
-
完成上述操作之后:
读取Spring核心配置文件初始化容器对象切换为读取配置类初始化容器对象
//加载配置文件初始化容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//加载配置类初始化容器 使用新的类AnnotationConfigApplicationContext
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
bean注解开发作用范围与生命周期管理
- bean的作用范围
@Scope
- bean的生命周期
@PostConstruct
@PreDestory
@Repository
@Scope("singleton") //该注解控制作用范围
public class BookDaoImpl implements BookDao{
public void save(){
System.out.println("book dao save...");
}
@PostConstruct //生命周期初始化钩子注解
public void init(){
System.out.println("生命周期初始化钩子注解——测试函数...");
}
@PreDestory //生命周期销毁化钩子注解
public void destory(){
System.out.println("生命周期销毁钩子注解——测试函数...");
}
}
注解形式的依赖注入
对引用类型的注入
- 使用
@Autowired
注解开启自动装配模式(按类型)
@Service //还记得吗?这是定义bean操作在配置类中会扫描到这个bean并放在容器中
public class BookServiceImpl implements BookService {
@Autowired //这里使用依赖的自动注入,是不是很方便,但是记得这是按类型,并且核心:暴力反射
private BookDao bookDao;
public void save(){
System.out.println("book service save...");
bookDao.save();
}
}
- 注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法,打破了IoC部分思想。
- 注意:自动装配建议使用无参构造方法创建对象(默认), 如果不提供对应构造方法构造方法请提供唯一的构造方法。
针对上述Autowired注解是按类型注入依赖的解决方法
- 附加使用
@Qualifier
注解开启指定名称装配bean
@Service
public class BookServiceImpl implements BookService{
@Autowired
@Qualifier("bookDao") //根据bean的名字查找
private BookDao bookDao;
}
注意:@Qualifier注解无法单独使用,必须配合@Autowired注解使用
对简单类型的注入
- 使用
@Value
实现简单类型注入
@Repository("bookDao")
public class BookDaoImpl implementes BookDao{
@Value("100")
private String connectionNum;
}
为什么使用@Value
注解而不是直接使用private String connectionNum = "100"呢?因为不想把变量值写死,有时变量的值可以来自外部properties文件,首先引入properties文件然后使用@Value(${}
)使用配置文件中的值
加载properties文件
- 使用
@PropertySource
注解加载properties文件
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig{ //还记得这是什么吗?:这是一个配置类
}
注意:此处路径仅支持单一文件配置,多文件请使用数组格式配置,不允许使用通配符 *
第三方bean管理
- 使用
@Bean
配置第三方bean
@Configruration
public class SpringConfig{
//1. 定义一个方法获得要管理的对象
//2. 添加@Bean注解,表示当前方法的返回值是一个bean
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
此处的bean其实不建议写在主配置类中(SpringConfig)
- 使用独立的配置类管理第三方bean
public class jdbcConfig{
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
将独立的配置类加入核心配置
- 推荐方式:导入式
public class JdbcConfig{
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
//相关配置
return ds;
}
}
- 使用
@Import
注解手动加入配置类到核心配置,此注解只能添加一次,多个数据请用数组格式
@Configuration
@Import(JdbcConfig.class)
public class SpringConfig{}
第三方bean依赖注入
简单类型依赖注入
public class JdbcConfig{
@Value("com.mysql.jdbc.Driver") //或者数据来着配置文件
private String dirver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("root")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
引用类型依赖注入
@Bean
public DataSource dataSource(BookService bookService){
System.out.println(bookService);
DruidDataSource ds = new DruidDataSource();
//属性设置
return ds;
}
- 引用类型只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象
- 即:形参bookService需要已经被放入bean容器中管理,然后才可以自动装配
XML配置与注解配置比较
功能 | XML配置 | 注解 |
---|---|---|
定义bean | bean标签 id属性 class属性 | @Component @Controller @Service @Repository @ComponentScan |
设置依赖注入 | setter注入(set方法) 构造器注入(构造方法) 自动装配 | @Autowired @Qualifier @Value |
配置第三方的bean | bean标签 静态工厂、实例工厂、FactoryBean | @Bean |
作用范围 | scope属性 | @Scope |
生命周期 | init-method destory-method | @PostConstructor @PreDestory |
六、Spring的事务管理
-
事务概念:是一组操作的集合,是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败
-
操作步骤:
- 开启事务(一组操作开始前,开启事务):start transaction / begin;
- 提交事务(这组操作全部成功后,提交事务):commit
- 回滚事务(中间任何一个操作出现异常,回滚事务):rollback;
注解:@Transactional
- 注解:@Transcational
- 位置:业务(service)层的方法,类上,接口上
- 作用:将当前方法提交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务,出现异常,回滚事务
配置文件开启事务管理
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
1、属性【rollbackFor】
- 默认情况下,只有出现运行时异常【RuntimeException】才会出现事务回滚。使用
rollbackFor
属性用于控制出现任何异常都回滚事务。
2、属性【propagation】
- 事务传播行为:指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制
属性值 | 含义 |
---|---|
REQUIRED | 【默认值】需要事务,有则加入,无则创建新事务 |
REQUIRES_NEW | 需要新事务,无论有无,总是创建新事务 |
SUPPORTS | 支持事务,有则加入,无则在无事务状态中运行 |
NOT_SUPPORTED | 不支持事务,无事务状态下运行,如果当前存在已有事务,则挂起当前事务 |
MANDATORY | 必须有事务,否则抛出异常 |
NEVER | 必须没有事务,否则抛出异常 |
… |
事务管理实例
-
在业务层接口上添加Spring事务管理【添加注解@Transactional】
public interface AccountService { @Transactional //开启事务管理 public void transfer(String out,String in,Double money); }
注意事项:
Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合
注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务
-
设置事务管理器
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager ptm = new DataSourceTransactionManager(); ptm.setDataSource(dataSource); return ptm;= }
注意事项:
事务管理器要根据实现技术进行选择
MyBatis框架使用的是JDBC事务
-
开启注解式事务驱动
@Com1figuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) @EnableTransactionManagement public class SpringConfig{}
七、面向切面编程【AOP】
- AOP:Aspect Oriented Programming(面向切面编程),其实就是面向特定的方法编程
- 作用:在不惊动原始设计的基础下为其进行功能增强
- 实现
- 动态代理是面向切面编程的主流实现。而SpringAOP是Spring框架的高级技术,在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程
SpringAOP快速入门
-
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
-
编写AOP程序:针对特定方法根据业务进行编程
@Component //交给Ioc容器管理 @Aspect //告诉spring这是一个AOP类 &步骤一 public class Test_AOP_time(){ @Around("execution(* com.itheima.service.*.*(..))") //需要作用的方法在那些位置上 &步骤二 /** * * 参数:ProceedingJoinPoint参数类型固定。表示使用的方法 */ public Object recordTime(ProceedingJoinPonit joinPoint) throws Throwable{ //&步骤三 long begin = System.currentTimeMills(); //获取系统当前时间 Object object = joinPoint.proceed(); //返回值固定为对象 long end = System.currentTimeMills(); //再次获取时间评判方法运行时常 log.info(joinPoint.getSignature()+"方法运行时间:{}ms",end-begin); return object; } }
使用场景
- 记录操作日志
- 权限控制
- 事务管理
- …
优势
- 代码无侵入
- 减少重复代码
- 提高开发效率
- 维护方便
AOP核心概念
- 连接点:JointPoint ,可以被AOP控制发方法(暗含方法执行时的相关信息)
- 通知:Advce,指那些重复的逻辑,也就是共性的功能(最终体现为一个方法)
- 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被调用
- 切面:Aspect,描述通知与切入点的对应关系(通知+ 切入点)
- 目标对象:Target, 通知所应用的对象
通知类型
@Around
:环绕通知,此注解标注的通知方法在目标方法前后都被执行@Before
:前置通知,此注解标注的通知方法在目标方法之前执行@After
:后置通知,此注解标注的通知方法在目标方法之后执行,无论是否有异常都会被执行@AfterReturning
:返回后通知,此注解标注的通知方法在目标方法之后执行,有异常就不会被执行@AfterThrowing
:异常后通知,此注解标注的通知方法发生异常后执行
注意事项:
@Around
环绕通知需要自己调用 **ProceedingJoinPoint.proceed()**来让原始方法执行,其他通知不需要考虑目标方法执行@Around
环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值
执行顺序
问题: 当有多个切面的切入点都匹配到了目标方法,目标方法运行时候,多个通知方法都会被执行。
- 在不同切面类中,默认按照切面类的类名字母排序
- 目标方法前的通知方法:字母排名靠前的先执行
- 目标方法后的通知方法:字母排名靠前的后执行
- 用 **@Order(数字)**加在切面类上来控制顺序
- 目标方法前的通知方法:数字小的先执行
- 目标方法后的通知方法:数字小的后执行
切入点表达式
- 切入点表达式:描述切入点方法的一种表达式
- 作用:主要用来决定项目中哪些方法需要加入通知
- 常见形式
execution(...)
:根据方法的签名来匹配@annotation(...)
:根据注解匹配
@Before("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
@Before("@annotation(com.itheima.anno.Log)")
表达式-execution
execution 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution(访问修饰符? 返回值 包名.类名.? 方法名字(方法参数) throws 异常)
- 其中带有 ? 的表示可以省略的部分
- 访问修饰符:可省略(比如:public、protected)
- 包名.类名:可省略(不建议省略)
- throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
AOP入门案例
-
导入aop相关坐标
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
说明:spring-context 坐标依赖 spring-aop坐标
-
定义dao接口和实现类
public interface BookDao{ public void save(); public void update(); }
@Repository public class BookDaoImpl implements BookDao { public void save(){ System.out.println(System.currentTimeMills()); System.out.println("book dao save..."); } public void update(){ System.out.println("book dao update ..."); } }
-
定义切入点与通知类
@Component //将该类交给Spring管理 @Aspect //告诉Spring下面这个类是用于做AOP public class MyAdvice { //说明:切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑 @Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt(){} @Before("pt()") //绑定切入点和通知关系,并指定通知添加到原始连接点的具体执行位置 public void before(){ //定义通知类,制作通知 System.out.println(System.currentTimeMills()); } }
-
Spring核心配置添加注解【说明添加了AOP注解】
@Comfiguration @ComponentScan("com.itheima") @EnableAspectJAutoProxy //告知Spring添加了AOP public class SpringConfig{}
AOP工作流程
-
Spring容器启动
-
读取所有切面配置中的切入点
-
初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
-
匹配失败,创建对象
-
匹配成功,创建原始对象(目标对象)的代理对象
-
-
获取bean执行方法
- 获取bean,调用方法并执行,完成操作
- 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强内容,完成操作
打jar包
-
执行Maven打包指令package
-
执行Java指令,运行jar包
java -Dserver.port=9000 -jar tlias-web-management-0.0.1=SNAPSHOT.jar --server.port=10010v
注意事项:
Springboot 项目进行打包的时候,需要引入插件 spring-boot-maven-plugin(基于官方骨架创建项目,会自动添加该插件)