目录
0、动态代理简述
1、JDK动态代理
1.1 JDK动态代理的代码演示
1.1.1 创建一个被代理的对象:接口SomeService
1.1.2 创建接口SomeService的实现类SomeServiceImpl
1.1.3 创建接口SomeService的增强类ServiceUtils
1.1.4 创建一个MyInvocationHandler类, 且实现InvocationHandler接口, 来增强被代理类
1.1.5 测试动态代理的效果-测试代码的基类BaseTest
1.1.6 测试动态代理的效果(通过创建动态代理来对被代理类实现方法增强)
1.1.7 测试结果与项目结构
2、AOP:面向切面编程:简述
2.1 AOP简述(掌握)
2.2 AOP面向切面编程对有什么好处?
2.3 AOP编程术语(掌握)
2.3.1 切面(Aspect)
2.3.2 连接点(JoinPoint)
2.3.3 切入点(Pointcut)
2.3.4 目标对象(Target)
2.3.5 通知(Advice)
3、AOP:面向切面编程:代码演示
3.1 AspectJ 对 AOP 的实现(掌握)
3.2 AspectJ 简介
3.3 AspectJ 的通知类型(理解)
3.4 AspectJ 的切入点表达式(掌握)
3.4.1 切入点表达式的原型解释
3.4.2 切入点表达式的原型举例:
3.5 AspectJ 实现AOP的代码演示
3.5.0 使用AspectJ实现AOP的具体步骤如下
3.5.0 创建一个Maven项目, 项目结构图
3.5.1 加入jar包依赖(Spring依赖+AspectJ依赖):pom.xml
3.5.2 创建目标类(是一个接口):SomeService.java
3.5.3 创建目标类的实现类:SomeServiceImpl.java
3.5.4 创建切面类和编写切面方法:MyAspect.java
3.5.5 创建spring.xml配置文件声名对象, 也即把对象交给Spring容器统一管理
3.5.6 创建springmvc.xml(AOP暂时用不到, 只是web项目必要的配置文件)
3.5.7 AOP的测试类基类:BaseTest.java
3.5.8 AOP的测试类和测试结果:MyTest.java
4、AOP-面向切面变成-回顾
5、AOP中的其他类型的通知-代码演示
5.1 前置通知 @Before:方法有JoinPoint参数
5.1.1 测试结果
5.2 后置通知 @AfterReturning:注解有returning属性
5.2.1 测试结果
5.3 环绕通知 @Around:增强方法有 ProceedingJoinPoint 参数
5.3.1 测试结果
5.4 异常通知 @AfterThrowing:注解中有throwing属性
5.5 最终通知 @After:总是会被执行
5.6 定义切入点 @Pointcut
5.7 目前为止的项目结构
【参考B站视频】:https://www.bilibili.com/video/BV1nz4y1d7uy?p=1
package com.wind.service;
import org.springframework.stereotype.Service;
@Service
public interface SomeService {
void doSome();
void doOther();
}
package com.wind.serviceImpl;
import com.wind.service.SomeService;
import org.springframework.stereotype.Service;
@Service
public class SomeServiceImpl implements SomeService {
@Override
public void doSome() {
System.out.println("业务方法===SomeServiceImpl.doSome done...");
}
@Override
public void doOther() {
System.out.println("业务方法===SomeServiceImpl.doOther done...");
}
}
package com.wind.utils;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class ServiceUtils {
public static void doLog() {
System.out.println("非业务方法===方法开始执行的时间=" + new Date());
}
public static void doTx() {
System.out.println("非业务方法===方法执行完毕,提交事务的时间=" + new Date());
}
}
package com.wind.handler;
import com.wind.utils.ServiceUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
//定义一个目标对象:即将来的someServiceImpl对象
private Object targetObject;
public MyInvocationHandler(Object targetObject) {
this.targetObject = targetObject;
}
/**
* 通过代理对象执行目标方法时,会执行invoke方法。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("动态代理===MyInvocationHandler.invoke() done...+准备执行的方法=" + method.getName());
//定义一个方法返回值
Object result = null;
//动态代理-前置的方法增强
ServiceUtils.doLog();
//执行目标类的目标方法,底层通过Method类中的invoke方法完成
result = method.invoke(targetObject, args);
//动态代理-后置的方法增强
ServiceUtils.doTx();
//返回目标方法的返回值
return result;
}
}
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:config/spring.xml"})
public abstract class BaseTest {
}
import com.wind.handler.MyInvocationHandler;
import com.wind.service.SomeService;
import com.wind.serviceImpl.SomeServiceImpl;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
@Component
public class MyApp extends BaseTest {
@Autowired
private SomeServiceImpl someServiceImpl;
@Test
public void someServiceTest() {
someServiceImpl.doSome();
someServiceImpl.doOther();
}
/***
* 使用JDK的Proxy创建动态代理
*/
@Test
public void someServiceProxyTest() {
//1.创建目标类对象
SomeServiceImpl targetService = new SomeServiceImpl();
//2.创建InvocationHandler对象
InvocationHandler invocationHandler = new MyInvocationHandler(targetService);
//3.创建Proxy动态代理对象
SomeService targetServiceProxy = (SomeService) Proxy.newProxyInstance(
targetService.getClass().getClassLoader(),
targetService.getClass().getInterfaces(),
invocationHandler
);
//4.通过动态代理对象执行invocationHandler中的invoke方法
targetServiceProxy.doSome();
}
}
AOP(Aspect Orient Programming),面向切面编程,面向切面编程是从动态角度考虑程序运行过程。AOP,面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 Spring 框架中的一个重要内容。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性, 同时提高了开发的效率。
面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无 关的代码,如安全检查、事务、日志、缓存等。 若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在 一起。这样,会使主业务逻辑变的混杂不清。
例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但是,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑---转账。
(1)减少重复。(2)专注业务。(3)注意:面向切面编程只是面向对象编程的一种补充。使用 AOP 减少重复代码,专注业务实现:
切面,泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice),实际就是对主业务逻辑的一种增强。
连接点,是指可以被切面织入的具体方法,通常业务接口中的方法均为连接点。
切入点,是指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
目标对象,是指将要被增强的对象。即包含主业务逻辑的类的对象。上例中的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标 对象。当然,不被增强,也就无所谓目标不目标了。
通知,表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。
对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之 一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。
AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切 面实现。AspetJ官网地址:https://www.eclipse.org/aspectj/
AspetJ是 Eclipse 的开源项目,官网介绍如下:
(1)a seamless aspect-oriented extension to the Javatm programming language:一种基于 Java 平台的面向切面编程的语言。
(2)Java platform compatible:兼容 Java 平台,可以无缝扩展。
(3)easy to learn and use:易学易用。
AspectJ 中常用的通知有五种类型:(1)前置通知。(2)后置通知。(3)环绕通知。(4)异常通知。(5)最终通知。
AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
(1)modifiers-pattern:访问权限类型
(2)ret-type-pattern:返回值类型
(3)declaring-type-pattern:包名类名
(4)name-pattern(param-pattern):方法名(参数类型和参数个数)
(5)throws-pattern:抛出异常类型
(6)?:表示可选的部分以上表达式共 4 个部分。
【总结】execution(访问权限 方法返回值 方法声明(参数) 异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
【总结】execution(访问权限 方法返回值 方法声明(参数) 异常类型)
(1)execution(public * *(..)) :指定切入点为:任意公共方法。
(2)execution(* set*(..)) :指定切入点为:任何一个以“set”开始的方法。
(3)execution(* com.xyz.service.*.*(..)) :指定切入点为:定义在 service 包里的任意类的任意方法。
(4)execution(* com.xyz.service..*.*(..)) :指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现 在类名中时,后面必须跟“*”,表示包、子包下的所有类。
(5)execution(* *..service.*.*(..)) :指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点。
(6)execution(* *.service.*.*(..)) :指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点。
(7)execution(* *.ISomeService.*(..)) :指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点 。
(8)execution(* *..ISomeService.*(..)) :指定所有包下的 ISomeSerivce 接口中所有方法为切入点。
(9)execution(* com.xyz.service.IAccountService.*(..)):指定切入点为:IAccountService 接口中的任意方法。
(10)execution(* com.xyz.service.IAccountService+.*(..)) :指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有 实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
(11)execution(* joke(String,int))) :指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可 以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。
(12)execution(* joke(String,*))) :指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数 可以是任意类型,如 joke(String s1,String s2)和 joke(String s1,double d2) 都是,但 joke(String s1,double d2,String s3)不是。
(13)execution(* joke(String,..))) :指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有 任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3)都是。
(14)execution(* joke(Object)) :指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类 型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。
(15)execution(* joke(Object+))) :指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也 是。
参考:https://blog.csdn.net/cmm0401/article/details/111773134
4.0.0
com.wind
ssm-web8-aop
1.0-SNAPSHOT
war
UTF-8
1.8
1.8
5.2.5.RELEASE
3.4.6
2.0.3
8.0.22
1.2.4
org.springframework
spring-context
${spring.version}
org.springframework
spring-aspects
${spring.version}
org.aspectj
aspectjweaver
1.9.2
org.springframework
spring-tx
${spring.version}
org.springframework
spring-jdbc
${spring.version}
org.springframework
spring-webmvc
${spring.version}
javax.servlet
javax.servlet-api
4.0.1
javax.servlet.jsp
jsp-api
2.2.1-b03
jstl
jstl
1.2
org.mybatis
mybatis
${mybatis.version}
org.mybatis
mybatis-spring
${mybatis.spring.version}
mysql
mysql-connector-java
${mysql.version}
com.alibaba
druid
${druid.version}
com.fasterxml.jackson.core
jackson-core
2.10.2
com.fasterxml.jackson.core
jackson-databind
2.10.2
junit
junit
4.13
test
org.springframework
spring-test
5.2.5.RELEASE
test
ssm-web
maven-compiler-plugin
3.1
${maven.compiler.target}
maven-clean-plugin
3.1.0
maven-resources-plugin
3.0.2
maven-compiler-plugin
3.8.0
maven-surefire-plugin
2.22.1
maven-war-plugin
3.2.2
maven-install-plugin
2.5.2
maven-deploy-plugin
2.8.2
src/main/java
**/*.properties
**/*.xml
src/main/resources
**/*.properties
**/*.xml
package com.wind.bao01;
/**
* 这个目标接口
*/
public interface SomeService {
void doSome(String name, Integer age);
String doOther(String name, Integer age);
String doAround(String name, Integer age);
}
package com.wind.bao01;
/**
* 这个是目标接口的实现类,也就是目标类:被代理的对象
*/
public class SomeServiceImpl implements SomeService {
/**
* 目标方法:准备给目标方法做增强,在目标方法执行之前打印目标方法执行的开始时间
*/
@Override
public void doSome(String name, Integer age) {
System.out.println("====目标业务方法执行了。SomeServiceImpl.doSome()====");
}
@Override
public String doOther(String name, Integer age) {
System.out.println("====目标业务方法执行了。SomeServiceImpl.doOther()====");
return "doOther.result=abcdefg";
}
@Override
public String doAround(String name, Integer age) {
System.out.println("====目标业务方法执行了。SomeServiceImpl.doAround()====");
return "doAround.result=abcdefg";
}
}
package com.wind.bao01;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @Aspect :这是Aspectj框架中的注解,用来声名一个类是作为切面类来使用。
* 作用:标识当前类是一个切面类。
* 切面类:用来给业务方法增强功能的类,在这个类中写业务增强功能。
* 使用位置:该注解定义在一个类的上面。
*/
@Aspect
public class MyAspect {
/**
* 在切面类中定义各种各样的切面方法:切面方法是实现业务功能增强的。
* 切面方法定义的要求:
* (1)公共的方法,public
* (2)方法没有返回值
* (3)方法名称自定义
* (4)方法可以有参数,也可以没有参数。如果有参数,则参数不是自定义的,有几个参数类型可以使用。
*/
/**
* @Before :前置通知的注解。
* 属性value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
* 位置:作用在切面方法的上面。
* 特点:
* (1)在目标方法执行之前先执行。
* (2)不会改变目标方法的执行结果。
* (3)不会影响目标方法的执行。
*/
@Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore() {
//这个方法就是你的切面想要执行的功能
System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
}
@After(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
public void myAfter() {
//这个方法就是你的切面想要执行的功能
System.out.println("后置通知,切面功能myAfter()=在目标方法执行之后输出时间=" + new Date());
}
}
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:config/*.xml"})
public abstract class BaseTest {
}
import com.wind.bao01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest extends BaseTest {
@Test
public void bao01Test() {
String config = "config/spring.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(config);
SomeService proxy = (SomeService) context.getBean("someServiceImpl");
System.out.println(proxy.getClass().getName());
proxy.doSome("yangguo", 20);
}
}
【不光前置通知的方法可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数,而且,该参数只能放在通知方法中的第一个参数位置上。】
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数,该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。
下面代码的功能:在通知方法中获取业务方法信息, 如方法签名/参数等-见方法myBefore2()。
package com.wind.bao01;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Arrays;
import java.util.Date;
import java.util.Objects;
/**
* @Aspect :这是Aspectj框架中的注解,用来声名一个类是作为切面类来使用。
* 作用:标识当前类是一个切面类。
* 切面类:用来给业务方法增强功能的类,在这个类中写业务增强功能。
* 使用位置:该注解定义在一个类的上面。
*/
@Aspect
public class MyAspect {
/**
* 在切面类中定义各种各样的切面方法:切面方法是实现业务功能增强的。
* 切面方法定义的要求:
* (1)公共的方法,public
* (2)方法没有返回值
* (3)方法名称自定义
* (4)方法可以有参数,也可以没有参数。如果有参数,则参数不是自定义的,有几个参数类型可以使用。
*/
/**
* @Before :前置通知的注解。
* 属性value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
* 位置:作用在切面方法的上面。
* 特点:
* (1)在目标方法执行之前先执行。
* (2)不会改变目标方法的执行结果。
* (3)不会影响目标方法的执行。
*/
@Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore() {
//这个方法就是你的切面想要执行的功能
System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
}
/**
* 指定通知方法中的参数:JoinPoint
* JoinPoint:业务方法,要加入到切面功能的业务方法。
* 作用是:可以在通知方法中获取业务方法执行时的信息,例如方法名称、方法参数等。
* 如果你的通知方法中需要用到业务方法的信息,就可以加入JoinPoint来获取。
* 这个JoinPoint参数是由框架自动赋值的,必须是在第一个参数位置上。
*/
@Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore2(JoinPoint joinPoint) {
//获取方法的完整信息
System.out.println("方法的签名(定义)=" + joinPoint.getSignature());
System.out.println("方法的名称=" + joinPoint.getSignature().getName());
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg != null) {
System.out.println("方法的名称=" + arg);
}
}
//这个方法就是你的切面想要执行的功能
System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
}
@After(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
public void myAfter() {
//这个方法就是你的切面想要执行的功能
System.out.println("后置通知,切面功能myAfter()=在目标方法执行之后输出时间=" + new Date());
}
}
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。
所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外, 还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
package com.wind.bao01;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Arrays;
import java.util.Date;
import java.util.Objects;
/**
* @Aspect :这是Aspectj框架中的注解,用来声名一个类是作为切面类来使用。
* 作用:标识当前类是一个切面类。
* 切面类:用来给业务方法增强功能的类,在这个类中写业务增强功能。
* 使用位置:该注解定义在一个类的上面。
*/
@Aspect
public class MyAspect {
/**
* 在切面类中定义各种各样的切面方法:切面方法是实现业务功能增强的。
* 切面方法定义的要求:
* (1)公共的方法,public
* (2)方法没有返回值
* (3)方法名称自定义
* (4)方法可以有参数,也可以没有参数。如果有参数,则参数不是自定义的,有几个参数类型可以使用。
*/
/**
* @Before :前置通知的注解。
* 属性value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
* 位置:作用在切面方法的上面。
* 特点:
* (1)在目标方法执行之前先执行。
* (2)不会改变目标方法的执行结果。
* (3)不会影响目标方法的执行。
*/
@Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore() {
//这个方法就是你的切面想要执行的功能
System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
}
/**
* 注意:@Before直接是可以指定通知方法中的参数的:使用关键字 JoinPoint 来指定。
* JoinPoint:业务方法,要加入到切面功能的业务方法。
* 作用是:可以在通知方法中获取业务方法执行时的信息,例如方法名称、方法参数等。
* 如果你的通知方法中需要用到业务方法的信息,就可以加入JoinPoint来获取。
* 这个JoinPoint参数是由框架自动赋值的,必须是在第一个参数位置上。
*/
@Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore2(JoinPoint joinPoint) {
//获取方法的完整信息
System.out.println("方法的签名(定义)=" + joinPoint.getSignature());
System.out.println("方法的名称=" + joinPoint.getSignature().getName());
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg != null) {
System.out.println("方法的名称=" + arg);
}
}
//这个方法就是你的切面想要执行的功能
System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
}
/**
* @AfterReturning :后置通知的注解。
* 属性:
* (1)value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
* (2)returning 自定义的变量,表示目标方法的返回值的。注:自定义的变量名必须和通知方法饿形参名保持一致。
* 位置:作用在切面方法的上面。
* 特点:
* (1)在目标方法执行之后执行。
* (2)可以有参数。
* (3)可以获取到目标方法的返回值,可以根据这个返回值做不同的处理功能。相当于:Object myRes = doOther();
* (4)可以修改这个返回值。
*/
@AfterReturning(value = "execution( String com.wind.bao01.SomeServiceImpl.doOther(String,Integer))", returning = "myRes")
public void myAfter(Object myRes) {
//myRes:是目标方法执行后返回的值,可以根据这个值来做你的增强功能
System.out.println("后置通知,切面功能myAfter()=在目标方法执行之后得到的结果=" + myRes);
}
}
@Test
public void bao01Test() {
String config = "config/spring.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(config);
SomeService proxy = (SomeService) context.getBean("someServiceImpl");
System.out.println(proxy.getClass().getName());
proxy.doOther("yangguo", 20);
}
在目标方法执行之前与之后执行。被注解为环绕增强的方法要有返回值,Object 类型,并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回,该增强方法实际是拦截了目标方法的执行。
package com.wind.bao01;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Date;
/**
* @Aspect :这是Aspectj框架中的注解,用来声名一个类是作为切面类来使用。
* 作用:标识当前类是一个切面类。
* 切面类:用来给业务方法增强功能的类,在这个类中写业务增强功能。
* 使用位置:该注解定义在一个类的上面。
*/
@Aspect
public class MyAspect {
/**
* 在切面类中定义各种各样的切面方法:切面方法是实现业务功能增强的。
* 切面方法定义的要求:
* (1)公共的方法,public
* (2)方法没有返回值
* (3)方法名称自定义
* (4)方法可以有参数,也可以没有参数。如果有参数,则参数不是自定义的,有几个参数类型可以使用。
*/
/**
* @Before :前置通知的注解。
* 属性value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
* 位置:作用在切面方法的上面。
* 特点:
* (1)在目标方法执行之前先执行。
* (2)不会改变目标方法的执行结果。
* (3)不会影响目标方法的执行。
*/
@Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore() {
//这个方法就是你的切面想要执行的功能
System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
}
/**
* 注意:@Before直接是可以指定通知方法中的参数的:使用关键字 JoinPoint 来指定。
* JoinPoint:业务方法,要加入到切面功能的业务方法。
* 作用是:可以在通知方法中获取业务方法执行时的信息,例如方法名称、方法参数等。
* 如果你的通知方法中需要用到业务方法的信息,就可以加入JoinPoint来获取。
* 这个JoinPoint参数是由框架自动赋值的,必须是在第一个参数位置上。
*/
@Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore2(JoinPoint joinPoint) {
//获取方法的完整信息
System.out.println("方法的签名(定义)=" + joinPoint.getSignature());
System.out.println("方法的名称=" + joinPoint.getSignature().getName());
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg != null) {
System.out.println("方法的名称=" + arg);
}
}
//这个方法就是你的切面想要执行的功能
System.out.println("前置通知,切面功能myBefore()=在目标方法执行之前输出时间=" + new Date());
}
/**
* @AfterReturning :后置通知的注解。
* 属性:
* (1)value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
* (2)returning 自定义的变量,表示目标方法的返回值的。注:自定义的变量名必须和通知方法饿形参名保持一致。
* 位置:作用在切面方法的上面。
* 特点:
* (1)在目标方法执行之后执行。
* (2)可以有参数。
* (3)可以获取到目标方法的返回值,可以根据这个返回值做不同的处理功能。相当于:Object myRes = doOther();
* (4)可以修改这个返回值。
*/
@AfterReturning(value = "execution( String com.wind.bao01.SomeServiceImpl.doOther(String,Integer))", returning = "myRes")
public void myAfter(Object myRes) {
//myRes:是目标方法执行后返回的值,可以根据这个值来做你的增强功能
System.out.println("后置通知,切面功能myAfter()=在目标方法执行之后得到的结果=" + myRes);
}
/**
* @Around :环绕通知的注解。
* 属性:
* (1)value:切入点表达式execution,用来标识该切面方法在哪些业务方法中被切入。
* 位置:作用在切面方法的上面。
* 特点:
* (1)它是功能最强的通知。
* (2)在目标业务方法的前和后都可以执行增强功能。
* (3)控制目标方法是否可以被调用执行。
* (4)修改原来的目标方法的执行结果,影响最后的结果。
* (5)环绕通知,类似于JDK动态代理中的InvocationHandler接口。
* 参数:
* (1)ProceedingJoinPoint:就等同于Method,用于执行目标方法。
* (2)返回值,就是目标方法的执行结果,可以被修改。
* 用处:环绕通知通常情况下是用来做事务的:在目标方法执行之前开启事务,然后执行目标方法,最后提交事务。
*/
@Around(value = "execution( String com.wind.bao01.SomeServiceImpl.doAround(String,Integer))")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object result = null;
String name = "";
Object[] args = proceedingJoinPoint.getArgs();
if (args != null && args.length >= 1) {
name = (String) args[0];
}
//1.在目标方法之前做增强
System.out.println("环绕通知===目标方法之前做增强,时间=" + new Date());
//2.执行目标方法
if (name.equals("yangguo")) {
result = proceedingJoinPoint.proceed();
result = result + ",对目标方法的结果做二次修正";
} else {
result = "在AOP.myAround()中,我改变了目标方法执行的结果了";
}
//3.在目标方法之后做增强
System.out.println("环绕通知===目标方法之后做增强,提交事务时间=" + new Date());
//4.返回最终的结果
return result;
}
}
import com.wind.bao01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest extends BaseTest {
@Test
public void bao01Test() {
String config = "config/spring.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(config);
SomeService proxy = (SomeService) context.getBean("someServiceImpl");
System.out.println(proxy.getClass().getName());
//proxy.doSome("yangguo", 20);
//proxy.doOther("yangguo", 20);
String around = proxy.doAround("yangguo1", 20);
System.out.println(around);
}
}
(1)目标方法执行了:
(2)目标方法没有执行:
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。
无论目标方法是否抛出异常,该增强均会被执行。
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维 护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。
代表的就是@Pointcut 定义的切 入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。