spring之AOP介绍
AOP 面向切面编程
一、动态代理
动态代理是指,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理对象只是由代理生成工具(不是真实定义的类)在程序运行时由 JVM根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。
1、JDK动态代理
动态代理的实现方式常用的有两种:使用 JDK的Proxy,与通过CGLIB生成代理
Jdk的动态要求目标对象必须实现接口,这是java设计上的要求。
从jdk1.3以来,java 语言通过 java.lang.reflect包提供三个类支持代理模式Proy、Method 和 InovcationHandler
2、CGLIB动态代理(了解)
CGLIB(Code Generation Library)是一个开源项目。是一个强大的、高性能、高质量的Code生成类库,它可以在运行期扩展Java 类与实现Java 接口。它广泛的被许多AOP的框架使用,例如Spring AOP。
使用 JDK 的Proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在接口,则无法使用该方式实现。但对于无接口的类,要为其创建动态代理,就要使用CGLIB来实现。
CGLIB代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。所以,使用CGLIB生成动态代理,要求目标类必须能够被继承,即不能是 final 的类。
CGLIB经常被应用在框架中,例如Spring ,Hibernate等。cglib 的代理效率高于Jdk。项目中直接使用动态代理的地方不多。一般都使用框架提供的功能。
3、动态代理的作用
- 在目标类源代码不改变的情况下,增加功能
- 减少代码的重复
- 专注业务逻辑代码
- 解耦合,让业务功能和日志,事务非业务功分离
二、不使用AOP的开发方式(理解)
1、创建目标类
SomeServiceImpl(目标类)
package cn.edu.huat.service.impl;
import cn.edu.huat.service.SomeService;
public class SomeServiceImpl implements SomeService {
public void doSome() {
System.out.println("执行业务方法doSome");
}
public void doOther() {
System.out.println("执行业务方法doOther");
}
}
2、创建InvocationHandler接口的实现类
(1)创建工具类util,增加功能
package cn.edu.huat.util;
import java.util.Date;
public class ServiceTools {
public static void doLog(){
System.out.println("方法的执行时间" + new Date());
}
public static void doTrans(){
System.out.println("方法执行完毕后,提交事务");
}
}
(2)实现目标方法的增加功能
package cn.edu.huat.handle;
import cn.edu.huat.util.ServiceTools;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object target; //目标对象
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过代理对象执行方法时,会调用这个invoke()
System.out.println("执行MyInvocationHandler中的Invoke()");
System.out.println("method名称:" + method.getName());
String methodName = method.getName();
Object result = null;
if ("doSome".equals(methodName)){
//在目标方法执行之前,输出时间
ServiceTools.doLog();
//执行目标类的方法,通过Method类实现
result = method.invoke(target,args);
//在目标方法执行之后,提交事务
ServiceTools.doTrans();
} else {
result = method.invoke(target,args);
}
//目标方法的执行结果
return result;
}
}
3、使用jdk中类Proxy,创建代理对象
实现创建对象的能力
package cn.edu.huat;
import cn.edu.huat.handle.MyInvocationHandler;
import cn.edu.huat.service.SomeService;
import cn.edu.huat.service.impl.SomeServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class MyTest {
public static void main(String[] args) {
//使用jdk的Proxy创建代理对象
//创建目标对象
SomeService target = new SomeServiceImpl();
//创建InvocationHandler对象
InvocationHandler handler = new MyInvocationHandler(target);
//使用Proxy创建代理
SomeService proxy = (SomeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
//通过代理执行方法,会调用Handler中的invoke()
proxy.doSome();
System.out.println("=====================");
proxy.doOther();
}
}
三、AOP简介
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP底层,就是采用动态代理模式实现的。采用了两种代理:
- JDK的动态代理
- CGLIB的动态代理
AOP为 Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技 术。AOP是Spring 框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。
若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。
例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的 “冗余” 代码,还大大干扰了主业务逻辑 ------- 如转账。
如何理解面向切面编程?
-
需要在分析项目功能时,找出切面
-
合理的安排切面的执行时间(在目标方法前,还是目标方法后)
-
合理的安全切面执行的位置,在哪个类,哪个方法增加增强功能
何时考虑使用aop技术?
-
当要给一个系统中存在的类修改功能 。但是原有类的功能不完善,并且还有源代码时,使用aop增加功能
-
要给项目中的多个类,增加一个相同的功能时,使用aop
-
给业务方法增加事务、日志输出等功能
四、AOP编程术语(掌握)
1、切面(Aspect)
切面,表示增强的功能。就是一堆代码,完成某一个功能。非业务功能。常见的切面功能有日志、事务、统计信息、参数检查、权限验证。
常用的切面是通知(Advice)。 实际就是对主业务逻辑的一种增强。
2、连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
3、切入点(Pointcut)
切入点,指多个连接点方法的集合。多个方法。
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。
被标记为final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
4、目标对象(Target)
给哪个类的方法增加功能,这个类就是目标对象
5、通知(Advice)
表示切面功能执行的时间
6、切面三要素
(1)切面的功能代码
使用Aspect表示切面干什么
(2)切面的执行位置
使用Pointcut表示切面执行的位置
(3)切面的执行时间
使用Advice表示时间,在目标方法之前,还是目标方法之后。
五、AspectJ对AOP的实现(掌握)
对于AOP这种编程思想,很多框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了AOP的功能,且其实现方式更为简捷,使用更为方便。而且还支持注解式开发。所以,Spring 又将AspectJ 对于AOP的实现也引入到了自己的框
架中。
在Spring中使用AOP开发时,一般使用AspectJ 的实现方式。
AspectJ 是一个优秀面向切面的框架,它扩展了Java 语言,提供了强大的切面实现。
aspectJ:一个开源的专门做aop的框架。spring框架中集成了aspectj框架,邇过spring就能使用aspectj的功能。
aspectJ 框架实现aop有两种方式:
- 使用xml 的配置文件:配置全局事务
- 使用注解,在项目中要做aop功能,一般都使用注解
1、AspectJ的通知类型
(1)@Aspect
aspectJ 框架中的注解
- 作用:表示当前类是切面类。
- 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
- 位置:在类定义的上面
(2)@Before
前置通知注解
- 属性:value,是切入点表达式,表示切面的功能执行的位置
- 位置:在方法的上面
- 特点
- 在目标方法之前先执行的
- 不会改变目标方法的执行结果
- 不会影响目标方法的执行
(3)@AfterReturning
后置通知
- 属性
- value,切入点表达式
- returning自定义的变量,表示目标方法的返回值
- 自定义变量名必须和通知方法的形参名一样
- 位置:在方法定义的上面
- 特点
- 在目标方法之后执行的
- 能够获取到目标方法的返回值, 可以根据这个返回值做不同的处理功能
- 可以修改这个返回值
(4)@Around
环绕通知
-
属性:value切入点表达式
-
位置:在方法的定义上面
-
特点
- 功能最强的通知
- 在目标方法的前和后都能增強功能
- 控制目标方法是否被调用执行
- 修改原来的目标方法的执行结果,影响最后的调用结果
-
环绕通知,等同于jdk 动态代理的 InvocationHandler 接口
-
参数:ProceedingJoinPoint 等同于 Method
-
作用:执行目标方法
-
返回值:目标方法的执行结果 ,可以被修改
(5)@AfterThrowing
异常通知
- 属性
- value,切入点表达式
- throwinng 自定义的变量,表示目标方法抛出的异常对象
- 变量名必须和方法的参数名一样
- 特点
- 在目标方法抛出异常时执行的
- 可以做异常的监控程序,监控目标方法执行时是不是有异常
- 如果有异常,可以发送邮件,短信等进行通知
(6)@After
最终通知
- 属性:value,切入点表达式
- 位置:在方法的上面
- 特点
- 总是会执行
- 在目标方法之后执行的
(7)@Pointcut
定义和管理切入点,如果你的项目中有多个切入点表达式是重复的,可以复用的。可以使用@Pointcut
- 属性:value,切入点表达式
- 位置:在自定义的方法上面
- 特点
- 当使用@Pointcut 定义在一个方法的上面。此时这个方法的名称就是切入点表达式的别名
- 其它的通知中,value属性可以使用这个方法名称,代替切入点表达式
2、AspectJ的切入点表达式
切入点表达式:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws pattern?)
//execution(访问权限 方法返回值 方法声明(参数) 异常类型)
解释:
- modifiers-pattern:访问权限类型
- ret-type-pattern:返回值类型
- declaring-type-pattern:包名类名
- name- pattern(param-pattern):方法名(参数类型和参数个数)
- throws-pattern:抛出异常类型
- ?:表示可选的部分
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
- *:0至多个任意字符
- …:用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包路径
- +:用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类
常用举例:
execution(public * *(..)) //指定切入点为:任意公共方法
execution(* set*(..)) //任何一个以“set"开始的方法
execution(* com.xyz.service.*.*(..) //定义在service包里的任意类的任意方法
execution(* com.xyz.service..*.*(..)) //定义在service包或者子包里的任意类的任意方法。“."出现在类名中时,后面必须跟“*”,表示包、子包下的所有类
execution(* *..service.*.*(..)) //指定所有包下的serivce子包下所有类(接口)中所有方法为切入点
3、AspectJ的开发环境
(1)maven依赖
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--aspectj依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
(2)引入AOP约束
在AspectJ 实现AOP时,要引入AOP的约束。配置文件中使用的AOP约束中的标签,均是AspectJ 框架使用的,而非Spring框架本身在实现AOP时使用的。
AspectJ 对于AOP的实现有注解和配置文件两种方式,常用是注解方式。
4、Aspect基于注解的AOP实现
(1)实现步骤
-
创建目标类:接口和他的实现类。要做的是给类中的方法增加功能
-
创建切面类:普通类
- 在类的上面加入@Aspect
- 在类中定义方法,方法就是切面要执行的功能代码。在方法的上面加入aspectj中的通知注解, 例如@Before有需要指定切入点表达式execution()
-
创建spring的配置文件:声明对象,把对象交给容器统一管理。声明对象可以使用注解或者xml 配置文件<bean>
- 声明目标对象
- 声明切面类对象
- 声明aspectJ 框架中的自动代理生成器标签。自动代理生成器:用来完成代理对象的自动创建功能的
-
创建测试类,从spring容器中获取目标对象(实际就是代理对象)。通过代理执行方法,实现aop的功能增强。
(2)@Before前置通知-方法有JoinPoint参数
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。
不光前置通知的方法,可以包含一个JoinPoint类型参数,所有的通知方法均可包含该参数。
举例:
MyAspect(切面类)
前置通知定义方法,方法是实现切面功能的。
方法的定义要求:
- 公共方法public
- 方法没有返回值
- 方法名称自定义
- 方法可以有参数,也可以没有参数
- 如果有参数,参数不是自定义的,有几个参数类型可以使用
package cn.edu.huat.sp01;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
@Aspect
public class MyAspect {
@Before(value = "execution(public void cn.edu.huat.sp01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore(){
//切面要执行的功能代码
System.out.println("前置通知,切面功能:在目标方法之前输出时间:" + new Date());
}
}
applicationContext.xml(spring配置文件)
<?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: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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--把对象交给spring容器,由spring容器统一创建,管理对象-->
<!--声明目标对象-->
<bean id="SomeService" class="cn.edu.huat.sp01.SomeServiceImpl" />
<!--声明切面类对象-->
<bean id="MyAspect" class="cn.edu.huat.sp01.MyAspect" />
<!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。创建代理对象是在内存中实现的,修改目标对象的 内存中的结构。创建为代理对象所以目标对象就是被修改后的代理对象。
aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象-->
<aop:aspectj-autoproxy />
</beans>
MyTest01(测试类)
package cn.edu.huat;
import cn.edu.huat.sp01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest01 {
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
SomeService proxy = (SomeService) ctx.getBean("SomeService");
//通过代理的对象执行方法,实现目标方法执行时,增强了功能
proxy.doSome("李四",22);
}
}
指定通知方法中的参数:JoinPoint
- JoinPoint:业务方法,要加入切面功能的业务方法
- 作用:可以在通知方法中获取方法执行时的信息,例如方法名称, 方法的实参
- 如果切面功能中需要用到方法的信息,就加入JoinPoint。这个JoinPoint参数的值是由框架赋予,必须是第一个位置的参数
package cn.edu.huat.sp01;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
@Aspect
public class MyAspect {
@Before(value = "execution(* *..SomeServiceImpl.do*(..))")
public void myBefore(JoinPoint joinPoint){
//获取方法的完整定义
System.out.println("方法的签名(定义) = " + joinPoint.getSignature());
System.out.println("方法的名称 = " + joinPoint.getSignature().getName());
//获取方法的实参
Object args[] = joinPoint.getArgs();
for (Object arg:args){
System.out.println("参数 = " + arg);
}
//切面要执行的功能代码
System.out.println("前置通知,切面功能:在目标方法之前输出时间:" + new Date());
}
}
(3)@AfterReturning后置通知-注解有returning属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含JoinPoint参数外,还可以包含用于接收返回值的变量。该变量最好为Object 类型,因为目标方法的返回值可能是任何类型。
后置通知定义方法,方法是实现切面功能的。
方法的定义要求:
- 公共方法public
- 方法没有返回值
- 方法名称自定义
- 方法有参数的,推荐是object,参数名自定义
SomeService(接口)
package cn.edu.huat.sp02;
public interface SomeService {
String doOther(String name,Integer age);
}
SomeServiceImpl(实现方法)
package cn.edu.huat.sp02;
public class SomeServiceImpl implements SomeService {
@Override
public String doOther(String name, Integer age) {
System.out.println("===目标方法doOther()===");
return "abc";
}
}
MyAspect(切面类)
package cn.edu.huat.sp02;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
@Aspect
public class MyAspect {
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "result")
public void myAfterReturning(Object result){
//Object result:是目标方法执行后的返回值,根据返回值做你的切面的处理功能
System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:" + result);
}
}
后置通知的执行:
object result = doother();
//参数传递:传值,传引用
myAfterReturing(result);
System.out.println("result = " + result);
(4)@Around环绕通知-增强方法有ProceedingJoinPoint参数
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个ProceedingJoinPoint 类型的参数。接口ProceedingJoinPoint 其有一个proceed() 方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
环绕通知方法的定义格式
- 公共方法public
- 必须有一个返回值,推荐使用Object
- 方法名称自定义
- 方法有参数,固定的参数 ProceedingJoinPoint
SomeService(接口)
package cn.edu.huat.sp03;
public interface SomeService {
String doFirst(String name,Integer age);
}
SomeServiceImpl(实现方法)
package cn.edu.huat.sp03;
public class SomeServiceImpl implements SomeService {
@Override
public String doFirst(String name, Integer age) {
System.out.println("===业务方法doFirst()===");
return "doFirst";
}
}
MyAspect(切面类)
package cn.edu.huat.sp03;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
@Aspect
public class MyAspect {
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//实现环绕通知
Object result = null;
System.out.println("环绕感知:在目标方法之前,输出时间:" + new Date());
//1、目标方法调用
result = pjp.proceed(); //等同于method.invoke();或者Object result = doFirst();
//2、在目标方法的前或后增加功能
System.out.println("环绕通知:在目标方法之后,提交事务");
//返回目标方法的执行结果
return result;
}
}
控制目标方法是否被调用执行演示:
package cn.edu.huat.sp03;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
@Aspect
public class MyAspect {
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
String name = "";
//获取第一个参数值
Object args[] = pjp.getArgs();
if (args != null && args.length > 1){
Object arg = args[0];
name = (String)arg;
}
//实现环绕通知
Object result = null;
System.out.println("环绕感知:在目标方法之前,输出时间:" + new Date());
//1、目标方法调用
if ("王五".equals(name)){
//符合条件,调用目标方法
result = pjp.proceed(); //等同于method.invoke();或者Object result = doFirst();
}
//2、在目标方法的前或后增加功能
System.out.println("环绕通知:在目标方法之后,提交事务");
//返回目标方法的执行结果
return result;
}
}
输出不符合条件的参数输出结果如下:
修改原来的目标方法的执行结果演示:
package cn.edu.huat.sp03;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
@Aspect
public class MyAspect {
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
String name = "";
//获取第一个参数值
Object args[] = pjp.getArgs();
if (args != null && args.length > 1){
Object arg = args[0];
name = (String)arg;
}
//实现环绕通知
Object result = null;
System.out.println("环绕感知:在目标方法之前,输出时间:" + new Date());
//1、目标方法调用
if ("王五".equals(name)){
//符合条件,调用目标方法
result = pjp.proceed(); //等同于method.invoke();或者Object result = doFirst();
}
//2、在目标方法的前或后增加功能
System.out.println("环绕通知:在目标方法之后,提交事务");
//修改目标方法的执行结果,影响最后的调用结果
if (result != null){
result = "Hello AspectJ AOP";
}
//返回目标方法的执行结果
return result;
}
}
MyTest03(测试类)
package cn.edu.huat;
import cn.edu.huat.sp03.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest03 {
@Test
public void test03() {
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
SomeService proxy = (SomeService) ctx.getBean("SomeService");
String str = proxy.doFirst("王五", 33);
System.out.println("str = " + str);
}
}
注意:环绕通知(Around)经常做事务, 在目标方法之前开启事务,执行目标方法在目标方法之后提交事务
(5)@AfterThrowing异常通知-注解中有throwing属性(了解)
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。
异常通知方法的定义格式
- 公共方法public
- 没有返回值
- 方法名称自定义
- 方法有一个参数Exception, 如果还有则是 JoinPoint
SomeService(接口)
package cn.edu.huat.sp04;
public interface SomeService {
void doSecond();
}
SomeServiceImpl(实现方法)
package cn.edu.huat.sp04;
public class SomeServiceImpl implements SomeService {
@Override
public void doSecond() {
System.out.println("执行业务方法doSecond()" + (10/0));
}
}
MyAspect(切面类)
package cn.edu.huat.sp04;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
@Aspect
public class MyAspect {
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "e")
public void myAfterThrowing(Exception e){
System.out.println("异常通知:方法发生异常时,执行:" + e.getMessage());
//发送邮件,短信,通知开发人员
}
}
执行时相当于就是以下程序:
try{
SomeServiceImpl.doSecond(..);
}catch(Exception e){
myAfterThrowing(e);
}
(6)@After最终通知(了解)
无论目标方法是否抛出异常,该增强均会被执行。
最终通知方法的定义格式
- 公共方法public
- 没有返回值
- 方法名称自定义
- 方法没有参数,如果有则是 JoinPoint
SomeService(接口)
package cn.edu.huat.sp05;
public interface SomeService {
void doThird();
}
SomeServiceImpl(实现方法)
package cn.edu.huat.sp05;
public class SomeServiceImpl implements SomeService {
@Override
public void doThird() {
System.out.println("执行业务方法doThird()" + (10/0));
}
}
MyAspect(切面类)
package cn.edu.huat.sp05;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class MyAspect {
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
//一般做清除资源的工作
}
}
执行时相当于就是以下程序:
try{
SomeServiceImpl.doThird(..);
} catch(Exception e) {
} finally {
myAfter();
}
(7)@Pointcut定义切入点
当较多的通知增强方法使用相同的execution切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut注解,用于定义execution切入点表达式。
其用法是,将@Pointcut注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut定义的切入点。这个使用@Pointcut注解的方法一般使用 private 的标识方法,即没有实际作用的方法。
package cn.edu.huat.sp06;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class MyAspect {
@After(value = "myPc()")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
}
@Before(value = "myPc()")
public void myBefore(){
System.out.println("前置通知,在目标方法执行之前执行");
}
@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
private void myPc(){
//无需代码
}
}
六、cglib代理
1、没有接口是cglib代理
MyTest07(测试类)
package cn.edu.huat;
import cn.edu.huat.sp07.SomeServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest07 {
@Test
public void test03() {
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
SomeServiceImpl proxy = (SomeServiceImpl) ctx.getBean("SomeService");
System.out.println("proxy = " + proxy.getClass().getName());
proxy.doThird();
}
}
目标类没有接口,使用cglib动态代理,spring 框架会自动应用cglib
2、有接口也可使用cglib代理
<?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: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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="SomeService" class="cn.edu.huat.sp08.SomeServiceImpl" />
<bean id="MyAspect" class="cn.edu.huat.sp08.MyAspect" />
<!--告诉框架,要使用cglib动态代理-->
<aop:aspectj-autoproxy proxy-target-class="true" />
</beans>
上一篇: spring Aop详细讲解
下一篇: nginx反向代理配置
推荐阅读
-
Spring AOP中使用args表达式的方法示例
-
spring AOP的Around增强实现方法分析
-
Spring AOP访问目标方法的参数操作示例
-
Spring MVC之DispatcherServlet详解_动力节点Java学院整理
-
Spring AOP 动态多数据源的实例详解
-
Spring MVC之DispatcherServlet_动力节点Java学院整理
-
hadoop入门之namenode工作特点介绍
-
详解Spring Boot实战之Filter实现使用JWT进行接口认证
-
详解Spring Data JPA系列之投影(Projection)的用法
-
spring boot aop 记录方法执行时间代码示例