1.AOP概念和作用
1.1概念
它是一种编程范式,用于指导开发者如何组织程序结构
1.2.作用
在不惊动原始设计的基础上对其进行功能代码增强
1.3.底层
通过动态代理来实现的,Spring官方提供了两种代理方式,JDK和第三方cglib ,所以AOP就是为了简化动态代理开发
1.4AOP的核心概念
-
切入点(Pointcut):定义(通过表达式或注解)哪些方法需要被增强,例如:update()、delete()方法,select()方法没有被增强所以不是切入点,但是是连接点。
-
连接点(JoinPoint):被增强的方法(可能被织入的地方),例如:update()、delete()、select()等都是连接点。
-
通知(Advice):也就是在切入点目标方法前后需要执行的增强代码操作,一般都是需要增强的公共功能代码
-
在SpringAOP中,增强代码操作都是以方法的形式呈现(也就是需要将增强的代码封装为方法)
-
-
通知类:通知方法所在的类叫做通知类,该通知类一般最终会升级为切面类
-
切面(Aspect):描述通知与切入点的对应关系,一般都是一个Java类(旧的配置模式中就是一个xml文件),在该切面类中进行配置描述哪些通知方法对应哪些切入点方法。
举例:
Spring AOP实现方法调用前后打印日志
首先就是创建一个业务服务类,用于模拟一些业务方法
再定义一个切面类,用来封装日志记录的一些逻辑,使用切入点和通知确定需要增强的代码操作,再业务类中再用连接点插入日志记录的代码
2.AOP工作流程
2.1工作流程
1.使用Spring容器进行启动
2.读取所有切面配置中的切入点(所有的切面配置类都进行了IOC开发)
3.初始化目标Bean(也就是被增强的类),判定bean对应的类中的方法是否匹配到任意切入点
-
匹配失败,创建原始对象
-
匹配成功,创建原始对象(目标对象)的代理对象
4.获取bean执行方法
-
获取的bean是原始对象时,调用方法并执行,完成操作
-
获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
2.2知识点拆开
目标对象(Target):被代理的对象,也叫原始对象,该对象中的方法没有任何功能增强。 代理对象(Proxy):代理后生成的对象,由Spring帮我们创建代理对象。
Spring框架底层创建代理对象方式选择:两种方式
1)如果目标对象实现了任意接口,就使用JDK方式创建代理对象(使用Proxy类创建代理对象)
2)如果目标对象没有实现任意接口,就使用第三方cglib方式创建代理对象(使用Enhancer类创建代理对象)
3.切入点理解
切入点:匹配要进行增强的方法
切入点表达式:要进行增强的方法的描述方式
-
描述方式一:执行com.yaorange.service包下的BookService接口中的无参数save方法
execution(void com.yaorange.service.BookService.save())
描述方式二:执行void com.yaorange.service.impl包下的BookDaoImpl类中的无参数save方法
execution(void com.yaorange.service.impl.BookServiceImpl.save())
-
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数类型列表) 异常名)
-
修饰符可以省略:动作关键字(返回值 包名.类/接口名.方法名(参数类型列表) 异常名)
通配符
目的:可以使用通配符描述切入点,快速描述。
*:表示单个任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
.. :标识多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
参考写法:一般包名会完整指定,目的提高框架的查找效率
业务方法开发隐式规则:方法名建议使用统一前缀,如:查询使用get开发,新增使用add开头,修改使用update开头,删除使用remove开头
*:表示任意,但是必须有内容
..:表示任意,有无均可
//匹配com.yaorange.service.impl包中所有以ServiceImpl结尾的接口类中所有get开头方法,方法参数任意
//匹配指定包中多个类中多个方法
@Pointcut("execution(* com.yaorange.service.impl.*ServiceImpl.get*(..))")
public void pt(){}
//匹配指定包中多个类中所有方法
@Pointcut("execution(* com.yaorange.service.impl.*ServiceImpl.*(..))")
public void pt(){}
//匹配指定包中所有类中所有方法
@Pointcut("execution(* com.yaorange.service.impl.*.*(..))")
public void pt(){}
4。基于注解开发
4.2.1 开发步骤:
1)自定义注解
注意点:
1.@Target需要开发者根据需要选择,注解可用位置,
ElementType.TYPE:类上使用
ElementType.METHOD:方法上使用
2.注解属性,定义语句是基于方法级,default指定默认返回值
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
//在注解中定义的属性,是用于反射时获取属性值进行逻辑判断(注解属性照顾是基于getXXX获取的)
//实际应用,参考后期若依项目环境已有功能学习(后期模拟面试必须能说明,罚款10元班费)
//语法: 数据类型 属性名() default 默认值;
//如: String title() default "";
// int code() default 1;
}
2)在开发切入点的注解中关联自定义注解
语法:@Pointcut("@annotation(自定义注解的路径)")
@Pointcut("@annotation(com.yaorange.annotation.Log)")
public void pt(){}//切入点方法是空方法
3)在需要被增强的方法上使用自定义标识
@Log
public void save() {
System.out.println("book service save ...");
}
4.AOP通知类型
4.1分类
-
前置通知:在切入点方法执行之前执行
-
后置通知:在切入点方法执行之后执行,如果切入点方法内部出现异常将不会执行。
-
异常通知:在切入点方法执行之后执行,只有当切入点方法内部出现异常之后才执行。
-
最终通知:在切入点方法执行之后执行,无论切入点方法内部是否出现异常,最终通知都会执行。
-
环绕通知(重点):手动调用切入点方法并对其进行增强的通知方式。
4.2通知详解
4.2.1 前置通知
-
名称:@Before
-
类型:==方法注解==
-
位置:通知方法定义上方
-
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
例:
@Before("pt()")
public void before() {
System.out.println("before advice ...");
}
4.2.2 最终通知
-
名称:@After
-
类型:==方法注解==
-
位置:通知方法定义上方
-
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后(不管是否出现异常)运行
@After("pt()")
public void after() {
System.out.println("after advice ...");
}
4.2.3 后置通知
-
名称:@AfterReturning
-
类型:==方法注解==
-
位置:通知方法定义上方
-
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行后运行
@AfterReturning("pt()")
public void afterReturning() {
System.out.println("afterReturning advice ...");
}
4.2.4 异常通知
-
名称:@AfterThrowing(了解)
-
类型:==方法注解==
-
位置:通知方法定义上方
-
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
@AfterThrowing("pt()")
public void afterThrowing() {
System.out.println("afterThrowing advice ...");
}
5.2.5 环绕通知
-
名称:@Around
-
类型:==方法注解==
-
位置:通知方法定义上方
-
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
-
特点:被标识方法必须定义ProceedingJoinPoint参数,ProceedingJoinPoint框架在执行该方法时会自动给参数依赖注入,通过参数对象可以操控目标方法
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
Object rs = pjp.proceed();
//Object[] args = pjp.getArgs();//获取调用目标方法时传入的参数值
// 省略:对args参数值进行调整
//Object rs =pjp.proceed(args);//只有需要对目标方法执行的参数值进行调整时,才使用有参数的执行方法
System.out.println("around after advice ...");
return rs;
}
==环绕通知注意事项==
-
环绕通知方法形参必须是ProceedingJoinPoint,表示正在执行的连接点,使用该对象的proceed()方法表示对原始对象方法进行调用,返回值为原始对象方法的返回值。
-
环绕通知方法的返回值建议写成Object类型,用于将原始对象方法的返回值进行返回,哪里使用代理对象就返回到哪里。
5.Spring事务管理
1.事务管理简介
1.1作用
事务作用:保障一系列的数据库sql操作要么全部成功要么全部失败
-
Spring事务管理作用:在==业务层==保障一系列的数据库操作同成功同失败(业务方法中同时调用多个mapper接口方法)
-
Spring的事务管理:
-
开发者在Spring配置类中开启事务支持,并配置事务管理器IOC配置
-
开发者需要在对应的业务方法上使用事务管理注解(该注解也就是一个切入点注解,由官方提供)
-
框架底层基于事务管理器(Spring官方开发的类)提供的方法,对业务层目标方向进行事务控制,如:(事务提交,事务回滚...),官方是通过AOP思想完成事务控制方法的增强执行
-
Spring事务管理配置:(重点)
【第一步】开启事务管理注解底层驱动
使用注解:@EnableTransactionManagement
【第二步】设置事务管理器(将事务管理器添加到IOC容器中)
说明:可以直接在SpringConfig中配置事务管理器,也可以单独新增配置类配置
@Configuration
@ComponentScan({"com.yaorange.service.impl"})
@EnableTransactionManagement//开启Spring的事务管理,提供事务注解的底层实现
@Import({JdbcConfig.class,MyBatisConfig.class})
public class SpringConfig {
//配置事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
注意事项
-
事务管理器要根据实现技术进行选择(基于MyBatis框架需要使用DataSourceTransactionManager)
-
MyBatis框架使用的是JDBC事务(<transactionManager type="JDBC">)
【第三步】在业务层实现类上或方法添加Spring事务管理注解
开发建议:初期开发时可以在类上直接使用注解,后期代码优化时将注解移植到有更新操作的业务方法上
注意事项:注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到类上表示当前类中所有方法开启事务,实际开发中建议给包含更新操作的业务方法添加注解
6.Spring事务角色
6.1 Spring事务角色
-
事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
-
事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是其他业务类的方法