【Spring】AOP面向切面编程(Spring4和Spring5区别)

1. 什么是AOP

AOP:能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
在这里插入图片描述

2. AOP常用注解

@Component
@Aspect  //@Aspect 表示它是一个切面。
public class DemoAspect {
  //配置切入点 
  @Pointcut("execution(* com.aop.service.*.*(..))")
  public void myPointCut () {
  }

  //配置前置通知
  @Before("myPointCut()")
  public void myBefore () {
    System.out.println("——————前置通知————");
  }

  //*returning为连接点的返回值,命名须与myAfterReturning()方法的Object参数一致。
  @AfterReturning(pointcut = "myPointCut()", returning = "obj")
  public void myAfterReturning (JoinPoint point, Object obj) {
    System.out.println("——————返回通知————");
    //获取连接点方法传入的实参
    Object[] args = point.getArgs();
    //获取连接点方法的方法名
    String methodName = point.getSignature().getName();
    //获取连接点方法所在的对象
    Object targetObj = point.getTarget();
    String targetClassName = point.getClass().getName();
  }

  @AfterThrowing(pointcut = "myPointCut()", throwing = "eeeee")
  public void myAfterThrowing (JoinPoint point, Exception eeeee) {
    //注意那串eeeee了吗,那是为了引起你的注意,告诉你这两个命名须一致。
    System.out.println("——————异常通知————" + eeeee.getMessage());
  }

  @After("myPointCut()")
  public void myAfter (JoinPoint point) {
    System.out.println("——————最终通知————" + point.getSignature().getName());
  }
}

3.Spring4各种通知的执行节点


在这里插入图片描述
*如果抛出异常,那@Around方法的后半部分就应该是不会执行的

4. 在aop中校验不通过如何不让程序进入核心代码?

通过aop中注解的执行的先后顺序我们知道,校验发生在核心代码前面的只剩下两个——@Before,@Around。
@Before : 这个注解只有在异常时才不会走核心方法——连接点。正常@Before无法阻止当前线程进入连接点。
@Around : 这个注解在连接点前后执行。并且注解的方法传入的ProceedingJionPoint 类中封装的代理方法proceed()可以让当前线程从aop方法转到连接点——核心代码方法。所以一般我们用这个注解,如果aop的安全校验不通过,则不调用proceed()方法,就永远不会进入连接点。
除此外,要注意除了Around注解的方法可以传ProceedingJionPoint 外,别的几个都不能传这个类。

5. 同一个方法被多个Aspect类拦截

aspect1 和 aspect2 的执行顺序是未知的。

5.1 如何指定每个 aspect 的执行顺序呢?

给aspect添加@Order注解,该注解全称为:org.springframework.core.annotation.Order

@Order(5)
@Component
@Aspect
public class Aspect1 {
    // ...
}

@Order(6)
@Component
@Aspect
public class Aspect2 {
    // ...
}

这样修改之后,可保证不管在任何情况下, aspect1 中的 advice 总是比 aspect2 中的 advice 先执行。如下图所示:
在这里插入图片描述

注意:

  • 如果在同一个 aspect 类中,针对同一个 pointcut,定义了两个相同的 advice(比如,定义了两个@Before),那么这两个 advice 的执行顺序是无法确定的,哪怕你给这两个 advice 添加了@Order这个注解,也不行。这点切记。

  • 对于@Around这个advice,不管它有没有返回值,但是必须要方法内部,调用一下pjp.proceed();否则,Controller 中的接口将没有机会被执行,从而也导致了
    @Before这个advice不会被触发。比如,我们假设正常情况下,执行顺序为”aspect2 -> apsect1 ->controller”,如果,我们把 aspect1中的@Around中的
    pjp.proceed();给删掉,那么,我们看到的输出结果将是:

6. AOP应用:

6.1 创建目标对象类

public interface IService {
	String update(String msg);
}

@Service
public class IServiceImpl implements IService {
	@Override
	public String update(int uid, String msg) {
		System.out.println("serviceimpl运行了");		
		return "更新了数据:"+msg;
	}
}

6.2 切面

