SpringAOP Advice 通知例子

前言

前些天写了【详解什么是Spring AOP】虽然叫详解,但是里面其实还是留了很多内容没有说。于是就想着还是写几个辅助的文章,把前面博客里面的坑给填上,既然要做了就要做好对不对。那么这次要填的坑就是Spring AOP里面的Advice语义中的方法都应该怎么用,这部分的内容在官网【Declaring Advice】这部分的章节里也能够找到。更多Spring内容进入【Spring解读系列目录】

Advice Sample

概念的正文已经在详解这个博客中详细的说过了,所以这里就不多罗嗦了。还是老样子,我们首先要引入必要的依赖,构建一个小demo。假设我们有一个接口Dao,有一个实现类DemoDao,有一个切面类MyAspect,一个测试类MainTest,一个配置类AppConfig。

<!-- Spring上下文是一个配置,向Spring框架提供上下文信息。 -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.8.RELEASE</version>
</dependency>
<!--aspectj语法支持-->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.6</version>
</dependency>
@Configuration
@ComponentScan("com")
@EnableAspectJAutoProxy //开启AspectJ语法支持
public class AppConfig {
}
@Component
@Aspect
public class MyAspect {
    @Pointcut("within(com.demo.dao.*)")
    public void myPointCutWithin(){}
}
public interface Dao {
	void print();
    String printString(String str) throws Exception;
}
@Repository("demoDao")
public class DemoDao implements Dao{
    @Override
    public void print() {
        System.out.println("print Empty");
    }
    @Override
    public String printString(String str) throws Exception{  //throws抛出异常
        System.out.println("print String:"+str);
        return str;
    }
}
public class MainTest {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(AppConfig.class);
        Dao dao= (Dao) anno.getBean("demoDao");
        dao.printString("parameter");
    }
}

Advice Type Sample

Before Advice

添加Before语句到MyAspect中,运行main()方法:

@Before("myPointCutWithin()")
public void beforeDao(){
	System.out.println("this is a beforeDao");
}

运行结果,在方法执行前打印了。
this is a beforeDao
print String:param

After (Finally) Advice

添加After语句到MyAspect中,运行main()方法:

@After("myPointCutWithin()")
public void afterFinally(){
	System.out.println("this is a afterFinally");
}

运行结果,在方法执行后打印了。
print String:param
this is a afterFinally

After Returning Advice

添加AfterReturning语句到MyAspect中,运行main()方法:

@AfterReturning("myPointCutWithin()")
public void afterWithReturn(){
	System.out.println("this is a afterWithReturn");
}

运行结果,在方法执行后打印了。这个效果不容易和After (Finally)区别开来,
	因为即便是void类型的也是返回了空,所以无论是什么都会打印的。
print String:param
this is a afterWithReturn

还有更进一步的方法,我们可以拿到返回值:

@AfterReturning(pointcut="myPointCutWithin()", returning = "str") //获取返回值
public void afterWithReturnValue(Object str){
	System.out.println(str.toString()); //打印返回值
	System.out.println("this is a afterWithReturnValue");
}
运行结果:
print String:param
param	//连接点返回的值
this is a afterWithReturnValue

After Throwing Advice

添加AfterThrowing语句到MyAspect中,但是这个还是有些不一样的,因为我们必须要用throws语句抛出这个异常,才能被通知到,所以我们要改造一下DemoDao,让其能报一个空指针异常。

@AfterThrowing("myPointCutWithin()")
public void afterWithException(){
	System.out.println("this is a afterWithException");
}
@Repository("demoDao")
public class DemoDao implements Dao{
    Dao dao;
    /****/
    @Override
    public String printString(String str) throws Exception{
        System.out.println("print String:"+str);
        dao.print();  //调用到这里抛出异常
        return "String";
    }
}
运行结果,在方法执行抛出异常以后被通知到了。做个提醒:有异常最终一定要try-catch处理,千万不要让你的代码裸奔,
	养成良好的编码习惯是非常重要的。这里为了让大家看清楚,做了一个demo,实际上应该改造为下面的样子。
print String:param
this is a afterWithException
Exception in thread "main" java.lang.NullPointerException
	at com.demo.dao.DemoDao.printString(DemoDao.java:17)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
	
