一、一站式轻量级开源框架SpringAOP实际应用篇(零基础完整精细版)
问题引入:
传统java开发中,核心方法与横切关注点耦合:
:
我们下面来看如何解决这个问题(把横切关注点从核心方法中剥离抽取出来到其他类中,不再耦合):
1java自身的动态代理
2 使用SpringAOP实现
有关于动态代理的面试题:
二:AOP简介
1、OOP编程思想回顾
面向对象程序设计(Object Oriented Programming),其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。是一种计算机编程架构。OOP的一条基本原则是计算机程序由单个能够起到子程序作用的单元或对象组合而成。OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。OOP=对象+类+继承+多态+消息,其中核心概念是类和对象。
2、AOP概念
AOP:Aspect Oriented Programming的缩写,意为:面向切面编程。
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。
3、AOP应用场景
一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。
原始版:
这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。
优化版:
同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。
最终版:
4、AOP相关概念
横切(cross cutting):
OOP编程中,日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
切面(Aspect)
:横切关注点模块化(封装成类),即:把那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
连接点(Joint point)
:被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
切入点(Pointcut)
:定义了拦截“何处”的连接点的表达式,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
通知/增强(Advice
):定义了“何时”调用横切关注点,指横切关注点在连接点方法被调用的某个时间点执行。通知分为前置、后置、异常、最终、环绕通知五类。
目标对象(Target)
:织入 Advice 的目标对象,代理的目标对象。
织入(Weaving)
:将切面应用到目标对象并导致代理对象创建的过程。
5、AOP环境搭建
导入AOP的maven依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<!--使用AspectJ方式注解需要相应的包 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.11</version>
</dependency>
<!--使用AspectJ方式注解需要相应的包 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.11</version>
</dependency>
导入AOP命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
三、基于XML配置方式实现AOP
<!--***************bean**************-->
<bean id="deptServiceImpl" class="com.alibaba.spring.service.DeptServiceImpl"></bean>
<!--***************aspect************-->
<bean id="deptAspect" class="com.alibaba.spring.aspect.DeptAspect"></bean>
<!--****************AOP**************-->
<aop:config>
<!--定义全局切入点表达式-->
<aop:pointcut id="deptPointCut"
expression="execution(* com.alibaba.spring.service.DeptServiceImpl.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect ref="deptAspect">
<!--配置前置通知-->
<aop:before method="check" pointcut-ref="deptPointCut"></aop:before>
</aop:aspect>
</aop:config>
3.1定义业务层接口
/**
* 业务层接口
* Created by Administrator on 2019/8/15 0015.
*/
public interface DeptService {
public void insert();
public void update();
public void delete();
public void select();
}
3.2定义业务层实现类
/**
* Created by Administrator on 2019/8/15 0015.
*/
public class DeptServiceImpl implements DeptService{
@Override
public void insert() {
System.out.println("insert()......");
}
@Override
public void update() {
System.out.println("update()......");
}
@Override
public void delete() {
System.out.println("delete()......");
}
@Override
public void select() {
System.out.println("select()......");
}
}
3.3定义切面类
/**
* 切面(类)
* Created by Administrator on 2019/8/15 0015.
*/
public class DeptAspect {
/**权限校验*/
public void check() {
System.out.println("权限校验......");
}
/**日志记录*/
public void log(){
System.out.println("日志记录......");
}
}
3.4配置文件中添加AOP配置
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--***************bean**************-->
<bean id="deptServiceImpl" class="com.alibaba.spring.service.DeptServiceImpl"></bean>
<!--***************aspect************-->
<bean id="deptAspect" class="com.alibaba.spring.aspect.DeptAspect"></bean>
<!--****************AOP**************-->
<aop:config>
<!--定义全局切入点表达式-->
<aop:pointcut id="deptPointCut"
expression="execution(* com.alibaba.spring.service.DeptServiceImpl.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect ref="deptAspect">
<!--定义局部切入点表达式
<aop:pointcut id="deptPointCut"
expression="execution(* com.alibaba.spring.service.DeptServiceImpl.*(..))"></aop:pointcut>-->
<!--配置前置通知-->
<aop:before method="check" pointcut-ref="deptPointCut"></aop:before>
</aop:aspect>
</aop:config>
</beans>
3.5定义测试类
/**
* Created by Administrator on 2019/8/14 0014.
*/
public class Main {
public static void main(String[] args) {
//加载ioc容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
DeptService service=context.getBean(DeptService.class);
service.insert();
context.close();
}
}
3.6aspectJ表达式结构
切入点表达式 execution (* com.sample.service.impl..*.*(..))
1、execution(): 表达式主体。
2、第一个*号:表示返回类型,*号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个*号:表示类名,*号表示所有的类。
5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
3.7通知
<aop:before method="" pointcut-ref=""/>
method:切面中的方法名称
pointcut-ref:切入点表达式的id
3.7.1前置通知
@Before:前置通知:在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
切面中的方法:
public void check() {
System.out.println("权限校验......");
}
业务层中的方法:
public void insert() {
System.out.println("insert()......");
}
xml文件配置:
<aop:before method="check" pointcut-ref="deptPointCut"></aop:before>
3.7.2后置通知
@AfterReturning :后置通知:在某连接点正常完成后执行的通知,通常在一个匹配的方法返回的时候执行。
切面中的方法:
public void log(){
System.out.println("日志记录......");
}
业务层中的方法:
public void update() {
System.out.println("update()......");
}
xml文件配置:
<aop:after-returning method="log" pointcut-ref="deptPointCut"></aop:after-returning>
3.7.3环绕通知
@Around:环绕通知:包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
环绕通知最麻烦,也最强大,其是一个对方法的环绕,具体方法会通过代理传递到切面中去,切面中可选择执行方法与否,执行方法几次等。
环绕通知使用一个代理ProceedingJoinPoint类型的对象来管理目标对象,所以此通知的第一个参数必须是ProceedingJoinPoint类型,在通知体内,调用ProceedingJoinPoint的proceed()方法会导致后台的连接点方法执行。proceed 方法也可能会被调用并且传入一个Object[]对象-该数组中的值将被作为方法执行时的参数。
切面中的方法:
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
// 此位置的代码在连接点方法执行之前执行
System.out.println("前......");
Object retVal = pjp.proceed();
// 此位置的代码在连接点方法执行之后执行
System.out.println("后......");
return retVal;
}
业务层中的方法:
public void delete() {
System.out.println("delete()......");
}
xml文件配置:
<aop:around method="aroundMethod" pointcut-ref="deptPointCut"></aop:around>
3.7.4异常通知
@AfterThrowing:异常通知:在方法抛出异常退出时执行的通知。
切面中的方法:
public void exceptionMethod(){
System.out.println("核心方法发生异常了,该我出场了......");
}
业务层中的方法:
public void insertExcption() {
System.out.println("insert()......");
throw new RuntimeException("我故意抛的异常呀");
}
xml文件配置:
<aop:after-throwing method="exceptionMethod" pointcut-ref="deptPointCut"></aop:after-throwing>
3.7.5最终通知
@After 最终通知:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
切面中的方法:
public void afterMethod(){
System.out.println("不惧挫折,坚持到最后......");
}
业务层中的方法:
public void select() {
System.out.println("select()......");
}
xml文件配置:
<aop:after method="afterMethod" pointcut-ref="deptPointCut"></aop:after>
3.7.6全局切入点、局部切入点、通知范围切入点
全局切入点:多个切面共享
<aop:config>
<!--定义全局切入点表达式-->
<aop:pointcut id="deptPointCut"
expression="execution(* com.alibaba.spring.service.DeptServiceImpl.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect ref="deptAspect">
<aop:before method="check" pointcut-ref="deptPointCut"></aop:before>
</aop:aspect>
</aop:config>
局部切入点:一个切面共享
<aop:config>
<!--配置切面-->
<aop:aspect ref="deptAspect">
<aop:pointcut id="deptPointCut"
expression="execution(* com.alibaba.spring.service.DeptServiceImpl.*(..))"></aop:pointcut>
<aop:after-throwing method="exceptionMethod" pointcut-ref="deptPointCut"></aop:after-throwing>
</aop:aspect>
</aop:config>
通知范围切入点:当前通知内使用
<aop:config>
<!--配置切面-->
<aop:aspect ref="deptAspect">
<aop:after-throwing method="exceptionMethod" pointcut="execution(* com.alibaba.spring.service.DeptServiceImpl.*(..))"></aop:after-throwing>
</aop:aspect>
</aop:config>
3.8连接点方法参数及返回值的获取
3.8.1在前置通知中获取方法参数
3.8.1.1业务层方法
public void insert(String name,int age,Date date) {
System.out.println(“insert()…”+name+" “+age+” “+date.toString());
}
3.8.1.2切面中方法
public void check(String userName,int userAge,Date birth) {
System.out.println(“权限校验…”+userName+” “+userAge+” "+birth);
}
3.8.1.3切入点表达式
<aop:pointcut id=“deptPointCut”
expression=“execution(* com.alibaba.spring.service.DeptServiceImpl.*(…)) and args(userName,userAge,birth)”></aop:pointcut>
注意:切入点表达式中设定的参数名必须与切面方法中的参数名称相同
3.8.2在后置通知中获取方法返回值
3.8.2.1业务层方法
public List select() {
System.out.println(“select()…”);
List list=new ArrayList();
list.add(new Date());
list.add(new Date());
list.add(new Date());
return list;
}
3.8.2.2切面中方法
public void log(List list) {
System.out.println(“日志记录…”+list);
}
3.8.2.3切入点表达式及后置通知
<aop:pointcut id=“deptPointCut”
expression=“execution(* com.alibaba.spring.service.DeptServiceImpl.*(…))”></aop:pointcut>
<aop:after-returning method=“log” pointcut-ref=“deptPointCut” returning=“list”></aop:after-returning>
3.8.3在环绕通知中获取方法参数及返回值
3.8.3.1业务层方法
public List select(int id) {
System.out.println(“select()…”+id);
List list=new ArrayList();
list.add(new Date());
list.add(new Date());
list.add(new Date());
return list;
}
3.8.3.2切面中方法
public Object aroundMethod(ProceedingJoinPoint pjp,int id) throws Throwable {
// 此位置的代码在连接点方法执行之前执行
System.out.println(“前…”+id);
Object retVal = pjp.proceed();
// 此位置的代码在连接点方法执行之后执行
System.out.println(“后…”+retVal);
return retVal;
}
3.8.3.3切入点表达式及环绕通知
<aop:pointcut id=“deptPointCut”
expression=“execution(* com.alibaba.spring.service.DeptServiceImpl.*(…)) and args(id)”></aop:pointcut>
<aop:around method=“aroundMethod” pointcut-ref=“deptPointCut”></aop:around>
四 基于Annotation配置方式实现AOP
4.1环境搭建
4.1.1spring业务层
@Service
public class DeptServiceImpl implements DeptService{
…
}
4.1.2切面类
@Aspect
@Component
public class DeptAspect {
…
}
4.1.3spring配置文件
<context:component-scan base-package=“com.alibaba.spring”></context:component-scan>
aop:aspectj-autoproxy</aop:aspectj-autoproxy>
4.2全局和局部切入点
4.2.1全局切入点
@Aspect
@Component
public class DeptAspect {
/**
* 全局切入点表达式
/
@Pointcut("execution( com.alibaba.spring.service.DeptServiceImpl.insert(…)) and args(userName,userAge,birth)")
public void beforePointCut(){
}
/**
* 前置通知权限校验
*/
@Before("beforePointCut() && args(userName,userAge,birth)")
public void check(String userName, int userAge, Date birth) {
System.out.println("权限校验......" + userName + " " + userAge + " " + birth);
}
}
4.2.2局部切入点
/**
- 权限校验
/
@Before("execution( com.alibaba.spring.service.DeptServiceImpl.insert(…)) and args(userName,userAge,birth)")
public void check(String userName, int userAge, Date birth) {
System.out.println(“权限校验…” + userName + " " + userAge + " " + birth);
}
4.3前置通知
4.3.1局部无参
@Before(“execution(* com.alibaba.spring.service.DeptServiceImpl.insertNoParam(…))”)
public void check() {
System.out.println(“权限校验…”);
}
4.3.2局部有参
@Before(“execution(* com.alibaba.spring.service.DeptServiceImpl.insert(…)) and args(userName,userAge,birth)”)
public void check(String userName, int userAge, Date birth) {
System.out.println(“权限校验…” + userName + " " + userAge + " " + birth);
}
4.3.3全局无参
/**
- 全局无参切入点
/
@Pointcut("execution( com.alibaba.spring.service.DeptServiceImpl.insertNoParam(…))")
public void beforePointCut(){
}
/**
- 前置通知
*/
@Before(“beforePointCut()”)
public void check() {
System.out.println(“权限校验…”);
}
4.3.4全局有参
/全局有参切入点/
@Pointcut("execution( com.alibaba.spring.service.DeptServiceImpl.insertParam(…)) and args(userName,userAge,birth)")
public void beforePointCut(){
}
/*有参前置通知/
@Before(“beforePointCut() && args(userName,userAge,birth)”)
public void check(String userName, int userAge, Date birth) {
System.out.println(“权限校验…” + userName + " " + userAge + " " + birth);
}
4.4后置通知
4.4.1局部切入点无返回值
/**
- 无返回值后置通知
/
@AfterReturning(pointcut ="execution( com.alibaba.spring.service.DeptServiceImpl.selectNoReturn(…))")
public void log() {
System.out.println(“日志记录无返回值…”);
}
4.4.2局部切入点有返回值
/局部有返回值/
@AfterReturning(pointcut ="execution( com.alibaba.spring.service.DeptServiceImpl.select(…))",returning = “list”)
public void log(List list) {//list为返回值接收参数
System.out.println(“日志记录…” + list);
}
4.4.3全局切入点无返回值
/全局无返回值/
@Pointcut("execution( com.alibaba.spring.service.DeptServiceImpl.selectNoReturn(…))")
public void afterReturningPointCut(){
}
/*全局无返回值/
@AfterReturning(“afterReturningPointCut()”)
public void log(){
System.out.println(“日记记录全局无返回值…”);
}
4.4.4全局切入点有返回值
/全局无返回值/
@Pointcut("execution( com.alibaba.spring.service.DeptServiceImpl.select(…))")
public void afterReturningPointCut(){
}
/*全局有返回值/
@AfterReturning(value = “afterReturningPointCut()”,returning = “list”)
public void log(List list){
System.out.println(“日记记录全局有返回值…”+list);
}
4.5环绕通知
4.5.1局部有参数有返回值
/**
- 环绕通知
/
@Around("execution ( com.alibaba.spring.service.DeptServiceImpl.select(…)) and args(id)")
public Object aroundMethod(ProceedingJoinPoint pjp, int id) throws Throwable {
// 此位置的代码在连接点方法执行之前执行
System.out.println(“前…” + id);
Object retVal = pjp.proceed();
// 此位置的代码在连接点方法执行之后执行
System.out.println(“后,方法返回值…” + retVal);
return retVal;
}
4.5.2全局有参数有返回值
@Pointcut(“execution (* com.alibaba.spring.service.DeptServiceImpl.select(…)) and args(id)”)
public void aroundPointCut(){
}
@Around(“aroundPointCut() && args(id)”)
public Object aroundMethod(ProceedingJoinPoint pjp, int id) throws Throwable {
// 此位置的代码在连接点方法执行之前执行
System.out.println(“前…” + id);
Object retVal = pjp.proceed();
// 此位置的代码在连接点方法执行之后执行
System.out.println(“后,方法返回值…” + retVal);
return retVal;
}
4.6异常通知
4.6.1局部切入点
/**
- 异常通知
/
@AfterThrowing("execution ( com.alibaba.spring.service.DeptServiceImpl.insertExcption(…))")
public void exceptionMethod() {
System.out.println(“核心方法发生异常了,该我出场了…”);
}
4.6.2全局切入点
@Pointcut(“execution (* com.alibaba.spring.service.DeptServiceImpl.insertExcption(…))”)
public void exceptionPointCut(){
}
@AfterThrowing(“exceptionPointCut()”)
public void exceptionMethod() {
System.out.println(“核心方法发生异常了,该我出场了…”);
}
4.7最终通知
4.7.1局部切入点
/**
- 最终通知
/
@After("execution ( com.alibaba.spring.service.DeptServiceImpl.insertExcption(…))")
public void afterMethod() {
System.out.println(“不惧挫折,检查到最后…”);
}
4.7.2全局切入点
@Pointcut(“execution (* com.alibaba.spring.service.DeptServiceImpl.insertExcption(…))”)
public void afterPointCut(){
}
@After(“afterPointCut()”)
public void afterMethod() {
System.out.println(“不惧挫折,检查到最后…”);
}