Spring AOP(四)AOP开发详解

版权声明:From Lay https://blog.csdn.net/Sadlay/article/details/83478087

AOP开发详解

这里我们主要采用@AspectJ的注解方式讨论AOP的开发,因为Spring AOP只能对方法进行拦截,所以I首先要确定需要拦截什么方法,让它能织入约定的流程中。

确定连接点

任何AOP编程,首先确定的是在什么地方需要AOP,也就是需要确定连接点(什么类的什么方法)的问题。现在我们假设有一个UserService接口,它有一个printUser方法。

UserService接口

package com.lay.springboot_aop.aspect.service;

import com.lay.springboot_aop.aspect.pojo.User;

public interface UserService {
    public void printUser(User user);
}

UserServiceImpl实现类

package com.lay.springboot_aop.aspect.service.impl;

import org.springframework.stereotype.Service;

import com.lay.springboot_aop.aspect.pojo.User;
import com.lay.springboot_aop.aspect.service.UserService;
@Service
public class UserServiceImpl implements UserService{

    @Override
    public void printUser(User user) {
        if(user==null) {
            throw new RuntimeException("参数为空");
        }
        System.out.println("id = "+user.getId());
        System.out.println("name = "+user.getUserName());
        System.out.println("message = "+user.getMessage());
    }
    
}

User实体类

package com.lay.springboot_aop.aspect.pojo;

public class User {
    public Integer id;
    
    public String userName;
    
    public String message;
    
    public Integer getId() {
        return id;
    }
    
    public void setId(Integer id) {
        this.id = id;
    }
    
    public String getUserName() {
        return userName;
    }
    
    public void setUserName(String userName) {
        this.userName = userName;
    }
    
    public String getMessage() {
        return message;
    }
    
    public void setMessage(String message) {
        this.message = message;
    }
    
}

这样一个普通的服务的接口和实现类就实现了。

下面我们将以printUser方法为连接点,进行AOP编程

开发切面

有了连接点,我们还需要一个切面,通过它可以描述AOP的信息和流程的织入。下面我们创建一个切面类

MyAspect切面类

package com.lay.springboot_aop.aspect;

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.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;

import com.lay.springboot_aop.aspect.pojo.User;
import com.lay.springboot_aop.aspect.validator.UserValidator;
import com.lay.springboot_aop.aspect.validator.impl.UserValidatorImpl;

@Aspect
public class MyAspect { 
    @Before("execution(* com.lay.springboot_aop.aspect.service.impl.UserServiceImpl.printUser(..))")
    public void before() {
        System.out.println("before-----------");
    }
    
    @Around("pointCut()")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("around before-------------");
        jp.proceed();
        System.out.println("around after-------------");
    }
    
    @After("execution(* com.lay.springboot_aop.aspect.service.impl.UserServiceImpl.printUser(..))")
    public void after() {
        System.out.println("after------------");
    }
    
    @AfterReturning("execution(* com.lay.springboot_aop.aspect.service.impl.UserServiceImpl.printUser(..))")
    public void afterReturning() {
        System.out.println("afterReturning------------");
    }
    
    @AfterThrowing("execution(* com.lay.springboot_aop.aspect.service.impl.UserServiceImpl.printUser(..))")
    public void afterThrowning() {
        System.out.println("afterThrowning------------");
    }
}

这里解释一下。首先Spring是以@Aspect注解作为切面声明的,让Spring知道这是一个切面,然后我们就可以通过各类注解来定义各类的通知了,例如@Before、@After 、@AfterReturning和 @AfterThrowing等几个注解。

Around环绕通知

环绕通知(Around)是所有通知中最为强大的通知,强大意味着难以控制。一般而言,使用它的场景是你需要大幅度修改原有目标对象的服务逻辑时,否则都尽量使用其他的通知。

环绕通知:取代原有目标对象方法的通知,提供了回调原有目标对象方法的能力。

	@Around("pointCut()")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("around before-------------");
        jp.proceed();
        System.out.println("around after-------------");
    }

它拥有一个ProceedingJoinPoint类型的参数。这个参数的对象有一个proceed()方法,通过这个方法可以回调原有目标对象的方法。

切点定义