******************捕获你的异常进行处理,杜绝裸奔。******************
public class MainTest {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(AppConfig.class);
        Dao dao= (Dao) anno.getBean("demoDao");
        try {
            dao.printString("param");
        }catch (Exception e){
            System.out.println("Do something");
        }
    }
}
输出结果:
print String:param
this is a afterWithException
Do something

这个方法也有一个升级版,可以允许你指定想要捕获的异常,比如这里指定捕获空指针异常被通知。这里如果写了别的异常比如官网提供的DataAccessException,通知就无法调用了:

//使用NullPointerException作为参数传递进来,或者使用更广泛的Exception,不过
@AfterThrowing(pointcut="myPointCutWithin()", throwing="ex") //指定空指针异常
public void afterWithException2(NullPointerException ex){ 
	System.out.println("this is a target afterWithException2");
}
输出结果:
print String:param
this is a target afterWithException2
Do something

Around Advice

添加Around语句到MyAspect中:

@Around("myPointCutWithin()")
public void aroundNormal(ProceedingJoinPoint pjp){
    System.out.println("this is a aroundNormal before");
    try {
        pjp.proceed();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
    System.out.println("this is a aroundNormal after");
}
运行结果:
this is a aroundNormal before
print String:param
this is a aroundNormal after

可以看到,这里面连接点是能够在通知中启动的,也就是说能够在通知中修改连接点的内容,这也是为什么官网说Around是最强大的通知,怎么修改呢?ProceedingJoinPoint 这个类已经把连接点描述清楚了,它继承了JoinPoint,所以拥有JoinPoint的一切方法。而JoinPoint正是Spring描述连接点的类,所以ProceedingJoinPoint 对象就能够获取到整个连接点的内容。比如getArgs()这个方法,就可以获取到连接点的参数,并封装成一个数组。那么我们要做的就很简单了,改变传参,修改数组的内容就好了啊。

@Around("myPointCutWithin()")
public void aroundChange(ProceedingJoinPoint pjp){
    System.out.println("this is a aroundChange before");
    Object[] objects=pjp.getArgs();
    for (int i = 0; i < objects.length; i++) {
        objects[i]="bbb"; //修改内容param为bbb
    }
    try {
        pjp.proceed(objects);
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
    System.out.println("this is a aroundChange after");
}
运行结果:
this is a aroundChange before
print String:bbb
this is a aroundChange after

Advice的执行顺序

我们有这么多的Advice类型可以执行,那么他们的执行顺序怎么界定呢?全部都打开执行一下,除了After Throwing是必须异常通知外,其他的都是不影响的。

输出结果如下,可以很明显看出:Around before->before->after return->after->Around after.
this is a aroundChange before
this is a beforeDao
print String:bbb
this is a afterWithReturn
this is a afterFinally
this is a aroundChange after

Advice 使用表达式

我们之前的例子都是使用的切点的方法作为Advice的作用域,其实如果不想写切点方法也是可以的。Advice获取的只是切点方法的注解内容而已,所以这里是能够直接用表达式替代的,例如:

@Before("within(com.demo.dao.*)")  //直接用表达式
public void beforeDao(){
	System.out.println("this is a beforeDao");
}
输出:
this is a beforeDao
print String:param
和使用@Before("myPointCutWithin()")效果是一模一样的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring AOP(面向切面编程)是Spring框架中的一个关键特性,它允许开发者在不修改原始代码的情况下,通过切入点和通知来实现横切关注点的功能。 下面我给出一个简单的Spring AOP例子来解释其工作原理。 假设我们有一个Spring应用程序,其中包含一个名为UserService的服务类,负责处理用户相关的业务逻辑,比如验证用户、保存用户等操作。 首先,我们需要定义一个切入点(Join point),用来确定在UserService的哪些方法上我们需要应用AOP。例如,我们可以将切入点定义为所有保存用户的方法。 其次,我们需要定义通知(Advice),它是在切入点之前或之后执行的代码逻辑。例如,我们可以定义一个前置通知(Before advice)来在保存用户之前验证用户的信息。 接下来,我们需要将切入点和通知组合起来,创建一个切面(Aspect)。切面是一个类,它包含了切入点和通知,并指定了在切入点上何时应用通知。在我们的例子中,切面可以是一个名为ValidationAspect的类,其中定义了一个前置通知方法。 最后,我们需要将切面配置为Spring容器的一部分。我们可以通过在Spring的配置文件中声明一个<aop:aspectj-autoproxy>元素来完成这一配置。这样,当应用程序启动时,Spring容器会自动为我们创建切面。 当我们调用UserService的保存用户方法时,Spring AOP就会根据切面配置来决定是否将前置通知应用于该方法。如果切入点的条件匹配,前置通知会提前执行验证用户的逻辑。 通过这个例子,我们可以看到,Spring AOP提供了一种非侵入式的方式来增强代码的功能,使得我们可以在不修改原始代码的情况下,通过切入点和通知来实现横切关注点的功能。这样可以提高代码的模块化和可重用性,降低代码的复杂性。 ### 回答2: Spring AOP(面向切面编程)是Spring框架中的一个重要特性,用于在不改变原有代码的情况下增加功能和耦合性。它通过将主要的业务逻辑(核心业务)与横切关注点(如日志记录、事务管理等)分离来实现。 下面是一个可以帮助理解Spring AOP例子:假设我们有一个简单的应用程序,其中包含一个Service层的类来处理用户的注册请求。在用户注册成功后,我们希望自动生成一个欢迎邮件,同时在控制台中记录下来。这里就可以使用Spring AOP来实现。 首先,我们需要定义一个切面类来处理横切关注点,例如记录日志和发送欢迎邮件。切面类应该实现通知(Advice)。在这个例子中,我们可以使用@Before通知来在目标方法执行之前发送欢迎邮件,并使用@After通知在目标方法执行之后记录日志。 接下来,在Spring配置文件中进行配置。我们需要定义一个切点(Pointcut),以告诉Spring在哪些方法上应用切面。在这个例子中,我们可以使用execution()表达式定义一个切点,以匹配Service层中的所有方法。 最后,在Service层的类中,我们需要使用一个@Autowired注解将切面类注入到目标类中。这样,当Service层中的方法被调用时,切面类就会自动执行。 通过这个例子,我们可以清楚地看到Spring AOP如何帮助我们将横切关注点与核心业务分离,并在不改变原有代码的情况下实现功能的增强。这样,我们可以更容易地维护、扩展和重用代码,提高系统的可维护性和灵活性。 ### 回答3: Spring AOPSpring框架中的一个关键特性,它提供了一种方法来通过动态代理技术在应用程序的不同层之间添加横切关注点。 举一个简单的例子来说明Spring AOP的用法。假设我们有一个电商网站,我们想要在用户购买商品之前验证用户的身份。我们可以通过使用Spring AOP来实现这个功能。 首先,我们需要定义一个切面(Aspect),用于对购买商品的方法进行拦截和处理。我们可以创建一个类,并在其中定义一个方法,使用@Before注解来标记它。在这个方法中,我们可以编代码来验证用户的身份,例如检查用户是否已登录。 然后,我们需要配置Spring AOP,告诉它在哪些类和方法上应用这个切面。我们可以在Spring配置文件中使用<aop:aspectj-autoproxy>元素来启用自动代理,然后在<aop:config>元素中定义切面的位置和应用规则。 接下来,我们需要在我们的购买商品方法上添加一个标记(Annotation),以告诉Spring AOP对该方法应用切面。我们可以在方法上添加一个自定义的注解,例如@RequiresAuthentication。然后,我们可以在切面的方法中使用@Pointcut注解来指定切入点,即哪些方法应该被拦截。 最后,我们可以测试这个例子。我们可以创建一个控制器(Controller),其中有一个方法用于购买商品。当用户调用该方法时,Spring AOP会自动拦截并执行切面中的逻辑,验证用户的身份是否合法。如果用户未登录或身份验证失败,可以抛出异常或返回错误信息。 综上所述,Spring AOP是一个非常强大的功能,可以帮助我们在应用程序中实现横切关注点,提高代码的模块化和可维护性。以上是一个简单的例子,展示了如何使用Spring AOP来验证用户身份。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值