- 1. 简介
- 2. 术语
- 3. 常用的AOP通知
- 4. 引入(Introduction)
- 5. 五种通知(增强)代码实现
- 5.1 匹配方法执行连接点
- 5.2 对注解
面向切面编程,相对于OOP面向对象编程 Spring的AOP的存在目的是为了解耦。AOP可以让一组类共享相同的行为。在OOP中只能继承和实现接口,且类继承只能单继承,阻碍更多行为添加到一组类上,AOP弥补了OOP的不足。
还有就是为了清晰的逻辑,让业务逻辑关注业务本身,不用去关心其它的事情,比如事务。
Spring的AOP是通过JDK的动态代理和CGLIB实现的。
2. 术语- 通知(有的地方叫增强)(Advice):需要完成的工作叫做通知,就是你写的业务逻辑中需要比如事务、日志等先定义好,然后需要的地方再去用。
- 连接点(Join point):就是spring中允许使用通知的地方,基本上每个方法前后抛异常时都可以是连接点。
- 切点(Poincut):其实就是通知和切点的结合,通知和切点共同定义了切面的全部内容,它是干什么的,什么时候在哪执行
- 引入(Introduction):在不改变一个现有类代码的情况下,为该类添加属性和方法,可以在无需修改现有类的前提下,让它们具有新的行为和状态。其实就是把切面(也就是新方法属性:通知定义的)用到目标类中去
- 目标(target):被通知的对象。也就是需要加入额外代码的对象,也就是真正的业务逻辑被组织织入切面。
- 织入(Weaving):把切面加入程序代码的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:
- 编译期:切面在目标类编译时被织入,这种方式需要特殊的编译器
- 类加载期:切面在目标类加载到JVM时被织入,这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码
- 运行期:切面在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP就是以这种方式织入切面的。
例子:
public class TestAopService { void save(){} void list(){} }
TestAopService 中的save()方法前需要开启事务,在方法后关闭事务,在抛异常时回滚事务。
UserService中的所有方法都是连接点(JoinPoint),save()方法就是切点(Poincut)。需要在save()方法前后执行的方法就是通知(Advice),切点和通知合起来就是一个切面(Aspect)。save()方法就是目标(target)。把想要执行的代码动态的加入到save()方法前后就是织入(Weaving)。
3. 常用的AOP通知- before(前置通知): 在方法开始执行前执行
- after(后置通知): 在方法执行后执行
- afterReturning(返回后通知): 在方法返回后执行
- afterThrowing(异常通知): 在抛出异常时执行
- around(环绕通知): 在方法执行前和执行后都会执行
执行顺序
around > before > around > after > afterReturning
4. 引入(Introduction)配置类
package com.cjz.study.aspect; import com.cjz.study.service.DoSthService; import com.cjz.study.service.impl.DoSthServiceImpl; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.DeclareParents; import org.springframework.stereotype.Component; @Aspect @Component public class IntroductionAspect { @DeclareParents(value = "com.cjz..service..*", defaultImpl = DoSthServiceImpl.class) public DoSthService doSthService; }
实现类相关
public interface UserService { void testIntroduction(); } @Service public class UserServiceImpl implements UserService { @Override public void testIntroduction() { System.out.println("do testIntroduction"); } } public interface DoSthService { void doSth(); } @Service public class DoSthServiceImpl implements DoSthService { @Override public void doSth() { System.out.println("do sth ...."); } }
test
@Test public void testIntroduction(){ userService.testIntroduction(); DoSthService doSthService = (DoSthService) userService; doSthService.doSth(); }
输出
do testIntroduction do sth ....5. 五种通知(增强)代码实现 5.1 匹配方法执行连接点
配置类
package com.cjz.study.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class TestExecutionAspect { @Pointcut("execution(* com.cjz.study.service.impl.ExecutionAspectServiceImpl.*(..))") public void pointcut() { } @Before("pointcut()") public void beginTransaction() { System.out.println("before beginTransaction"); } @After("pointcut()") public void commit() { System.out.println("after commit"); } @AfterReturning(value = "pointcut()", returning = "returnObject") public void afterReturning(JoinPoint joinPoint, Object returnObject) { System.out.println("afterReturning"); } @AfterThrowing("pointcut()") public void afterThrowing() { System.out.println("afterThrowing afterThrowing rollback"); } @Around("pointcut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { try { System.out.println("around 方法执行前"); //方法执行 Object object = joinPoint.proceed(); System.out.println("around 方法执行后"); return object; } catch (Throwable e) { e.printStackTrace(); throw e; } finally { System.out.println("around"); } } }
业务相关代代码
public interface ExecutionAspectService { Integer testExecutionAspectService(int number); } @Service public class ExecutionAspectServiceImpl implements ExecutionAspectService { @Override public Integer testExecutionAspectService(int number) { if(number ==0){ throw new RuntimeException(); } return number; } }
测试
@Test public void testExecutionAspectService(){ executionAspectService.testExecutionAspectService(2); }
输出
around 方法执行前 before beginTransaction afterReturning after commit around 方法执行后 around
常用的execution解释
例: execution(* com.cjz..service.*.*(..))
- execution 表达式的主体
- 第一个* 代表任意的返回值
- com.cjzaop所横切的包名
- 包后面… 表示当前包及其子包
- 第二个* 表示类名,代表所有类
- .*(…) 表示任何方法,括号代表参数 … 表示任意参数
例: execution(* com.cjz..service.*Service.add*(String))
com.cjz 包及其子包下的service包下,类名以Service结尾,方法以add开头,参数类型为String的方法的切点。
5.2 对注解注解
@Documented //有关java doc的注解 @Target(ElementType.METHOD)//针对方法 @Retention(RetentionPolicy.RUNTIME)//保留时间,这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用. public @interface Log { String value() default ""; }
配置类
@Aspect @Component public class TestAnnotationAspect { @Pointcut(value = "@annotation(com.cjz.study.annotation.Log)") public void pointcut() { } @Around(value = "pointcut()", argNames = "joinPoint") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { try { Method me = ((MethodSignature) joinPoint.getSignature()).getMethod(); System.out.println("方法执行前"); Log annotation = me.getAnnotation(Log.class); System.out.println("Log注解的value值" + annotation.value()); //方法执行 Object proceed = joinPoint.proceed(); System.out.println("方法执行后"); return proceed; } catch (Throwable throwable) { throw throwable; } finally { System.out.println("around"); } } }
相关业务类
public interface AnnotationService { void testAnnotationAspect(); } @Service public class AnnotationServiceImpl implements AnnotationService { @Override @Log("log") public void testAnnotationAspect() { System.out.println("testAnnotationAspect"); } }
测试
@Test public void testAnnotationService(){ annotationService.testAnnotationAspect(); }
输出
方法执行前 Log注解的value值log testAnnotationAspect 方法执行后 around
更多详细配置请参考 官方文档
AOP 实现Redis缓存数据查询 链接