在切面的定义中,我们看到例如@Before、@After 、@AfterReturning和 @AfterThrowing等几个注解,它们会定义一个正则表达式,这个正则式的作用就是定义什么时候启用AOP,毕竟不是所有的功能都是需要启动AOP的,也就是Spring会通过这个正则式去匹配确定对应的方法(连接点)是否启动切面编程。上面我们在每个注解后都重复写了同一个正则式,显然是比较冗余。为了克服这个问题,Spring定义了切点(Pointcut)的概念。

切点(Pointcut):向Spring描述哪些类的哪些方法需要启动AOP编程。

有了切点的概念,我们可以做如下修改

MyAspect切面类

package com.lay.springboot_aop.aspect;

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.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;

import com.lay.springboot_aop.aspect.pojo.User;
import com.lay.springboot_aop.aspect.validator.UserValidator;
import com.lay.springboot_aop.aspect.validator.impl.UserValidatorImpl;

@Aspect
public class MyAspect {
    
    
    @Pointcut("execution(* com.lay.springboot_aop.aspect.service.impl.UserServiceImpl.printUser(..))")
    public void pointCut() {
    }
    
    @Before("pointCut()")
    public void before(JoinPoint point ,User user) {
        System.out.println("before-----------");
    }
    
    @Around("pointCut()")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("around before-------------");
        jp.proceed();
        System.out.println("around after-------------");
    }
    
    @After("pointCut()")
    public void after() {
        System.out.println("after------------");
    }
    
    @AfterReturning("pointCut()")
    public void afterReturning() {
        System.out.println("afterReturning------------");
    }
    
    @AfterThrowing("pointCut()")
    public void afterThrowning() {
        System.out.println("afterThrowning------------");
    }
}

代码中,使用了注解@Pointcut来定义切点,它标注在pointCut方法上,则在后面的通知注解中就可以使用方法名称来定义了。

正则表达式

我们对正则式简单了解一下,首先我们看下面的正则式

execution(* com.lay.springboot_aop.aspect.service.impl.UserServiceImpl.printUser(..))

其中

  • execution:表示在执行的时候,拦截里面的正则匹配的方法;
  • *:表示任意返回类型的方法
  • com.lay.springboot_aop.aspect.service.impl.UserServiceImpl:指定目标对象的全限定名
  • printUser:指定目标对象的方法
  • (…):任意参数

这样Spring通过正则式知道你需要对类UserServiceImpl的printUser方法进行AOP增强。

对于这个正则式,我们还可以使用AspectJ的指示器。

项目类型 描述
arg() 限定连接点方法参数
@args() 通过连接点方法参数上的注解进行限定
execution() 用于匹配连接点的执行方法
this() 限定连接点匹配AOP代理Bean引用为指定的类型
target 目标对象(即被代理对象)
@target() 限定目标对象的配置了指定的注解
within 限定连接点匹配指定的类型
@within() 限定连接点带有匹配注解类型
@annotation() 限定带有指定注解的连接点

测试AOP

完成了连接点、切面和切点定义,接下来我们可以进行测试AOP。

我们创建一个用户控制器(UserController)

UserController

package com.lay.springboot_aop.aspect.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.lay.springboot_aop.aspect.pojo.User;
import com.lay.springboot_aop.aspect.service.UserService;
import com.lay.springboot_aop.aspect.validator.UserValidator;

@Controller
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService=null;
    
    @RequestMapping("/print")
    @ResponseBody
    public User printUser(Integer id,String userName,String message) {
        User user=new User();
        user.setId(id);
        user.setUserName(userName);
        user.setMessage(message);
        userService.printUser(user);
        return user;
    }
}

这里通过自动注入UserService服务接口,然后使用它进行用户信息的打印。方法标注了@ResponseBody,所以最后Spring MVC会将其转换为JSON响应请求(如果控制器是注解@RestController包含了@ResponseBody)。

启动类SpringbootAopApplication

package com.lay.springboot_aop;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.lay.springboot_aop.aspect.MyAspect;

@SpringBootApplication(scanBasePackages= {"com.lay.springboot_aop.aspect"})
public class SpringbootAopApplication {
    
	public static void main(String[] args) {
		SpringApplication.run(SpringbootAopApplication.class, args);
	}
	@Bean(name="myAspect")
	public MyAspect initMyAspect() {
	    return new MyAspect();
	}
}

然后启动程序,打开浏览器输入请求地址:http://localhost:8080/user/print?id=1&userName=username&message=men

查看控制台打印,就可以看到方法被织入到对应的流程中了。

猜你喜欢

转载自blog.csdn.net/Sadlay/article/details/83478087
今日推荐