Spring学习
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。
专有名词
- 控制反转IoC(inversion of control)
- 面向切面编程AOP(aspect oriented programming)
- 数据访问 (data access)
- 数据集成 (data integration)
- 核心容器 (core container)
- 依赖注入 (dependency injection)
- DAO层(data access object)通过DAO模式,可以将数据访问逻辑封装在DAO接口和实现类中
- Service层,实现业务逻辑的类,它通常会调用DAO(数据访问对象)来访问数据库
核心概念
-
目标:充分解耦
- 使用IOC容器管理bean(IOC)
- 在IOC容器内将由依赖关系的bean进行关系绑定(DI)
-
最终效果
- 使用对象时不仅可以直接从IOC容器中获取,并且获取到的bean已经绑定了所有的依赖关系。
控制反转IOC
- 为了解决代码耦合度偏高的现状,使用对象时,在程序中不要主动使用new产生对象,转换为由外部提供对象。对象的创建控制权由程序转移到外部。
- Spring技术对IOC思想进行了实现。
- spring提供了一个容器,称为IOC容器,用来充当IOC思想中的外部。
- IOC容器负责对象的创建,初始化等一系列工作,被创建或被管理的对象在IOC容器中统称为Bean。
依赖注入DI
在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入。
IOC入门案例思路分析
- 管理什么?(service与Dao)
- 如何将被管理的对象告知IOC容器?(配置)
- 被管理的对象交给IOC容器,如何获取到IOC容器?(接口)
- IOC容器得到后,如何从容器中获取bean?(接口方法)
- 使用Spring导入哪些坐标?(pom.xml)
步骤
-
在pom.xml导入Spring的坐标。
pom.xml是Maven项目的核心文件,全称是Project Object Model(项目对象模型)。它是一个XML文件,用于定义项目的各种属性和配置。在pom.xml文件中,可以定义项目的名称、版本、依赖项、插件、构建配置等。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency>
-
定义Spring管理的类(接口)
//创建BookService接口 public interface BookService { public void save(); }
//创建BookServiceImpl实现类 public class BookServiceImpl implements BookService { private BookDao bookDao = new BookDaoImpl(); public void save(){ System.out.println("book service save ...."); bookDao.save(); } }
//创建BookDao接口 public interface BookDao { public void save(); }
//创建BookDaoImpl实现类 public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } }
-
创建Spring配置文件,配置对应类作为Spring管理的bean
在resource文件夹下创建Spring 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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 1.导入spring的坐标spring-context 对应版本是5.2.10.RELEASE--> <!-- 2.配置bean--> <!-- bean标签表示配置bean--> <!-- id属性表示给bean起名字--> <!-- class属性表示给bean定义类型--> <bean id="bookDao" class="com.myproject.dao.Impl.BookDaoImpl"/> <bean id="bookService" class="com.myproject.service.impl.BookServiceImpl"/> </beans>
-
初始化IOC容器(Spring核心容器/Spring容器),通过容器获取bean
创建java类
public class App2 { public static void main(String[] args) { //3.获取 IOC容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //4.获取bean BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); BookService bookService = (BookService) ctx.getBean("bookService"); bookService.save(); } }
DI入门案例思路分析
- 基于IOC管理bean。
- Service中使用new形式创建的Dao对象是否保留?(否)
- Service中需要的Dao对象如何进入Service中?(提供方法)
- Service与Dao间的关系如何描述?(配置)
步骤
-
删除使用new的形式创建对象的代码
public class BookServiceImpl implements BookService { //5.删除业务层中使用new的方式创建的dao对象 //private BookDao bookDao = new BookDaoImpl(); private BookDao bookDao; public void save(){ System.out.println("book service save ...."); bookDao.save(); } }
-
提供依赖对象对应的setter方法,容器调用该方法
public class BookServiceImpl implements BookService { //5.删除业务层中使用new的方式创建的dao对象 //private BookDao bookDao = new BookDaoImpl(); private BookDao bookDao; public void save(){ System.out.println("book service save ...."); bookDao.save(); } //6.提供对应的set方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
-
配置service与dao之间的关系,在spring config配置文件中使用property标签将两个类进行绑定
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 1.导入spring的坐标spring-context 对应版本是5.2.10.RELEASE--> <!-- 2.配置bean--> <!-- bean标签表示配置bean--> <!-- id属性表示给bean起名字--> <!-- class属性表示给bean定义类型--> <bean id="bookDao" class="com.myproject.dao.Impl.BookDaoImpl"/> <bean id="bookService" class="com.myproject.service.impl.BookServiceImpl"> <!-- 7.配置service与dao的关系--> <!-- property标签表示配置当前bean的属性--> <!-- name属性表示配置哪一个具体的属性--> <!-- ref属性表示参照哪一个bean--> <property name="bookDao" ref="bookDao"/> </bean> </beans>
bean配置
bean基础配置
类别 | 描述 |
---|---|
名称 | bean |
类型 | 标签 |
所属 | beans标签 |
功能 | 定义Spring核心容器管理的对象 |
格式 | <beans> <bean/> <bean></bean> </beans> |
属性列表 | id:bean的id,使用容器可以通过id值获取对应的bean,在容器中id值唯一 class:bean的类型,即配置的bean的全路径类名 |
范例 | ` |
` |
bean别名配置
类别 | 描述 |
---|---|
名称 | name |
类型 | 属性 |
所属 | bean标签 |
功能 | 定义bean的别名,可定义多个,使用逗号(,)分号(;)空格( )分隔 |
范例 | <bean id="bookDao" **name="dao bookDaoImpl"** class="com.myproject.dao.Impl.BookDaoImpl"/> <bean id="bookService" **name="service serviceDaoImpl"** class="com.myproject.service.impl.BookServiceImpl"> |
注意事项:获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常NoSuchBeanDefinitionException: No bean named ‘bookService’ available
bean作用范围配置
类别 | 描述 |
---|---|
名称 | scope |
类型 | 属性 |
所属 | bean标签 |
功能 | 定义bean的作用范围,可选范围如下 singleton:单例(默认) prototype:非单例 |
范例 | <bean id="bookDao" class="com.myproject.dao.Impl.BookDaoImpl" **scope="prototype"** /> |
为什么bean默认为单例?
- 性能和资源优化:对于无状态的Bean(如服务(Service)或数据访问对象(DAO)),单例可以节省系统资源,比如内存,因为Spring容器只需要创建一个Bean实例。此外,每次需要这个Bean时,Spring容器可以立即返回已经存在的实例,而无需重新创建和初始化,从而提高性能。
- 共享状态:对于需要维护状态信息的Bean,单例模式可以提供一个全局访问点,所有的请求都能够访问同一个实例,从而共享状态。这在某些情况下是有用的,比如对于配置数据的存储。
- 实践中的常规需求:在实际的企业应用开发中,许多服务组件通常都是无状态的,并且在整个应用生命周期中都存在。因此,单例模式成为了一种自然的选择。
- 简化配置:将Bean设置为单例模式可以简化配置。如果所有的Bean都是单例的,那么在配置和管理Bean的时候就不需要考虑多个实例之间的关系和状态的同步问题,从而简化了配置和管理的复杂性。
- 提高系统稳定性:在Spring的AOP(面向切面编程)编程中,单例可以提高系统稳定性。因为一个切面只会在第一次运行时产生一个代理对象,而这个代理对象会一直被重复使用,直到代理对象被销毁。
适合交给容器进行管理的bean?
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
不适合交给容器进行管理的bean?
- 封装实体的域对象domain
bean实例化-三种方式
bean本质上就是对象,创建bean使用构造方法完成
以前创建对象是使用new和构造方法,对于spring来说也是使用构造方法。
第一种方式-构造方法(常用)
-
提供可访问的构造方法
public class BookDaoImpl implements BookDao { //构造方法 public BookDaoImpl() { } public void save() { System.out.println("book dao save ..."); } }
-
创建Spring配置文件,配置对应类作为Spring管理的bean
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bookDao" class="com.myproject.dao.Impl.BookDaoImpl"/> </beans>
注意:如果无参构造方法不存在,将抛出异常BeanCreationException
第二种方式-静态工厂(了解)
- 配置静态工厂
- 配置bean
第三种方式-实例工厂(了解)
- 配置工厂
- 配置bean
bean生命周期
-
初始化容器
- 创建对象
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
-
使用bean
- 执行业务操作
-
关闭/销毁容器
- 执行bean销毁方法
bean生命周期配置
init-method与destory-method
<bean id="bookService" class="com.myproject.service.impl.BookServiceImpl"
**init-method="aa"**
**destroy-method="bb"**
>
bean的销毁时间
-
容器关闭前出发bean的销毁
-
关闭容器方式
- 手工关闭容器
ConfigurableApplicationContext
接口close()操作 - 注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
ConfigurableApplicationContext
接口registerShutdownHook()操作
- 手工关闭容器
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.close();
}
}
两种依赖注入方式
向一个类中传递数据的方式有两种
- 普通方法(set方法)
- 构造方法
依赖注入描述了容器中建立bean与bean之间依赖关系的过程,bean运行过程中需要的数据类型
- 引用类型
- 简单类型(基本数据类型与String)
依赖注入方式
-
setter注入-使用property标签
- 简单类型
- 引用类型
-
构造器注入-使用constructor-arg标签
- 简单类型
- 引用类型
setter注入-引用类型-使用ref属性
-
在bean中定义引用类型属性并提供可访问的set方法
public class BookServiceImpl implements BookService { private BookDao bookDao; //提供对应的set方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
-
spring配置中使用property标签ref属性注入引用类型对象
<bean id="bookService" class="com.myproject.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
<bean id="bookDao" class="com.myproject.dao.Impl.BookDaoImpl"/>
</bean>
</beans>
setter注入-简单类型-使用value属性
-
在bean中定义引用类型属性并提供可访问的set方法
public class BookServiceImpl implements BookService { //引用类型 private BookDao bookDao; //简单类型 private int connectionNum; private String databaseName; //提供对应的set方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } public void setConnectionNum(int connectionNum) { this.connectionNum = connectionNum; } public void setDatabaseName(String databaseName) { this.databaseName = databaseName; } public void save(){ System.out.println("book service save ...."+databaseName+","+connectionNum); bookDao.save(); } }
-
配置中使用property标签value属性注入简单类型数据
<bean id="bookDao" class="com.myproject.dao.Impl.BookDaoImpl"/> <bean id="bookService" class="com.myproject.service.impl.BookServiceImpl"> //使用ref属性注入引用类型数据 <property name="bookDao" ref="bookDao"/> //使用value属性注入简单类型数据 <property name="databaseName" value="mysql"/> <property name="connectionNum" value="100"/> </bean> </beans>
构造器注入-引用类型ref属性-简单类型value属性
-
编写CatDaoImpl实现类
public class CatDaoImpl implements CatDao { public void speak(){ System.out.println("miao~miao~maio~"); } }
-
编写AnimalServiceImpl实现类
public class AnimalServiceImpl implements AnimalService { private String animal; private int num; private CatDao catDao; //使用构造函数 public AnimalServiceImpl(String animal, int num ,CatDao catDao) { this.animal = animal; this.num = num; this.catDao = catDao; } public void speak(){ System.out.println("有"+num+"只"+animal); catDao.speak(); } }
-
编写bean配置,使用构造器注入,使用constructor-arg标签
<!--使用构造器注入--> <bean id="catDao" class="com.myproject.dao.Impl.CatDaoImpl"/> <bean id="animalService" class="com.myproject.service.impl.AnimalServiceImpl"> <!--简单属性使用value属性--> <constructor-arg name="animal" value="小猫"/> <constructor-arg name="num" value="3"/> <!--引用属性使用ref属性--> <constructor-arg name="catDao" ref="catDao"/> </bean>
构造器注入-参数适配(了解)
-
配置中使用constructor-arg标签type属性设置 按形参类型 注入
<bean id="animalService" class="com.myproject.service.impl.AnimalServiceImpl"> <!--type属性设置 按形参类型 注入--> <constructor-arg type="java.lang.String" value="小猫"/> <constructor-arg type="int" value="3"/> </bean>
-
配置中使用constructor-arg标签index属性设置 按形参位置 注入
<bean id="animalService" class="com.myproject.service.impl.AnimalServiceImpl"> <!--type属性设置 按形参类型 注入--> <constructor-arg index="0" value="小猫"/> <constructor-arg index="1" value="3"/> </bean>
依赖自动装配
-
使用spring配置文件中bean标签autowire属性设置自动装配
<bean id="catDao" class="com.myproject.dao.Impl.CatDaoImpl"/> <bean id="animalService" class="com.myproject.service.impl.AnimalServiceImpl" autowire="byType"> <constructor-arg name="animal" value="小猫"/> <constructor-arg name="num" value="3"/> <!--使用依赖自动装配,不需要下面--> <!--<constructor-arg name="catDao" ref="catDao"/>--> </bean>
-
使用autowire属性设置自动装配,需要setter方法
public void setCatDao(CatDao catDao) { this.catDao = catDao; }
依赖自动装配特征
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
- 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
集合注入
使用setter方法实现集合的注入
Array
<property name="array">
<array>
<value>100</value>
<value>200</value>
</array>
</property>
List
<property name="list">
<list>
<value>aaaa</value>
<value>bbbb</value>
</list>
</property>
Set
<property name="set">
<set>
<value>cccc</value>
<value>dddd</value>
</set>
</property>
Map
<property name="map">
<map>
<entry key="1" value="eeee"/>
<entry key="2" value="dddd"/>
</map>
</property>
Properties
<property name="properties">
<props>
<prop key="3">ffff</prop>
<prop key="4">gggg</prop>
</props>
</property>
- 集合注入使用场景大部分都是在简单类型,如有些框架,需要初始化一些数据。
- 实际开发中集合注入的使用量也是极少的。
数据源对象管理-第三方资源配置管理
数据源对象案例:阿里的druid这个数据源
阿里Druid是阿里巴巴开源平台上一个数据库连接池实现,结合了C3P0、DBCP等DB池的优点,同时加入了日志监控。Druid可以很好的监控DB池连接和SQL的执行情况,天生就是针对监控而生的DB连接池。
-
在pom.xml配置文件中加上阿里druid坐标
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency>
-
在spring配置文件中管理DruidDataSouce对象
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbd:mysql://localhost:3306/spring_db"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean>
-
编写类与main方法获取bean
public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource dataSource = (DataSource) ctx.getBean("dataSource"); System.out.println(dataSource); } }
加载properties文件
在Java中,.properties
文件是一种配置文件,主要用于存储和读取配置数据。这种文件通常用于应用程序的属性设置,如数据库连接参数、应用设置、语言资源等。
Properties
类是Java的标准库中的一部分,可以方便地加载、访问和修改.properties
文件中的数据。
-
开启context命名空间
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
-
编写jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db jdbc.username=root jdbc.password=root
-
使用context空间加载properties文件
<context:property-placeholder location="jdbc.properties"/>
-
使用属性占位符${}读取properties文件中的属性
<bean class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
加载properties文件时的几种情况
-
不加载系统属性
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
-
加载多个properties文件
<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"/>
注解开发定义bean-@Component
-
使用 @Component定义bean
@Component("catDao") public class CatDaoImpl implements CatDao { public void speak(){ System.out.println("miao~miao~miao~"); } }
-
spring核心配置文件中通过组件扫描加载bean
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.myproject.dao.impl"/> </beans>
Spring提供@Component注解的三个衍生注解
- @Controller:用于表现层bean定义
- @Service:用于业务层bean定义
- @Repository:用于数据层bean定义
所有类都用@Component注解,会导致分不清。这三个注解都与@Component功能一致,只是方便我们理解。
纯注解开发
Spring3.0开启了纯注解开发模式,使用java类替代配置文件,开启了Spring快速开发赛道
-
使用 @Component定义bean
@Component("catDao") public class CatDaoImpl implements CatDao { public void speak(){ System.out.println("miao~miao~miao~"); } }
-
创建java配置类代替spring配置文件
@Configuration //@Configuration注解用于设定当前类为配置类 @ComponentScan("com.myproject") //@ComponentScan注解设定扫描路径 public class SpringConfig {}
-
加载配置类初始化容器
public class App2 { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); DogDao dogDao = (DogDao) ctx.getBean("dogDao"); dogDao.speak(); } }
@ComponentScan注解设定扫描路径,此注解只能添加一次,多个数据用数组格式
@ComponentScan({"com.myproject.dao","com.myproject.service"})
注解开发依赖注入–@Autowired
使用 @Autowired注解开启自动装配模式,简单类型与引用类型下面例子一起体现
-
使用 @Repository注解定义数据层的bean
@Repository("dogDao") public class DogDaoImpl implements DogDao { @Override public void speak() { System.out.println("wang~wang~wang~"); } }
-
使用 @Service注解定义业务层的bean
@Service("aniaml") public class AnimalServiceImpl implements AnimalService { public void animal() { System.out.println("一只"+name+"在"); } }
-
创建java配置类代替spring配置文件
@Configuration //@Configuration注解用于设定当前类为配置类 @ComponentScan("com.myproject") //@ComponentScan注解设定扫描路径 public class SpringConfig {}
-
使用 @Autowired注解开启自动装配模式
@Service("aniaml") public class AnimalServiceImpl implements AnimalService { //使用@Value注解实现简单类型注入 @Value("小狗") private String name; //可以使用@Qualifier注解开启指定名称装配 //@Qualifier("dogDao2") @Autowired private DogDao dogDao; @Override public void animal() { System.out.println("一只"+name+"在"); dogDao.speak(); } }
注意:
- 自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法
- 自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法
- @Qualifier注解无法单独使用,必须配合@Autowired注解使用
加载properties文件
使用 @PropertySource注解加载properties文件
-
创建外部文件jdbc.properties,提供数据
name=xiaogou
-
在配置类中使用 @PropertySource注解加载properties文件
@Configuration @ComponentScan("com.myproject") @PropertySource("jdbc.properties") public class SpringConfig {}
-
使用@Value注解获取外部数据
@Service("aniaml") public class AnimalServiceImpl implements AnimalService { // 使用外部文件获取数据 @Value("${name}") private String name; @Autowired private DogDao dogDao; @Override public void animal() { System.out.println("一只"+name+"在"); dogDao.speak(); } }
注意:配置类中使用 @PropertySource注解加载properties文件,路径仅支持单一文件配置,多文件请使用数组格式配置,不允许使用通配符*
@PropertySource({"jdbc.properties","jdbc2.properties"})
注解开发管理第三方bean
数据源对象案例:阿里的druid这个数据源
-
在pom.xml配置文件中加上阿里druid坐标
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency>
-
编写jdbc配置类(这是独立的配置类,不建议写到Spring核心配置类中)
@Configuration public class JdbcConfig { //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; } }
-
在Spring配置类中导入jdbc配置类
@Configuration //扫描式 //@ComponentScan("com.myproject.config") //导入式 @Import(JdbcConfig.class) public class SpringConfig2 {}
-
编写类与main方法获取bean
public class App4 { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig2.class); DataSource dataSource = ctx.getBean(DataSource.class); System.out.println(dataSource); } }
注意:使用@Import注解手动加入配置类到核心类,此注解只能添加一次,多个数据请用数组格式@Import({JdbcConfig.class,JdbcConfig2.class})
第三方依赖注入
-
简单类型依赖注入@Value
public class JdbcConfig { @Value("com.mysql.jdbc.Driver") private String driver; @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 class JdbcConfig { //只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象 @Bean public DataSource dataSource(DogDao dogDao){ dogDao.speak(); System.out.println(dogDao); DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } }
XML配置对比注解配置
功能 | XML配置 | 注解 |
---|---|---|
定义bean | bean标签 id属性 class属性 | @Component @Controller @Service @Repository @ComponentScan |
设置依赖注入 | setter注入(set方法) 构造器注入(构造方法) 自动装配 | @Autowired @Qualifier @Value |
配置第三方bean | bean标签 | @Bean |
作用范围 | scope属性 | @Scope |
生命周期 | 标准接口 init-method destory-method | @PostConstructor @PreDestory |
AOP简介
- AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。
- 作用:在不惊动原始设计的基础上为其进行功能增强。
AOP核心概念
-
连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法,抛出异常,设置变量等。
- 在SpringAop中,理解为方法的执行
-
切入点(PointCut):匹配连接点的式子
- 在SpringAop中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
-
通知(Advice):在切入点处执行的操作,也就是共性功能
- 在SpringAOP中,功能最终以方法的形式呈现
-
通知类:定义通知的类
-
切面(Aspect):描述通知与切入点的对应关系
AOP入门案例思路分析
案例设定:测定接口执行效率
简化设定:在接口执行前输出当前系统时间
开发模式:注解
思路分析:
-
导入aop相关坐标(pom.xml)
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
-
制作连接点方法(原始操作,定义Dao接口与实现类)
public interface BookDao { public void save(); public void update(); }
@Repository public class BookDaoImpl implements BookDao { @Override public void save() { System.out.println(System.currentTimeMillis()); System.out.println("save~~~~~~"); } @Override public void update() { System.out.println("update~~~~~~"); } }
-
制作共性功能(通知类与通知)
public class MyAdvice { public void method(){ System.out.println(System.currentTimeMillis()); } }
-
定义切入点(切入点定义依托一个不具有实际意义的方法进行,即无参数,无返回值,方法体无实际逻辑)
public class MyAdvice { //意思为:执行到update这个方法时,为切入点 @Pointcut("execution(void com.myproject.dao.BookDao.update())") private void pt(){} }
-
绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置
public class MyAdvice { //意思为:执行到update这个方法时,为切入点 @Pointcut("execution(void com.myproject.dao.BookDao.update())") private void pt(){} //共性功能在切入点前面执行 @Before("pt()") public void method(){ System.out.println(System.currentTimeMillis()); } }
-
开启Spring对AOP注解驱动支持
@Configuration @ComponentScan("com.myproject") //开启Spring对AOP注解驱动支持 @EnableAspectJAutoProxy public class SpringConfig {}
AOP工作流程
-
Spring容器启动
-
读取所有切面配置中的切入点
-
初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
-
获取bean执行方法
- 获取bean,调用方法并执行,完成操作
- 获取的bean时代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法完成最终工作的
代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
AOP切入点表达式
-
切入点:要进行增强的方法
-
切入点表达式:要进行增强的方法的描述方式
描述方式一:执行包下的接口中的方法
execution(void com.myproject.dao.BookDao.update())
描述方式二:执行包下的实现类中的方法
execution(void com.myproject.dao.impl.BookDaoImpl.update())
-
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/类名.方法名(参数) 异常名)
- 动作关键词:描述切入点的行为动作,例如execution表示执行到指点切入点
- 访问修饰符:public,private等,可以省略
- 异常名:方法定义中抛出指定异常,可以省略
-
可以使用通配符描述切入点,快速描述
-
*:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.myproject.*.BookDao.update*(*))
-
…:多个连续的任意符号,可以独立出现,常用与简化包名与参数的书写
execution(public User com..dao.BookDao.update(..))
-
+:专用于匹配子类类型
execution(* *..*BookDao+.*(..))
-
-
书写技巧
- 描述切入点通常描述接口,而不描述实现类
- 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
- 包名书写尽量不使用…匹配,效率过低,常用*做单个包描述匹配或精准匹配
- 接口名书写采用*匹配,例如UserService写成 *Service
- 方法名书写以动词进行精准匹配,名词采用*匹配,例如getById写成getBy**
AOP通知类型
-
AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
-
AOP通知共分为5中类型
-
前置通知:@Before,当前通知方法在原始切入点方法前运行
//共性功能在切入点前面执行 @Before("pt()") public void beforemethod(){ System.out.println(System.currentTimeMillis()); }
-
后置通知:@After,设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
//共性功能在切入点后面执行 @After("pt()") public void aftermethod(){ System.out.println(System.currentTimeMillis()); }
-
环绕通知(重点):@Around,当前通知方法在原始切入点方法前后运行
//共性功能在切入点环绕执行(前后都执行) @Around("pt()") public Object aroundmethod(ProceedingJoinPoint pjp) throws Throwable{ System.out.println(System.currentTimeMillis()); //表示对原始操作的调用 Object ret = pjp.proceed(); System.out.println(System.currentTimeMillis()); return ret; }
-
返回后通知(了解):@AfterReturning,当前通知方法在原始切入点方法正常执行完毕后运行
-
抛出异常后通知(了解):@AfterThrowing,当前通知方法在原始切入点方法抛出异常后运行
-
@Around注意事项
环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
对原始方法的调用可以不接受返回值,通知方法设置成void即可,如果接受返回值,必须设定为Object类型
由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象