1.IOC和DI配置文件开发
spring,主要内容有两点:IOC,AOP。AOP里面spring独到的事务处理。
- 理清spring框架设计思想
1.1.Spring Framework系统架构
spring4架构图
系统架构讲究上层依赖于下层
- Data Access:数据访问
- Data Integration:数据集成
- Web:Web开发
- Aop:面向切面编程,不惊动原始程序的基础上增强功能
- Aspects:AOP思想实现
- Core Container:核心容器,里面装的什么?在java中那当然是对象啦
- Test:单元测试与集成测试
Spring Framework 学习路线
第一部分:核心容器(核心概念IOC/DI、容器基本操作)
第二部分:整合:整合数据层技术MyBatis
第三部分:AOP(核心概念、AOP基础操作、AOP实用开发)
1.2.核心概念
简单通俗来讲:在dao层和service层中,要想在service层中使用dao对象,就需要 new 一个对象,这样就与“低耦合高内聚”思想背道而驰,然后为了解决这个问题,就有了IOC和DI。
- IOC控制反转:就不需要我们自己new对象了,他会自己new对象,也就是Bean;(IOC就是使用IOC容器管理Bean)
- DI依赖注入:对于多个有依赖关系的对象,也会绑定他们之间的依赖关系(DI就是在IOC容器内将有依赖关系的bean进行关系绑定);
- IOC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IOC容器中统称为Bean;
IOC和DI是一种思想,Bean是一种对象,IOC容器是管理bean对象的一种容器
1.3.快速入门
spring程序开发基本步骤
1.导入相关依赖坐标
2.编写dao接口和实现类
3.创建spring配置文件,并在配置文件中创建Bean
4.使用Spring的Api获取Bean实例
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--spring文件头-->
<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 - context 依赖-->
<!--2.配置bean-->
<bean id="bookDao" class="com.chen.dao.daoImpl.BookDaoImpl"></bean>
<bean id="bookService" class="com.chen.service.serviceImpl.BookServiceImpl">
<!--property标签表示配置当前bean的属性
name表示配置哪一个具体的属性
ref属性表示参照的那个bean-->
<property name="bookDao" ref="bookDao"></property>
</bean>
</beans>
//main中
//3.获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//4.获取bean
// BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// bookDao.bookDao();
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.bookService();
//Service层中
//5.删除业务层中使用new的形式创建dao对象
// private BookDao bookDao = new BookDaoImpl();
private BookDao bookDao;
@Override
public void bookService() {
bookDao.bookDao();
System.out.println("bookService...");
}
//6.提供set方法
public void setBookDao(BookDao bookDao){
this.bookDao=bookDao;
}
1.4.Spring配置文件
1.4.1.Bean标签基本配置
基本属性
id:Bean标签在spring容器中的唯一标识
class:Bean的全限定类名
作用:配置对象交由spring来控制
默认调用的是对象的无参构造方法,如果没有无参构造方法就会创建失败。
1.4.2.Bean标签作用范围
scope:标识对象的作用范围,常见取值如下
取值范围 | 说明 |
---|---|
singleton | 默认值,单例,整个程序使用一个实例 |
prototype | 多例的,每个请求一个创建一个新示例 |
request | WEB项目中,Spring创建一个Bean对象,将对象放到request域中 |
session | WEB项目中,Spring创建一个Bean对象,将对象放到session域中 |
global session | WEB项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession相当于session |
1.4.3.实例化Bean的三种方式
1)使用无参构造方法
<Bean id="", class=""><Bean/>
2)工厂静态方法实例化
public class StaticFactoryBean {
public static BookDao createBookDao(){
return new BookDaoImpl();
}
}
<bean id="bookDao" class="com.chen.StaticFactoryBean" factory-method="createBookDao"></bean>
3)工厂实例方法实例化
public class FactoryBean {
public BookDao createBookDao(){
return new BookDaoImpl();
}
}
<bean id="FactoryBean" class="com.chen.FactoryBean"></bean>
<bean id="bookDao" factory-bean="FactoryBean" factory-method="createBookDao"></bean>
4)FactoryBean
public class BookDaoFactoryBean implements FactoryBean<BookDao> {
//代替原始实例工厂中创建对象的方法
@Override
public BookDao getObject() throws Exception {
return new BookDaoImpl();
}
//实例的对象对应类的字节码
@Override
public Class<?> getObjectType() {
return BookDaoImpl.class;
}
//是否单例
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
<bean id="bookDao" class="com.chen.BookDaoFactoryBean"></bean>
1.5.Bean生命周期
1.5.1.自己编写生命周期控制方法
提供生命周期
public class BookDaoImpl implements BookDao {
@Override
public void bookDao() {
System.out.println("BookDao...");
}
//Bean初始化对应操作
public void init(){
System.out.println("init...");
}
//Bean销毁前对应的操作
public void destroy(){
System.out.println("destroy...");
}
}
配置生命周期控制方法
<bean id="bookDao" class="com.chen.dao.daoImpl.BookDaoImpl" init-method="init" destroy-method="destroy"></bean>
<bean id="bookService" class="com.chen.service.serviceImpl.BookServiceImpl">
<property name="bookDao" ref="bookDao"></property>
<bean>
测试方法
public class App {
public static void main(String[] args) {
//3.获取IOC容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//4.获取bean
// BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// bookDao.bookDao();
BookService bookService = (BookService) ctx.getBean( "bookService");
bookService.bookService();
ctx.registerShutdownHook();//虚拟机关闭之前,把容器先关掉,这句代码写在哪里都行
//ctx.close();//直接关闭,放在前面就直接关闭了
}
}
1.5.2.接口控制
public class BookServiceImpl implements BookService , InitializingBean, DisposableBean {
private BookDao bookDao;
@Override
public void bookService() {
bookDao.bookDao();
System.out.println("bookService...");
}
public void setBookDao(BookDao bookDao){
System.out.println("setBookDao... ");
this.bookDao=bookDao;
}
@Override
public void destroy() throws Exception {
System.out.println("service destroy...");
}
@Override
public void afterPropertiesSet() throws Exception {//在属性创建之后执行
System.out.println("service init...");
}
}
1.5.3.Bean生命周期总结
- 初始化容器
1.创建对象(内存分配)
2.执行构造方法
3.执行属性注入
4.执行bean初始化方法 - 使用bean
1.执行业务操作 - 关闭/销毁容器
1.执行bean销毁方法,可以是ClassPathXmlApplicationContext接口下的colse()方法或者registerShutdownHook()方法
1.6.依赖注入
1.6.1.setter注入
1.设置set方法
2.配置bean
<bean id="bookDao" class="com.chen.dao.daoImpl.BookDaoImpl" init-method="init" destroy-method="destroy"></bean>
<bean id="bookService" class="com.chen.service.serviceImpl.BookServiceImpl">
<--Bean类型-->
<property name="bookDao" ref="bookDao"></property>
<--基本类型-->
<property name="databaseName" value="abc"></property>
</bean>
1.6.2.构造器注入
1.设置构造器
2.配置bean,注意此时的name为形参,其他与setter注入一致。(这是基本模式)
还有一种模式,不写名字,写类型type,这样可以解耦,与形参名称不耦合
<bean id="bookDao" class="com.chen.dao.daoImpl.BookDaoImpl" init-method="init" destroy-method="destroy"></bean>
<bean id="bookService" class="com.chen.service.serviceImpl.BookServiceImpl">
<--Bean类型-->
<constructor-arg name="bookDao" ref="bookDao"></constructor-arg>
<--基本类型-->
<constructor-arg type="java.lang.String" value="abc"></constructor-arg>
</bean>
这样虽然解耦,但是如果形参是两个相同类型的,就无法使用,于是有了下面写法,给出参数位置index
<bean id="bookDao" class="com.chen.dao.daoImpl.BookDaoImpl" init-method="init" destroy-method="destroy"></bean>
<bean id="bookService" class="com.chen.service.serviceImpl.BookServiceImpl">
<--Bean类型-->
<property name="bookDao" ref="bookDao"></property>
<--基本类型-->
<property index="0" type="java.lang.String" value="abc"></property>
</bean>
1.6.3.依赖注入的方式选择
构造器:强制依赖,必须要有的
setter方法:可选依赖,不一定要有的
在有必要的情况下可以两种都用,自己开发中推荐使用setter
1.6.4.依赖自动装配
- IOC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程叫做自动装配
- 自动装配方式
- 按类型(最常用)
- 按名称
- 按构造方法
- 不启用自动装配
<bean id="bookService" class="com.chen.service.serviceImpl.BookServiceImpl" autowire="byName">
自动装配特征
- 适用于引用类型依赖注入,不能对基本类型和String进行操作
- byType必须保障容器中相同类型的bean唯一,推荐使用
- byName必须保障容器中具有指定名称的bean,与变量名耦合度太高不推荐使用
- 自动装配优先级低于setter和构造器注入,如果同时出现,自动装配配置则失效
1.6.5.集合注入
<bean id="bookDao" class="com.chen.impl.BookDaoImpl">
<property name="array">
<array>
<value>100</value>
<value>200</value>
<ref bean="beanId"/>
<array>
<property>
<property name="map">
<map>
<entry key = "contry" value="china"/>
<entry key = "province" value="gansu"/>
<map>
<property>
<property name="properties">
<props>
<prop key="p1">aaa</prop>
<prop key="p2">bbb</prop>
<prop key="p3">ccc</prop>
</props>
</property>
1.6.6加载properties文件,配置第三方bean
<?xml version="1.0" encoding="UTF-8"?>
<!--spring文件头-->
<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
">
<!--system-properties-mode="NEVER"不加载系统属性,多个文件中间加","-->
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
<!--管理DruidDataSource对象-->
<bean id="dataSource" 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>
<bean id="bookDao" class="com.chen.dao.daoImpl.BookDaoImpl">
<property name="name" value="${username}"/>
</bean>
</beans>
1.7.容器
加载配置文件的两种方式:
//类路径加载配置文件
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//文件路径加载配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\IdeaProjects\\spring_datasource\\src\\main\\resources\\applicationContext.xml");
//加载多个配置文件
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml","bean2.xml");
获取bean
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
//按照类型,前提该类型的bean唯一
BookDao bean = ctx.getBean(BookDao.class);
初始化bean的时候可以设置延迟加载(加载配置文件时不构造对象)
- 用BeanFactory接口(所有容器类的顶层接口)的ClassPathResource实现类
- 使用ApplicationContext接口实现ClassPathXmlApplicationContext类,给bean标签加上lazy-init="true"属性
- BeanFactory是IOC容器的顶层接口,ApplicationContext接口时Spring容器的核心接口,初始化时bean立即加载
1.7.1.bean相关
<bean
id="bookDao" bean的Id
name="dao BookDaoImpl daoImpl" bean的别名
class="com.chen.dao.daoImpl.BookDaoImpl" bean类型,静态工厂类,FactoryBean类
scope="singleton" 控制bean的实例数量,单列,多例
init-method="init" 生命周期初始化方法
destroy-method="destroy" 生命周期销毁方法
autowire="byType" 自动装配类型
factory-method="getInstance" bean工厂方法,应用于静态工厂或实例工厂
factory-bean="com.itheima.factory.BookDaoFactory" 实例化工厂bean
lazy-init="true" 控制bean延迟加载
/>
2.IOC和DI注解开发
2.1.注解开发定义bean
- 使用@Component定义bean
@Component
public class BookServiceImpl implements BookService {
}
@Component("bookDao")
public class BookDaoImpl implements BookDao {
}
- 核心配置文件中通过组件扫描加载bean
<context:component-scan base-package="com.chen"/>
- Spring提供@Component注解的三个衍生注解
- @Controller:用于表现层bean定义
- @Service:用于业务层bean定义
- @Repository:用于数据层bean定义
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
}
@Service
public class BookServiceImpl implements BookService {
}
2.2纯注解开发
Spring3.0开启纯注解开发模式,使用java类代替配置文件
@Configuration
@Component("com.chen")
public class SpringConfig {
}
- @Configuration:设定当前类为配置类
- @CompontScan:设定扫描路径,多个数据用数组格式
@ComponentScan({"com.chen.service","com.itheima.dao"})
//获取容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
2.3. 纯注解开发中的bean生命周期
@Scope("singleton")
public class BookDaoImpl implements BookDao {
@PostConstruct//构造方法之后
public void init(){
System.out.println("init...");
}
@PreDestroy//销毁之前
public void destroy(){
System.out.println("destroy...");
}
}
public class App1 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
System.out.println(bookDao);
BookService bookService = ctx.getBean(BookService.class);
System.out.println(bookService);
ctx.close();
}
}
2.4.纯注解依赖注入
使用@Autowired注解开启自动装配模式
- 注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无法提供setter方法
- 注意:自动装配建议使用无参构造方法创建对象(默认),如果不提供无参构造方法,请提供唯一的构造方法
- 使用@Qualifier注解开启指定名称装配bean
@Autowired
@Qualifier("bookDao1")
private BookDao bookDao;
- 基本类型
@Value("123")
private String name;
- 如果value值要从配置文件获取,在配置文件加上@PropertySource(“配置文件名”)注解
@Configuration
@ComponentScan("com.chen")
@PropertySource("jdbc.properties")
public class SpringConfig {
}
@Value("${name}")
private String name;
2.5.管理第三方bean
public class JdbcConfig {
//1.定义一个方法获得要管理的对象
//2.添加@Bean,表示当前方法的返回值是一个bean,可以添加名字@Bean("dataSource")
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/tlias");
ds.setUsername("root");
ds.setPassword("2002");
return ds;
}
}
//这里有两种方式,@Import是第一种,多个用数组
//第二种是给JdbcConfig也加上@Configuration注解,然后再SpringConfig 里面加上@ComponentScan组件扫描
@Configuration
@Import({JdbcConfig.class})
public class SpringConfig {
}
- 第三方bean依赖注入
- 普通类型使用成员变量+@Value
- 引用类型只需要为bean定义方法设置形参,容器会根据类型自动装配
2.6.xml配置对比注解配置
2.7.Spring整合MyBatis
将MyBatis的xml配置文件写成配置类,
- Spring要管理SqlSessionFactory
- Spring要管理Mapper接口的扫描
具体步骤
1.导入需要的jar包
<dependency>
<!--Spring操作数据库需要该jar包-->
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<!--
Spring与Mybatis整合的jar包
这个jar包mybatis在前面,是Mybatis提供的
-->
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
2.编写数据源配置类和数据源
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
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;
}
}
数据配置jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/tlias?useSSL=false
jdbc.username=root
jdbc.password=2002
3.创建MyBatis配置类
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
//自动扫描并注册配置实体类的别名
ssfb.setTypeAliasesPackage("com.chen.domain");
//配置数据源
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
//设置mapper接口的包路径
msc.setBasePackage("com.chen.dao");
return msc;
}
}
4.创建主配置类
@Import({JdbcConfig.class, MybatisConfig.class})
@ComponentScan("com.chen")
@Configuration
@PropertySource("jdbc.properties")
public class SpringConfig {
}
5.编写主类
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService accountService = ctx.getBean(AccountService.class);
List<Account> all = accountService.findAll();
System.out.println(all);
}
}
2.8.Spring整合Junit
//设定类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//指定Spring配置类的注解,告诉Spring加载应用程序上下文
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
.......
}
}
3.AOP
- AOP面向切面编程,是一种编程范式,指导开发者如何组织程序结构
- OOP面向对象编程
- 作用:在不惊动原始设计的基础上为其进行功能增强
- Spring理念:无入侵式/无侵入式
3.1.核心概念
- 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
- 在SpringAOP中,理解为方法的执行
- 切入点(Pointcut):匹配连接点的式子(切入点在连接点中)
- 在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法
- 一个具体的方法:如com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法
- 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
- 连接点范围要比切入点范围大,是切入点的方法也一定是连接点,但是是连接点的方法就不一定要被增强,所以可能不是切入点。
- 在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法
- 通知(Advice):在切入点处执行的操作,也就是共性功能
- 在SpringAOP中,功能最终以方法的形式呈现
- 通知类:定义通知的类
- 切面(Aspect):描述通知与切入点的对应关系。
3.2.快速入门
1.导入相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!--AOP框架-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2.定义通知类,编写 通知,下面代码的method方法
3.定义切入点,下面代码的pt()方法
4.定义切面(绑定切入点与通知的关系),指定通知添加到原始方法的具体位置,这里的@Before(“pt()”)注解
5.交给Spring处理,添加@Component
@Aspect注解,并在Spring配置类中添加@EnableAspectJAutoProxy
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.chen.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
@Configuration
@ComponentScan("com.chen")
@EnableAspectJAutoProxy//开启注解格式AOP功能
public class SpringConfig {
}
3.3.工作流程
1.spring容器启动
2.读取所有切面配置的切入点
3.初始化bean,判断bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
4.获取bean执行方法 - 获取bean,调用方法并执行,完成操作
- 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作。
3.4.核心概念
- AOP的本质:代理模式
- 目标对象:原始功能去掉共性功能对应的类产生的对象,这种对象无法完成最终工作。
- 代理:目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
3.5.切入点表达式
- 两种描述方式:接口描述和实现类描述
- 标准格式:动作关键字(修饰访问符 返回值 包名.类名/接口名(参数)异常名)
- 访问修饰符和异常名可以省略
@Pointcut("execution(public void com.chen.dao.BookDao.findById(int))")
通配符
- *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符
- …:匹配多个连续的任意符号,可以独立出现,常用于简化报名与参数的书写
- +:专用于匹配子类类型(卸载接口或者类的后面)
3.5.1.书写技巧
对于切入点表达式的编写其实是很灵活的,那么在编写的时候,有没有什么好的技巧让我们用用:
- 所有代码按照标准规范开发,否则以下技巧全部失效
- 描述切入点通常描述接口,而不描述实现类,如果描述到实现类,就出现紧耦合了
- 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
- 包名书写尽量不使用…匹配,效率过低,常用*做单个包描述匹配,或精准匹配
- 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名
- 方法名书写以动词进行精准匹配,名词采用匹配,例如getById书写成getBy,selectAll书写成selectAll
- 参数规则较为复杂,根据业务方法灵活调整
- 通常不使用异常作为匹配规则
3.6.通知类型
五种类型,可以写pt(),也可以写成类名.pt()
- 前置通知@Before(“pt()”)
- 后置通知@After(“pt()”)
- 环绕通知@Around(“pt()”)
//因为不知道程序会不会有异常,所以要抛出异常
@Around("pt()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
System.out.printIn("around before advice ...");
//获取当前的类名和方法名,可以输出日志等等,
Signature signature = pjp.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
//表示原始操作的调用
pjp.proceed();
System.out.printIn("around before advice ...");
}
//对原始方法调用后,如果原始方法有返回值,需要接收返回值,并且把通知方法设置Object返回值
- 返回后通知@AfterReturning,只有方法没有抛出异常正常结束才行。
- 抛出异常后通知@AfterThrowing
3.7.通知获取数据
用JoinPoint或者ProceedingJoinPoint的getArgs接收参数
- 参数和返回值接收示例
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(String com.chen.dao.BookDao.findName(int))")
private void pt(){}
//接收参数示例
@Before("pt()")
public void method(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice...");
System.out.println(System.currentTimeMillis());
}
//接收参数和返回值示例
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
return pjp.proceed(args);
}
//接收返回值示例
@AfterReturning(value = "pt()",returning = "ret")
//写上接收返回值的形参名,如果有JoinPoint jp,这个必须为第一个参数
public void afterReturning(JoinPoint jp,Object ret){//定义一个接收返回值的形参
System.out.println("afterReturning advice..."+ret);
}
}
- 异常接收示例,“抛出异常后通知”接收异常与“返回后通知”接收返回值基本一致,环绕通知则是使用try-catch环绕
//接收异常示例
@Around("pt()")
public Object around(ProceedingJoinPoint pjp){
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
try {
return pjp.proceed(args);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
//接收异常示例
@AfterReturning(value = "pt()",returning = "throwable")
//写上接收异常的形参名,如果有JoinPoint jp,这个必须为第一个参数
public void afterReturning(JoinPoint jp,Throwable throwable){//定义一个接收异常的形参
System.out.println("afterReturning advice..."+throwable);
}
4.Spring事务
4.1.事务快速入门
1.一般在业务层接口上添加Spring事务管理@Transactional,降低耦合。当然也可以添加在接口,方法和实现类上。
2.设置事务管理器
//配置事务管理器,mybatis使用的是jdbc事务,这个PlatformTransactionManager 类是Spring官方提供
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
3.开启注解式事务驱动
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
4.2.事务角色
开启事务后,原来的事务1,事务2,会加入一个新的事务。
- 事务管理员:发起事务,在Spring中通常指代业务层开启事务的方法。
- 事务协调员:加入事务,在Spring中通常指代数据层方法,也可以是业务层方法。
4.3.事务相关配置
- 事务传播行为:事务协调员对事务管理员所携带事务的处理态度(事务是不是加入事务管理员的事务)
使用方式:给事务协调员加上注解,(我需要一个新事务,不加入事务管理员的事务)
public interface LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
void log(String out, String in, Double money);
}
5.SpringMVC
做表现层开发,相当于Servlet。
5.1.快速入门
1.导入maven依赖,SpringMVC坐标与Servlet坐标
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.28</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
2.创建SpringMVC控制器类(等同于servlet功能)
@Controller
public class UserController {
@ResponseBody
@RequestMapping("/sava")
public String save(){
System.out.println("User模块的save功能正在执行");
return "SpringMVC:save";
}
}
3.初始化SpringMVC环境(同Spring环境),设定SpringMVC加载对应的bean
@Configuration
@ComponentScan("com.chen.controller")
public class SpringMvcConfig {
}
4.初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理的请求。
//定义一个Servlet容器启动的配置类,在里面加载spring的配置
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//加载SpringMVC容器配置
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
//设置哪些请求归属SpringMVC处理
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
//加载spring容器配置
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
}
简写
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
//设置哪些请求归属SpringMVC处理
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
//加载SpringMVC容器配置
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
//加载Spring容器配置
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringConfig.class};
}
//POST请求中文乱码处理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
5.2.工作流程
- 启动服务器初始化过程
1.服务器启动,执行ServletContainersInitConfig
类,初始化web容器
2.执行createServletApplicationContext
方法,创建了WebApplicationContext
对象
3.WebApplicationContext
对象加载SpringMvcConfig
4.执行@ComponentScan
加载对应的bean
5.加载UserController
,每个@RequestMapping
的名称对应一个具体的方法
6.执行getServletMappings方法,定义所有的请求都通过SpringMVC - 单次请求过程
1.发送请求localhost:8080/save
2.web容器发现所有的请求都要经过SpringMVC,将请求交给SpringMVC处理
3.解析请求路径/save()
4.由/save
匹配执行对应的方法save()
5.执行save
6.监测到有@ResponseBodu直接将save()方法的返回值作为响应请求体返回给请求方。
5.3.bean加载控制
mvc控制controller中的bean,spring控制service和dao中的bean
- 加载spring相关bean的时候排除MVC相关bean
方式一: 设定扫描范围为con.chen,排除掉controller
@Configuration
//使用MyBatis配置类,可以不写扫描dao包,但是写上通用性更高,不写的话如果数据层换成别的技术就不适配了。
@ComponentScan({"com.chen.service","com.chen.dao"})
public class SpringMvcConfig {
}
方式二:设定精准扫描范围为service,dao等
@Configuration
@ComponentScan(value = "com.chen",
excludeFilters = @ComponentScan.Filter(
//过滤方式,这里为根据注解过滤
type = FilterType.ANNOTATION,
//过滤的注解类型,这里为过滤@Controller
classes = Controller.class
)
)
public class SpringMvcConfig {
}
- 方式三:不区分Spring和SpringMVC的环境,加载到同一个环境中
5.4.请求参数传递
5.4.1.普通参数传递
普通参数传递——get
普通参数传递——post
@ResponseBody
@RequestMapping("/sava")
public String save(String name,Integer age){
return R.success();
}
5.4.2.POJO类型参数传递
后台接收参数:
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}
注意:
- POJO参数接收,前端GET和POST发送请求数据的方式不变。
- 请求参数key的名称要和POJO中属性的名称一致,否则无法封装。
5.4.3.嵌套POJO类型参数传递
如果POJO对象中嵌套了其他的POJO类,如
public class Address {
private String province;
private String city;
//setter...getter...略
}
public class User {
private String name;
private int age;
private Address address;
//setter...getter...略
}
后台接收参数:
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}
5.4.4. 数组类型参数
- 数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型即可接收参数
后台接收参数:
//数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] likes){
System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes));
return "{'module':'array param'}";
}
5.4.5. 集合类型参数
//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
System.out.println("集合参数传递 likes ==> "+ likes);
return "{'module':'list param'}";
}
- 集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系
- 对于简单数据类型使用数组会比集合更简单些。
5.4.6.传递JSON数据
1、SpringMVC默认使用的是jackson来处理json的转换,所以需要在pom.xml添加jackson依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
2.postman设置
3.开启SpringMVC注解支持
@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
4.参数前添加@RequestBody
//使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> likes){
System.out.println("list common(json)参数传递 list ==> "+likes);
return "{'module':'list common for json param'}";
}
5.4.7.日期时间格式
//Date默认格式是yyyy/MM/dd HH:mm:ss
//@DateTimeFormat的pattern设置格式
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,@DateTimeFormat(pattern="yyyy-MM-dd")Date date1){
System.out.println("参数传递date"+date);
System.out.println("参数传递date1"+date1);
return "{'module':'data param'}";
}
5.5.响应参数传递
5.5.1.响应页面
@Controller
public class UserController {
@RequestMapping("/toJumpPage")
//注意
//1.此处不能添加@ResponseBody,如果加了该注入,会直接将page.jsp当字符串返回前端
//2.方法需要返回String
public String toJumpPage(){
System.out.println("跳转页面");
return "page.jsp";
}
}
5.5.2.响应字符串
@Controller
public class UserController {
@RequestMapping("/toText")
//注意此处该注解就不能省略,如果省略了,会把response text当前页面名称去查找,如果没有回报404错误
@ResponseBody
public String toText(){
System.out.println("返回纯文本数据");
return "response text";
}
}
5.5.3.响应JSON数据
@Controller
public class UserController {
@RequestMapping("/toJsonPOJO")
@ResponseBody
public User toJsonPOJO(){
System.out.println("返回json对象数据");
User user = new User();
user.setName("itcast");
user.setAge(15);
return user;
}
}
5.6.异常处理
5.6.1.异常处理器
出现异常现象的常见位置与常见诱因如下:
- 框架内部抛出的异常:因使用不合规导致
- 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
- 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
- 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
- 工具类抛出的异常:因工具类书写不严谨,不够健壮导致(例如:必要释放的链接长期未释放等)
解决方案:集中、统一的处理项目中出现的异常
创建异常处理器类,该类放在Controller下
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice{
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public R doException(Exception ex){
return new R.success(null)
}
}
5.6.2.项目异常处理
- 项目异常分类
- 业务异常(BusinessException)
- 规范的用户行为操作产生的异常
- 不规范的用户行为操作产生的异常
- 系统异常(SystemException)
- 项目运行过程中可预计无法避免的异常
- 其他异常(Exception)
- 编程人员未预见到的异常
- 业务异常(BusinessException)
- 项目异常处理方案
- 业务异常(BusinessException)
- 发送对应消息给用户,提醒规范操作
- 系统异常(SystemException)
- 发送固定消息传递个用户,安抚用户
- 发消息给运维
- 记录日志
- 其他异常(Exception)
- 发送固定消息传递个用户,安抚用户
- 发消息给开发(纳入预期范围内)
- 记录日志
1、自定义异常类,BusinessException与SystemException一致,下面只给吃一个代码示例
- 业务异常(BusinessException)
package com.chen.exception;
public class SystemException extends RuntimeException{//继承RuntimeException,异常向上抛出到controller
private Integer code;
public SystemException(Integer code,String message) {
super(message);
this.code = code;
}
public SystemException( Integer code,String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public SystemException( Integer code,Throwable cause) {
super(cause);
this.code = code;
}
}
2、将其他异常包成自定义异常
@GetMapping("/{id}")
public R<Book> getById(@PathVariable Integer id){
//模拟业务异常,包装成自定义异常
if (id==1){
throw new BusinessException("请不要用你的技术挑战我的耐性!!!");
}
//模拟系统异常,将有可能出现的异常进行包装,转换成自定义异常
try{
int i = 1/0;
}catch (Exception e){
throw new SystemException("服务器访问超时,请重试");
}
return R.success(bookService.getById(id));
}
3、处理器中处理自定义异常
@RestControllerAdvice
public class ProjectControllerAdvice {
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public R doException(){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return R.error("系统繁忙,请稍后再试");
}
//@ExceptionHandler用于设置当前处理器类对应的异常类型
@ExceptionHandler(SystemException.class)
public R doSystemException(SystemException ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
return R.error(ex.getMessage());
}
@ExceptionHandler(BusinessException.class)
public R doBusinessException(BusinessException ex){
return R.error(ex.getMessage());
}
}
5.7.拦截器
- 拦截器(interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
- 作用:
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
6.7.1.拦截器和过滤器的区别
- 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
- 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强
6.7.2.拦截器入门案例
1、声明拦截器bean ,并实现HandlerInterceptor接口(注意:扫描加载bean)
在controller包下新建interceptor包
@Component
public class ProjectInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle......");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle......");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion......");
}
}
2、定义配置类,继承WebMvcConfigurationSupport,实现addInterceptor方法(注意:扫描加载配置),添加拦截器并设定拦截的访问路径,路径可以通过可变参数设置多个。
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
}
}
5.6.3.拦截器参数
- request:请求对象
- response:响应对象
- handler:被调用的处理器对象,本质上是一个方法对象,对反射中的Method对象进行了再包装
- modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整
- ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理
5.6.4.拦截器链
- 当配置多个拦截器时,形成拦截器链
- 拦截器链的运行顺序参照拦截器添加顺序为准
- 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
- 当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作
运行顺序
- preHandle:与配置顺序相同,必定运行
- postHandle:与配置顺序相反,可能不运行
- afterCompletion:与配置顺序相反,可能不运行。