文章目录
一、AOP常用的术语
- 切面:就是那些重复的、公共的、通用的功能称为切面,例如:日志、事务、权限
- 连接点:就是目标方法。因为在目标方法中要实现目标方法的功能和切面功能。
- 切入点:指定切入点的位置。多个连接点构成切入点。切入点可以是一个目标方法,可以是一个类中的所有方法,可以是某个包下的所有类中的方法。
- 目标对象:操作谁,谁就是目标对象
- 通知:来指定切入的时机。是在目标方法执行前还是执行后还是出错时,还是环绕目标方法 切入切面功能。
二、AspectJ框架
AspectJ是一个优秀面向切面的框架,它扩展了Java语言,提供了强大的切面实现。
2.1 AspectJ常见的通知类型
@Before:前置通知
@AfterReturning后置通知
@Around:环绕通知
@After:最终通知
@Poincut:定义切入点
2.2 AspectJ的切入点表达式
规范的公式:
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
简化后的公式:
execution(方法返回值 方法声明(参数))
用到的符号:
-
* 代表任意的字符
-
..如果出现在方法的参数中,则代表任意参数。如果出现在路径中,则代表本路径及其所有的子路径
示例:
3. execution(public * (…)) 只要是public访问权限下的、任意返回值类型的、任意路径下的、任意方法的任意参数。也就是公共访问权限下的所有方法都可切入切面功能
4. execution( set*(…)) 所有以set开头的方法
5. execution(* com.xyz.service.impl..(…) ) impl包下的任意类的任意方法的任意参数
2.3 AspectJ的前置通知@Before
在目标方法执行钱切入切面的功能。在切面方法中不可获得目标方法的返回值,只能得到目标方法的签名。
实现步骤:
- 创建业务接口
/**
7. 业务接口
*/
public interface SomeService {
String doSome(String name,int age);
}
- 创建业务实现
/**
* 业务实现类
*/
public class SomeServiceImpl implements SomeService{
@Override
public String doSome(String name, int age) {
System.out.println("doSome的业务功能实现....."+name);
return "cccccccccc";
}
}
- 创建切面类,实现切面方法
/**
* 此类为切面类,包含各种切面方法
*/
@Aspect //在pom.xml文件中添加依赖。交给AspectJ的框架去识别切面类
public class MyAspect {
@Before(value = "execution(public String com.bjpowernode.SomeServiceImpl.doSome(String,int))")
public void before(){
System.out.println("切面方法中的前置功能实现......");
}
}
- 在applicationContext.xml文件中进行配置
<!--创建业务对象-->
<bean id="someService" class="com.bjpowernode.SomeServiceImpl"></bean>
<!--创建切面对象-->
<bean id="aspect" class="com.bjpowernode.MyAspect"></bean>
<!--绑定-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 测试类
@Test
public void test1(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SomeService service = (SomeService) ac.getBean("someService");
String s = service.doSome("西西",22);
System.out.println(s);
}
- 运行结果
注意:AspectJ框架切换JDK动态代理和CGLib动态代理:
-
使用接口来接永远不会出错<aop:aspectj-autoproxy></aop:aspectj-autoproxy>===》默认是JDK动态代理,取出对象时必须使用接口类型。 <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>===》设置为CGLib动态代理,取出对象时,可以使用接口和实现类来接
使用注解的方式实现AspectJ的前置功能:
- 创建业务接口
/**
7. 业务接口
*/
public interface SomeService {
String doSome(String name,int age);
}
- 创建业务实现
/**
* 业务实现类
*/
@Service
public class SomeServiceImpl implements SomeService{
@Override
public String doSome(String name, int age) {
System.out.println("doSome的业务功能实现....."+name);
return "cccccccccc";
}
}
- 创建切面类,实现切面方法
/**
* 此类为切面类,包含各种切面方法
*/
@Aspect //在pom.xml文件中添加依赖。交给AspectJ的框架去识别切面类
@Component
public class MyAspect {
@Before(value = "execution(public String com.bjpowernode.SomeServiceImpl.doSome(String,int))")
public void before(){
System.out.println("切面方法中的前置功能实现......");
}
}
- 在applicationContext.xml文件中进行配置(基于注解的访问要添加包扫描)
<context:component-scan base-package="com.bjpowernode"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 测试类(注意此时测试类中getBean的参数)
@Test
public void test1(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SomeService service = (SomeService) ac.getBean("someServiceImpl");
String s = service.doSome("西西",22);
System.out.println(s);
}
2.4 AspectJ的后置通知@ArterReturning
使用注解方式:
实现步骤:
- 创建业务接口(同上)
- 创建业务实现(同上)
- 创建切面类,实现切面方法
@Aspect //在pom.xml文件中添加依赖。交给AspectJ的框架去识别切面类
@Component
public class MyAspect {
//returning:指定目标方法的返回值名称,名称必须与切面方法的参数名称一致
//value:指定切入点表达式
@AfterReturning(value = "execution(* com.bjpowernode.*.*(..))",returning = "obj")
public void myafterReturning(Object obj){
System.out.println("后置通知功能实现....");
}
}
- 在applicationContext.xml文件中进行配置(基于注解的访问要添加包扫描)(同上)
- 测试类(同上)
- 运行结果
拓展:操作目标方法的返回值
@AfterReturning(value = "execution(* com.bjpowernode.*.*(..))",returning = "obj")
public void myafterReturning(Object obj){
System.out.println("后置通知功能实现....");
if(obj != null){
if(obj instanceof String){
String s = obj.toString().toUpperCase();//改为大写
System.out.println("在切面方法中目标方法的返回值:"+s);
}
}
}
运行结果:
结果是并没有将目标方法的返回值进行改变
再举一个例子:
1、添加一个学生实体类:
public class Student {
private String name;
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(String name) {
this.name = name;
}
}
2、在业务接口中添加一个方法:
public interface SomeService {
String doSome(String name,int age);
Student change();
}
3、在实现类中重写change()方法:
@Override
public Student change() {
System.out.println("change方法被执行");
return new Student("张三");
}
4、在切面中修改目标方法的返回值:
@AfterReturning(value = "execution(* com.bjpowernode.*.*(..))",returning = "obj")
public void myafterReturning(Object obj){
System.out.println("后置通知功能实现....");
if(obj != null){
if(obj instanceof String){
obj = obj.toString().toUpperCase();//改为大写
System.out.println("在切面方法中目标方法的返回值:"+obj);
}
if(obj instanceof Student){
Student stu = (Student) obj;
stu.setName("李四");
System.out.println("在切面方法中目标方法的返回值:"+stu);
}
}
}
5、测试:
@Test
public void test2(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SomeService service = (SomeService) ac.getBean("someServiceImpl");
Student stu = service.change();
System.out.println("目标方法的返回值:"+stu);
}
6、运行结果:
总结:
如果目标方法的返回值是8种基本数据类型或String类型则不可改变
如果是引用型数据类型则可以改变
2.5 AspectJ的环绕通知@Around
是通过拦截目标的方式,在目标方法前后增强功能的通知。是功能最强大的通知,一般事务使用此通知。它可以轻易的改变目标方法的返回值。
使用注解方式:
实现步骤(没有添加学生实体类,没有添加方法):
- 创建业务接口(同上)
- 创建业务实现(同上)
- 创建切面类,实现切面方法
@Around(value = "execution(* com.bjpowernode.*.*(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//前切功能实现
System.out.println("环绕通知中的前切功能实现");
//目标方法实现
Object obj = pjp.proceed(pjp.getArgs());
//后切功能实现
System.out.println("环绕通知中的后切功能实现");
return obj.toString().toUpperCase();//改变了目标方法的返回值
}
- 测试类(同上)
- 运行结果:
2.6 AspectJ的最终通知@After
无论目标方法是否正常执行,最终通知的代码都会被执行(类似于try-catch中的finally)。
使用注解方式:
实现步骤(没有添加学生实体类,没有添加方法):
- 创建业务接口(同上)
- 创建业务实现(同上)
- 创建切面类,实现切面方法
@After(value = "execution(* com.bjpowernode.*.*(..))")
public void myAfter(){
System.out.println("最终通知的功能实现了....");
}
- 测试类(同上)
- 运行结果: