Spring(三)-AOP面向切面编程

目录

一、增加功能导致的问题

二、AOP的概念

2.1 什么是AOP

2.2 AOP作用

2.3 AOP中术语

三、什么时候用AOP

四、AOP技术思想的实现

五、使用AspectJ框架实现AOP

5.1通知

5.2 Pointcut位置

扫描二维码关注公众号,回复: 17222557 查看本文章

5.3 @before前置通知

切入点表达式的变化形式

通知方法的参数JoinPoint

5.4 @AfterReturning后置通知

5.5 @Around环绕通知

5.6 @AfterThrowing异常通知

5.7 @After最终通知

六、AOP总结


一、增加功能导致的问题

问题引出:如果要在已经实现的某个复杂的业务方法前,后增加新的功能,传统方法是怎么做的?

在源代码中业务方法中增加新功能,

1)源代码可能改动的比较多

2)重负代码多

3)代码难于维护

二、AOP的概念

2.1 什么是AOP

AOP(Aspect Orient Programming) : 面向切面编程

Aspect : 表示切面,给业务方法增加的功能叫做切面。切面一般都是非业务功能,而且切面功能一般都是可复用的。例如:日志功能,事务功能,权限检查,参数检查,统计信息等。

Orient : 面向,对看

Programming : 编程

怎么理解面向切面编程? 以切面为核心设计开发你的应用

1)设计项目时,找出切面的功能。

2)安排切面的执行时间,执行的位置。

2.2 AOP作用

1)让切面功能复用

2)让开发人员专注业务逻辑,提高开发效率

3)实现业务功能和其他非业务功能解耦合

4)给存在的业务方法增加功能,不用修改原来的代码

2.3 AOP中术语

1)Aspect : 切面,给业务方法增加的功能。

2)JoinPoint : 连接点,连接切面的业务方法。在这个业务方法执行时,会同时执行切面的功能。

3)Pointcut : 切入点,是一个或多个连接点的集合。表示这些方法执行时,都能增加切面的功能。表示切面执行 的位置。

4)target : 目标对象,给哪个对象增加切面的功能,这个对象就是目标对象。

5)Advice : 通知,表示切面的执行时间,在目标方法之前执行切面还是目标方法之后执行切面。

AOP中重要三要素:Aspect,Pointcut,Advice。这个概念的理解是:在Advice的时间,在Pointcut的位置,执行Aspect。

AOP是一个动态的思想。在程序运行期间,创建代理(ServiceProxy),使用代理执行方法时增加切面的功能。这个代理对象是存在内存中的。

三、什么时候用AOP

你要给某些方法增加相同的一些功能。源代码不能修改。给业务方法增加非业务功能。都可以使用AOP。

四、AOP技术思想的实现

使用框架实现AOP,实现AOP的框架有很多,比较有名的两个:

1)Spring : Spring框架实现AOP的部分功能。Spring框架实现AOP的操作比较繁琐,笨重。

2)Aspectj : 独立的框架,专门是实现AOP。属于Eclipse

五、使用AspectJ框架实现AOP

AspectJ框架可以使用注解和xml配置文件两种方式实现AOP

5.1通知

Aspectj表示切面执行的时间,用的是通知(Advice)。这个通知可以使用注解表示

5个注解,表示切面的五个执行时间,这些注解称为通知注解。

@Before : 前置通知

@AfterReturning : 后置通知

@Around : 环绕通知

@AfterThrowing : 异常通知

@After : 最终通知

5.2 Pointcut位置

Pointcut用来表示切面执行的位置,使用Aspectj中切入点表达式。

切入点表达式语法: execution(访问权限修饰符 方法返回值 方法声明(参数) 异常类型)

5.3 @before前置通知

使用aspectj框架的注解,实现前置通知:

①业务逻辑

package com.feiyang.service;



public interface SomeService {

void doSome(String name,Integer age);

}
package com.feiyang.service.impl;

import com.feiyang.service.SomeService;

/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/

public class SomeServiceImpl implements SomeService {



@Override

public void doSome(String name, Integer age) {

System.out.println("业务方法doSome(),执行了");

}

}

②定义一个切面类:

package com.feiyang.handler;



import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;



import java.util.Date;



/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/



@Aspect

public class MyAspectj {



@Before(value = "execution(public void com.feiyang.service.impl.SomeServiceImpl.doSome(String, Integer))")

public void myBefore(){

System.out.println("前置通知执行了" + new Date());

}

}

③修改配置文件

​
<?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:context="http://www.springframework.org/schema/context"

xmlns:aop="http://www.springframework.org/schema/aop"

xsi:schemaLocation="Index of /schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd Index of /schema/context https://www.springframework.org/schema/context/spring-context.xsd Index of /schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

<bean id="someService" class="com.feiyang.service.impl.SomeServiceImpl"></bean>

<bean id="myAspect" class="com.feiyang.handler.MyAspectj"></bean>

<!-- 声明自动代理生成器:目的是创建目标对象的代理 -->

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

