代理模式
代理模式是常见的设计模式之一,顾名思义,代理模式就是代理对象具备真实对象的功能,并代替真实对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理。(为真实对象提供代理,然后供其他对象通过代理访问真实对象)
以计算器为例。
-
接口
-
实现类
-
静态代理
从静态代理的代码中可以看出,当实现类的方法越来越多的时候,这样构建方法的代码量是非常大的,所以我们就引进jdk动态代理(基于接口)。
-
动态代理
AOP概念及相关术语
概念
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
相关术语
-
横切关注点
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
-
通知
每一个横切关注点上要做的事情 都需要写一个方法来实现,这样的方法就叫通知方法。
-
前置通知:在被代理的目标方法前执行
-
返回通知:在被代理的目标方法成功结束后执行
-
异常通知:在被代理的目标方法异常结束后执行
-
后置通知:在被代理的目标方法最终结束后执行
-
环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知所对应的所有位置。
-
切面
封装通知方法的类。
-
目标
被代理的目标对象
-
代理
向目标对象应用通知后创建的代理对象
-
连接点 横切关注点的位置
-
切入点 定位连接点的方式
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)
如果把连接点看作数据库中的记录,那么切入点就是查询记录的SQL语句
Spring的AOP技术可以通过切入点定位到特定的连接点。
切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
作用
-
简化代码:把方法中固定位置的重复代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
-
代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。
基于注解的AOP实现
技术说明
-
动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)
-
cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
-
AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
-
引入依赖
在IOC所需依赖基础上再加入下面依赖即可:
<!-- spring-aspects会帮我们传递过来aspectjweaver --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version> </dependency>
2.准备被代理的目标资源
package com.my.spring.aop.annotation; public interface Calculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); }@Component public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; System.out.println("方法内部, result:" + result); return result; } @Override public int sub(int i, int j) { int result = i - j; System.out.println("方法内部, result:" + result); return result; } @Override public int mul(int i, int j) { int result = i * j; System.out.println("方法内部, result:" + result); return result; } @Override public int div(int i, int j) { int result = i / j; System.out.println("方法内部, result:" + result); return result; } }
3.交给IOC容器管理
<!-- AOP的注意事项: 切面类和目标类都需要交给IOC容器管理 切面类必须通过@Aspect注解标识为一个切面 在spring的配置文件中设置aop:aspectj-autoproxy标签开启基于注解的AOP --> <context:component-scan base-package="com.my.spring.aop.annotation"></context:component-scan> <!-- 开启基于注解的AOP --> <aop:aspectj-autoproxy />
4. 实现
/** *在切面中:需要通过指定的注解将方法标识为通知方法 * @before:前置通知,在目标对象方法执行之前执行 * @After:后置通知,在目标方法的finally子句中执行的 * @AfterReturning:返回通知,在目标对象方法返回值之后执行 * @AfterThrowing:异常通知,在目标对象方法的catch子句中执行 * * * 切入点表达式:设置在标识通知的注解的value属性中 * execution(public int com.my.spring.aop.annotation.CalculatorImpl.add(int,int ) * execution(* com.my.spring.aop.annotation.CalculatorImpl.*(..)) * 第一个*表示任意的访问修饰符和返回值类型 * 第二个*表示类中任意的方法 * ..表示任意的参数列表 * 类的地方也可以使用*,表示包下所有的类 * 3.重用切入点表达式 * // @Pointcut声明一个公共的切入点表达式 * @Pointcut("execution(* com.my.spring.aop.annotation.CalculatorImpl.*(..))") * public void pointCut(){ * * } * 使用方式:@Before("pointcut()") * * * 4.获取连接点的信息 * 在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点对应方法的信息 * Signature signature = joinPoint.getSignature(); * // 获取连接点所对应方法的参数 * Object[] args = joinPoint.getArgs(); * 5.切面的优先级 * 可以通过@Order注解的value属性设置优先级,默认值Integer的最大值 * @Order注解的value属性值越小,优先级越高 */ @Component @Aspect //将当前组件标识为切面 public class LoggerAspect { @Pointcut("execution(* com.my.spring.aop.annotation.CalculatorImpl.*(..))") public void pointCut(){ } //@Before("execution(public int com.my.spring.aop.annotation.CalculatorImpl.add(int,int ))") @Before("pointCut()") public void beforeAdviceMethod(JoinPoint joinPoint){ // 获取连接点所对应方法的签名信息 Signature signature = joinPoint.getSignature(); // 获取连接点所对应方法的参数 Object[] args = joinPoint.getArgs(); System.out.println("LoggerAspect,方法:" + signature + ",参数:" + Arrays.toString(args)); } @After("pointCut()") public void afterAdviceMethod(JoinPoint joinPoint){ Signature signature = joinPoint.getSignature(); Object[] args = joinPoint.getArgs(); System.out.println("LoggerAspect,方法:" + signature + ",执行完毕" ); } /** * 在返回通知中若要获取目标对象方法的返回值, * 只需要通过@AfterReturnint注解的returning属性 * 就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数 */ @AfterReturning(value = "pointCut()",returning = "result") public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){ System.out.println("LoggerAspect, 方法:" + joinPoint.getSignature() + ",结果:" + result); } /** * 在异常通知中若要获取目标对象方法的异常, * 只需要通过@AfterThrowing注解的throwing属性 * 就可以将通知方法的某个参数指定为接收目标对象方法出现的异常的参数 */ @AfterThrowing(value = "pointCut()", throwing = "ex") public void afterThrowingAdviceMethod(JoinPoint joinPoint, Exception ex){ Signature signature = joinPoint.getSignature(); System.out.println("LoggerAspect,方法:" + signature.getName() + "异常通知:" + ex); } @Around("pointCut()") // 环绕通知的方法的返回值一定要和目标对象方法的返回值一致 public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){ Object result = null; try { System.out.println("环绕通知-->前置通知"); // 表示目标对象方法的执行 result = joinPoint.proceed(); System.out.println("环绕通知-->返回通知"); } catch (Throwable e) { e.printStackTrace(); System.out.println("环绕通知-->异常通知"); }finally { System.out.println("环绕通知-->后置通知"); } return result; } }
5.结果