Spring面向切面(AOP)
Posted 一叶一落秋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring面向切面(AOP)相关的知识,希望对你有一定的参考价值。
- AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志、事务、权限等,Struts2的拦截器设计就是基于AOP的思想。
- AOP的基本概念
- Aspect(切面):通常是一个类,里面可以定义切入点和通知
- JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用。
- Advice(通知):AOP在特定的切入点上执行的增强处理,有before、after、afterReturning、afterThrowing、around
- Pointcut(切入点):AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。
- Spring AOP
- Spring中的AOP代理离不开Spring的IOC容器,代理的生成,管理及其依赖关系都是有IOC容器负责,Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLID代理。
- 基于注解的AOP配置方式
- 启用@Asject支持
- 在applicationContext.xml中配置
<aop:aspectj-autoproxy />
- 在applicationContext.xml中配置
- 通知类型介绍
- Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可。
- AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值。
- AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以用过改形参名来访问目标方法中所抛出的异常对象。
- After:在目标方法完成之后做增强,无论目标方法什么时候成功完成。@After可以指定一个切入点表达式。
- Around:环绕通知,在目标方法完成前后做增强处理,环绕通知最重要的通知类型,像事务、日志等都是环绕通知。
- 启用@Asject支持
- 测试实例
- Operator.java --> 切面类
@Component @Aspect public class Operator { @Pointcut("execution(* com.lynn.learning.spring.service..*.*(..))") public void pointCut(){} @Before("pointCut()") public void doBefore(JoinPoint joinPoint){ System.out.println("AOP Before Advice..."); } @After("pointCut()") public void doAfter(JoinPoint joinPoint){ System.out.println("AOP After Advice..."); } @AfterReturning(pointcut="pointCut()", returning="returnVal") public void afterReturn(JoinPoint joinPoint, Object returnVal){ System.out.println("AOP AfterReturning Advice:" + returnVal); } @AfterThrowing(pointcut="pointCut()",throwing="error") public void afterThrowing(JoinPoint joinPoint, Throwable error){ System.out.println("AOP AfterThrowing Advice:" + error); } @Around("pointCut()") public Object around(ProceedingJoinPoint pjp){ Object obj = null; System.out.println("AOP Around before..."); try { obj = pjp.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("AOP Around after..."); return obj; } }
- UserService.java --> 定义一些目标方法
@Service public class UserService { public void add(){ System.out.println("UserService add()"); } public String delete(){ System.out.println("UserService delete()"); return "delete函数返回值"; } public void edit(){ System.out.println("UserService edit()"); int i = 5/0; } }
- applicationContext.xml
<context:component-scan base-package="com.lynn.learning.spring"/> <aop:aspectj-autoproxy />
- UserServiceTest.java
public class UserServiceTest { @Test public void add() { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.add(); } @Test public void delete() { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.delete(); } @Test public void edit() { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.edit(); } }
- 注意:做环绕通知的时候,调用ProceedingJoinPoint的proceed()方法才会执行目标方法,同时需要返回值,否则在AfterReturning中无法获取返回值。
- Operator.java --> 切面类
- 通知执行的优先级
- 进入目标方法时,先织入Around,再织入Before
- 退出目标方法时,织入Around,再织入AfterReturning,最后才织入After
- 切入点的定义和表达式
- 切入点表达式的定义算是整个AOP中的核心,有一套自己的规范
- Spring AOP支持的切入点指示符:
- @Pointcut("execution(* com.lynn.learning.spring.service..*.*(..))")
- 第一个*表示匹配任意的方法返回值,..(两个点)表示零个或多个,上面的第一个..表示service包及其子包,第二个*表示所有类,第三个*表示所有方法,第二个..表示方法的任意参数个数
- execution:用来匹配执行方法的连接点
- @Pointcut("within(com.lynn.learning.spring.service*)")
- within限定匹配方法的连接点,上面的就是表示匹配service包下的任意连接点
- @Pointcut("this(com.lynn.learning.spring.service.UserService)")
- this用来限定AOP代理必须是指定类型的实例,如上,指定了一个特定的实例,就是UserService
- @Pointcut("bean(userService)")
- bean也是非常常用的,bean可以指定IOC容器中的bean的名称。
- @Pointcut("execution(* com.lynn.learning.spring.service..*.*(..))")
- Spring实现自定义注解
- 创建自定义注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface MyLog { String requestUrl(); }
- 解析注解,通过AOP实现
@Component @Aspect public class MyLogAspect { @Pointcut(value = "@annotation(com.lynn.learning.spring.annotation.MyLog)") public void pointcut() {} @Around(value = "pointcut() && @annotation(myLog)") public Object around(ProceedingJoinPoint point, MyLog myLog) { System.out.println("+++++执行了around方法+++++"); String requestUrl = myLog.requestUrl(); Class clazz = point.getTarget().getClass(); Method method = ((MethodSignature) point.getSignature()).getMethod(); System.out.println("执行了类:" + clazz + ", 方法:" + method + " 自定义请求地址:" + requestUrl); try { return point.proceed(); } catch (Throwable throwable) { return throwable.getMessage(); } } @AfterReturning(value = "pointcut() && @annotation(myLog)", returning = "result") public Object afterReturning(JoinPoint joinPoint, MyLog myLog, Object result) { System.out.println("+++++执行了afterReturning方法+++++"); System.out.println("执行结果:" + result); return result; } @AfterThrowing(value = "pointcut() && @annotation(myLog)", throwing = "ex") public void afterThrowing(JoinPoint joinPoint, MyLog myLog, Exception ex) { System.out.println("+++++执行了afterThrowing方法+++++"); System.out.println("请求:" + myLog.requestUrl() + " 出现异常"); } }
- 使用自定义注解
@Service public class UserService { @MyLog(requestUrl = "add") public void add(){ System.out.println("UserService add()"); } public String delete(){ System.out.println("UserService delete()"); return "delete函数返回值"; } public void edit(){ System.out.println("UserService edit()"); int i = 5/0; } }
- 测试
public class UserServiceTest { @Test public void add() { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.add(); } }
- 打印结果
+++++执行了around方法+++++ 执行了类:class com.lynn.learning.spring.service.UserService, 方法:public void com.lynn.learning.spring.service.UserService.add() 自定义请求地址:add AOP Around before... AOP Before Advice... UserService add() AOP Around after... AOP After Advice... AOP AfterReturning Advice:null +++++执行了afterReturning方法+++++ 执行结果:null
- @Retention:元注解,有一个属性value,是RetentionPolicy类型的,Enum RetentionPolicy是一个枚举类型。
- CLASS:表示注解的信息被保留在class文件(字节码文件)。当程序编译时,不会被虚拟机保存在运行时。默认行为。
- SOURCE:表示注解的信息会被编辑器抛弃,不会保留在class文件中,注解的信息只会保存在源文件中
- RUNTIME:表示注解的信息会被保留在class文件(字节码文件)中,当程序编译时,会被虚拟机保存在运行时。所以他们可以使用反射的方式读取。Retention.RUNTIME可以从JVM中读取Annotation注解的信息,以便在分析程序的时候使用。
- @Target:用来修饰注解的元注解。属性ElementType也是一个枚举类型
- TYPE:用于描述类、接口(包括注解类型)或enum
- FIELD:用于描述域
- METHOD:用于描述方法
- PARAMETER:用于描述参数(参数名)
- CONSTRUCTOR:用于描述构造函数
- LOCAL_VARIABLE:用于描述局部变量
- ANNOTATION_TYPE:用于描述注解
- PACKAGE:用于描述包
- TYPE_PARAMETER:用于描述参数类型
- TYPE_USE:任何位置都可以
- @Documented:表明这个注解应该被javadoc工具记录。默认情况下javadoc是不包括注解的,但如果声明注解是指定了@Documented,则他会被javadoc之类的工具处理,所以注解类型信息也会被包括在生成的文档中。
- @Inherited:指明被注解的类会自动继承. 更具体地说,如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类, 父类又有一个子类(subclass),则父类的所有属性将被继承到它的子类中.
- 创建自定义注解
以上是关于Spring面向切面(AOP)的主要内容,如果未能解决你的问题,请参考以下文章