​

④测试类:

package com.feiyang;



import com.feiyang.service.SomeService;

import org.junit.Test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;



/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/

public class Mytest01 {



@Test

public void test01(){

String config = "applicationContext.xml";

ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

SomeService someService = (SomeService) ctx.getBean("someService");

System.out.println(someService.getClass().getName());

someService.doSome("张三",18);

}

}

执行结果:

com.sun.proxy.$Proxy8

前置通知执行了Tue Oct 04 11:18:25 GMT+08:00 2022

业务方法doSome(),执行了

可以看到someService运行时类型为代理对象$Proxy8,

代理对象调用方法,才有切面的功能增强

切入点表达式的变化形式

package com.feiyang.handler;



import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;



import java.util.Date;



/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/



@Aspect

public class MyAspectj {



//匹配指定方法

/*@Before(value = "execution(public void com.feiyang.service.impl.SomeServiceImpl.doSome(String, Integer))")

public void myBefore(){

System.out.println("前置通知执行了" + new Date());

}*/



//匹配任意访问修饰符的指定方法

/*@Before(value = "execution(void com.feiyang.service.impl.SomeServiceImpl.doSome(String, Integer))")

public void myBefore(){

System.out.println("前置通知执行了" + new Date());

}*/



//匹配任意包下的,指定参数类型的doSome方法

/*@Before(value = "execution(* *..doSome(String, Integer))")

public void myBefore(){

System.out.println("前置通知执行了" + new Date());

}*/



//匹配任意包下,任意参数类型的doSome方法

/*@Before(value = "execution(* *..doSome(..))")

public void myBefore(){

System.out.println("前置通知执行了" + new Date());

}*/



//匹配任意包下,do开头的方法

/*@Before(value = "execution(* *..do*(..))")

public void myBefore(){

System.out.println("前置通知执行了" + new Date());

}*/



//匹配任意包下任意方法

@Before(value = "execution(* *..*(..))")

public void myBefore(){

System.out.println("前置通知执行了" + new Date());

}

}

通知方法的参数JoinPoint

package com.feiyang.handler;



import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;



import java.util.Date;



/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/



@Aspect

public class MyAspectj {



@Before(value = "execution(* *..*(..))")

public void myBefore(JoinPoint jp){

Object[] args = jp.getArgs();//数组中存放的是 方法的所有参数

for (Object arg : args) {

System.out.println("前置通知,获取方法的参数: " + arg);

}



String methodName = jp.getSignature().getName();

if("doSome".equals(methodName)){

System.out.println("doSome输出日志,在目标方法前先执行=" + new Date());

}else if("doOther".equals(methodName)){

System.out.println("doOther参数检查,在目标方法前先执行=" + new Date());

}else{

System.out.println("没有匹配到通知方法");

}

}

}

package com.feiyang;



import com.feiyang.service.SomeService;

import org.junit.Test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;



/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/

public class Mytest01 {



@Test

public void test01(){

String config = "applicationContext.xml";

ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

SomeService someService = (SomeService) ctx.getBean("someService");

someService.doSome("张三",18);

//someService.doOther("张三",18);

}

}

执行结果:

前置通知,获取方法的参数: 张三

前置通知,获取方法的参数: 18

doSome输出日志,在目标方法前先执行=Tue Oct 04 13:25:54 GMT+08:00 2022

业务方法doSome(),执行了

5.4 @AfterReturning后置通知

@AfterReturning后置通知

属性:value 切入点表达式

returning 自定义的变量,表示目标方法的返回值

自定义变量名称必须和通知方法的形参名一样

位置:在方法的上面

特点:

1.在目标方法之后执行的。

2.能获取到目标方法的执行结果

3.不会影响目标方法的执行

方法的参数:

Object res:表示目标方法的返回值,使用res接收doAfter()的调用结果,

Object res = doAfter()。

后置通知的执行顺序:

Object res = SomeServiceImpl.doAfter();

myAfter(res);

demo:

package com.feiyang.service;


public interface SomeService {

String doAfter();

}
package com.feiyang.service.impl;



import com.feiyang.service.SomeService;


/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/

public class SomeServiceImpl implements SomeService {



@Override

public String doAfter() {

System.out.println("后置通知doAfter()方法执行了");

return "res";

}

}
package com.feiyang.handler;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;

/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/

@Aspect
public class MyAspectj {

//后置通知

@AfterReturning(value = "execution(* *..doAfter(..))",returning = "res")

public void myAfter(Object res){

System.out.println("后置通知,在目标方法之后执行,能拿到业务执行结果:" + res);

}
}
package com.feiyang;

import com.feiyang.service.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/

public class Mytest01 {


@Test

public void test01(){
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
SomeService someService = (SomeService) ctx.getBean("someService");
someService.doAfter();
}

}

执行结果:

后置通知doAfter()方法执行了

后置通知,在目标方法之后执行,能拿到业务执行结果:res

注意:JoinPoint jp 必须是第一个参数