@Component
@Aspect  //@Aspect 表示它是一个切面。
public class DemoAspect {
	//配置切入点 
	@Pointcut("execution(* com.aop.service.*.*(..))")
	public void myPointCut() {}
	//配置前置通知
	@Before("myPointCut()")
	public void myBefore() {
		System.out.println("——————前置通知————");
	}
}

在这里插入图片描述

@Pointcut("execution("result range")
Pointcut注解有两个参数,result是返回值的类型 , range是指定范围来配置通知的类,两个参数用空格隔开
在演示代码中,我是用的 * com.aop.service..(…)),意义如下。

@Pointcut("execution(任意返回值     com包.aop包.service包.所有类.所有方法(任意参数))")
//其中返回值、包、类、方法的通配符为 *
//参数的通配符为 ..

6.3 测试前置通知

public class Test {
	public static void main(String[] args) {
		ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
		IService ser=ac.getBean(IService.class);	
		//update方法只是返回了这段字符串,所以并不会打印。
		ser.update("测试字符串");
		}
}

在这里插入图片描述

6.4 返回通知、异常通知、后置通知

//*returning为连接点的返回值,命名须与myAfterReturning()方法的Object参数一致。
@AfterReturning(pointcut="myPointCut()",returning="obj")
public void myAfterReturning(JoinPoint point,Object obj) {
	System.out.println("——————返回通知————");
	//获取连接点方法传入的实参
	Object[] args=point.getArgs();
	//获取连接点方法的方法名
	String methodName=point.getSignature().getName();
	//获取连接点方法所在的对象
	Object targetObj=point.getTarget();
	String targetClassName=point.getClass().getName();
}

@AfterThrowing(pointcut="myPointCut()",throwing="eeeee")
public void myAfterThrowing(JoinPoint point,Exception eeeee) {
	//注意那串eeeee了吗,那是为了引起你的注意,告诉你这两个命名须一致。
	System.out.println("——————异常通知————"+eeeee.getMessage());
}

@After("myPointCut()")
public void myAfter(JoinPoint point) {
	System.out.println("——————最终通知————"+point.getSignature().getName());
}

spring5执行结果:
在这里插入图片描述

6.5 环绕通知

*环绕通知的返回值必须是Object,形参必须是ProceedingJoingPoint。

@Component
@Aspect
public class DemoAspect {
//配置切入点 
@Pointcut("execution(* com.aop.service.*.*(..))")
public void myPointCut() {}
//环绕通知
@Around("myPointCut()")
public Object aroundAdvice(ProceedingJoinPoint proceeding) {
	//和JoinPoint一样,ProceedingJoinPoint也可以获取
	//连接点方法的实参
	Object[] args=proceeding.getArgs();
	//连接点方法的方法名
	String methodName=proceeding.getSignature().getName();
	//连接点方法所在的对象
	Object targetObj=proceeding.getTarget();
	String targetClassName=targetObj.getClass().getName();
	
	Object result=null;
	try {
		System.out.println("前置通知====");
		//执行连接点的方法 获取返回值
		result=proceeding.proceed(args);
		System.out.println("返回通知====");
		}catch (Throwable e) {
			System.out.println("异常通知===");
		}finally {
			System.out.println("最终通知===");
		} 
	return result;
}

Spring5运行结果:
在这里插入图片描述

7. Spring4和Spring5的区别:

Springboot1.X底层应用的是Spring4
Springboot2.X底层应用的是Spring5
当系统应用升级,应该考虑一下以下变更。
在这里插入图片描述

8.动态代理静态代理

对静态代理、JDK动态代理、CGLib动态代理做一个总结,静态代理的维护成本比较高,有一个被代理类就需要创建一个代理类,而且需要实现相同的接口。JDK动态代理模式和CGLib动态代理的区别是JDK动态代理需要被代理类实现接口,而CGLib则是生成被代理类的子类,要求被代理类不能是final的,因为final类无法被继承。

动态代理总结:https://www.cnblogs.com/teach/p/10763845.html
参考链接:https://blog.csdn.net/rainbow702/article/details/52185827
参考链接:https://blog.csdn.net/zhanglf02/article/details/78132304
示例参考:https://blog.csdn.net/saltsoul/article/details/93140707
更多详细案例:https://www.cnblogs.com/joy99/p/10941543.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值