//后置通知

@AfterReturning(value = "execution(* *..doAfter(..))",returning = "res")

public void myAfter(JoinPoint jp,Object res){

System.out.println("后置通知,在目标方法之后执行,能拿到业务执行结果:" + res);

}

下面这样会报错:

//后置通知

@AfterReturning(value = "execution(* *..doAfter(..))",returning = "res")

public void myAfter(Object res,JoinPoint jp){

System.out.println("后置通知,在目标方法之后执行,能拿到业务执行结果:" + res);

}

5.5 @Around环绕通知

@Around : 环绕通知

属性 : value

位置 : 在方法定义的上面

返回值 : Object,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值)

参数 : ProceedingJoinPoint,相当于反射中的Method

作用 : 执行目标方法的,等于Method.invoke()

特点 :

1.在目标方法前后都能增强功能

2.控制目标方法是否执行

3.修改目标方法的执行结果

demo:

package com.feiyang.service;

public interface SomeService {
String doFirst(String name);
}

package com.feiyang.service.impl;

import com.feiyang.service.SomeService;

/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/
public class SomeServiceImpl implements SomeService {

@Override
public String doFirst(String name) {
System.out.println("业务方法执行了。。。");
return "sss";
}
}
package com.feiyang.handler;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;


/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/


@Aspect
public class MyAspectj {


//后置通知

@AfterReturning(value = "execution(* *..doAfter(..))",returning = "res")
public void myAfter(JoinPoint jp,Object res){
System.out.println("后置通知,在目标方法之后执行,能拿到业务执行结果:" + res);
}


//环绕通知
@Around("execution(* *..doFirst(..))")
public void myAround(ProceedingJoinPoint pjp) throws Throwable {
//可以控制在业务方法执行前执行什么逻辑
System.out.println("执行了环绕通知的myAround()方法,在业务方法执行前");


//可以控制业务方法是否执行
String returnValue = (String)pjp.proceed();


//可以控制返回值
String res="";
if("aaa".equals(returnValue)){
res = returnValue;
}else{
res = "bbb";
}


//可以控制在业务方法执行后执行什么逻辑
System.out.println("执行了环绕通知的myAround()方法,在业务方法执行后: " + res);
}
}
package com.feiyang;



import com.feiyang.service.SomeService;

import org.junit.Test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;



/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/

public class Mytest01 {



//环绕通知

@Test

public void test03(){

String config = "applicationContext.xml";

ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

SomeService someService = (SomeService) ctx.getBean("someService");

someService.doFirst("张三");

}



}

执行结果:

执行了环绕通知的myAround()方法,在业务方法执行前

业务方法执行了。。。

执行了环绕通知的myAround()方法,在业务方法执行后bbb

5.6 @AfterThrowing异常通知

//异常通知

@AfterThrowing(value = "execution(* *..doSecond(..))", throwing = "ex")

public void myAfterThrowing(Exception ex){

System.out.println("异常通知,执行目标方法时抛异常了,异常原因:" + ex.getMessage());

}

@Override

public void doSecond(String name) {

int z;

z = 5/0;

System.out.println("异常通知业务方法执行了。");

}

//异常通知

@Test

public void test04(){

String config = "applicationContext.xml";

ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

SomeService someService = (SomeService) ctx.getBean("someService");

someService.doSecond("lisi");

}

执行结果:

异常通知,执行目标方法时抛异常了,异常原因:/ by zero

5.7 @After最终通知

在目标方法之后执行,总是会被执行,可以用来做程序最后的首位工作,例如清除临时数据,变量,清理内存等。

//最终通知

@After("execution(* *..doThird(..))")

public void myAfter(){

System.out.println("最终通知总是会执行。");

}

//最终通知

@Test

public void test05(){

String config = "applicationContext.xml";

ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

SomeService someService = (SomeService) ctx.getBean("someService");

someService.doThird();

}

执行结果:

业务方法执行了。

最终通知总是会执行。

5.8 @Pointcut

定义和管理切入点,不是通知注解。

package com.feiyang.handler;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import java.util.Date;


/**

* @author:飞扬

* @公众hao:程序员飞扬

* @description:

*/

@Aspect
public class MyAspectj {

@Before(value = "mypt()")

public void method1(){

System.out.println("业务逻辑执行了");

}


@After(value = "mypt()")

public void method2(){

System.out.println("业务逻辑执行了");

}


@Pointcut("execution(* *..doThird(..))")

public void mypt(){

//无需代码

}

}

六、AOP总结

        AOP是一种动态的技术思想,目的是实现业务功能和非业务功能的解耦合。业务功能是独立的模块,其他功能也是独立的业务模块。例如事务功能,日志等等。让这些功能,如事务,日志等是可以复用的。

        当目标方法需要一些功能时,可以在不修改源代码的情况下,使用aop的技术在程序执行期间,生成代理对象,通过执行业务方法,增加了功能。

猜你喜欢

转载自blog.csdn.net/lu_xin5056/article/details/128975249