AOP理解与使用

  AOP:面向切面编程,相对于OOP面向对象编程。Spring的AOP的存在目的是为了解耦。AOP可以让一组类共享相同的行为。在OOP中只能继承和实现接口,且类继承只能单继承,阻碍更多行为添加到一组类上,AOP弥补了OOP的不足。还有就是为了清晰的逻辑,让业务逻辑关注业务本身,不用去关心其它的事情,比如事务。

一:AOP术语

aop 有一堆术语,非常难以理解,简单说一下

  (1)通知(有的地方叫增强)(Advice):需要完成的工作叫做通知,就是你写的业务逻辑中需要比如事务、日志等先定义好,然后需要的地方再去用。

  (2)连接点(Join point):就是spring中允许使用通知的地方,基本上每个方法前后抛异常时都可以是连接点。

  (3)切点(Poincut):其实就是筛选出的连接点,一个类中的所有方法都是连接点,但又不全需要,会筛选出某些作为连接点做为切点。如果说通知定义了切面的动作或者执行时机的话,切点则定义了执行的地点。

  (4)切面(Aspect):其实就是通知和切点的结合,通知和切点共同定义了切面的全部内容,它是干什么的,什么时候在哪执行。

  (5)引入(Introduction):在不改变一个现有类代码的情况下,为该类添加属性和方法,可以在无需修改现有类的前提下,让它们具有新的行为和状态。其实就是把切面(也就是新方法属性:通知定义的)用到目标类中去。

  (6)目标(target):被通知的对象。也就是需要加入额外代码的对象,也就是真正的业务逻辑被组织织入切面。

  (7)织入(Weaving):把切面加入程序代码的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:

  编译期:切面在目标类编译时被织入,这种方式需要特殊的编译器。
  类加载期:切面在目标类加载到JVM时被织入,这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。
  运行期:切面在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP就是以这种方式织入切面的。

 例:

public class UserService{
    void save(){}
}

  在UserService中的save()方法前需要开启事务,在方法后关闭事务,在抛异常时回滚事务。那么,UserService中的所有方法都是连接点(JoinPoint),save()方法就是切点(Poincut)。需要在save()方法前后执行的方法就是通知(Advice),切点和通知合起来就是一个切面(Aspect)。save()方法就是目标(target)。把想要执行的代码动态的加入到save()方法前后就是织入(Weaving)。有的地方把通知称作增强是有道理的,在业务方法前后加上其它方法,其实就是对该方法的增强。

二:通知类型

  before(前置通知): 在方法开始执行前执行

  after(后置通知): 在方法执行后执行

  afterReturning(返回后通知): 在方法返回后执行

  afterThrowing(异常通知): 在抛出异常时执行

  around(环绕通知): 在方法执行前和执行后都会执行

三:执行顺序

  around > before > around > after > afterReturning

四:最常用的execution解释

例: execution(* com.example..service.*.*(..))

  (1)execution 表达式的主体

  (2)第一个* 代表任意的返回值

  (3)com.jiuxian aop所横切的包名

  (4)包后面.. 表示当前包及其子包

  (5)第二个* 表示类名,代表所有类

  (6).*(..) 表示任何方法,括号代表参数 .. 表示任意参数

例: execution(* com.example..service.*Service.add*(String))

表示: com.example包及其子包下的service包下,类名以Service结尾,方法以add开头,参数类型为String的方法的切点。

五:通知实现

1.配置类

@Aspect
@Component
public class TransactionAop {

    @Pointcut("execution(* com.example..service.*.*(..))")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void beginTransaction() {
        System.out.println("before beginTransaction");
    }

    @After("pointcut()")
    public void commit() {
        System.out.println("after commit");
    }

    @AfterReturning("pointcut()", returning = "returnObject")
    public void afterReturning(JoinPoint joinPoint, Object returnObject) {
        System.out.println("afterReturning");
    }

    @AfterThrowing("pointcut()")
    public void afterThrowing() {
        System.out.println("afterThrowing afterThrowing  rollback");
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            System.out.println("around");
            return joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
            throw e;
        } finally {
            System.out.println("around");
        }
    }
}

2.service方法

public interface UserService {
    String save(String user);
}

@Service
public class UserServiceImpl implements UserService {
    @Override
    public String save(String user) {
        System.out.println("保存用户信息");
        if ("a".equals(user)) {
            throw new RuntimeException();
        }
        return user;
    }
}

3.测试类

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAopApplicationTests {

    @Resource
    private UserService userService;

    @Test
    public void testAop1() {
        userService.save("张三");
        Assert.assertTrue(true);
    }

    @Test
    public void testAop2() {
        userService.save("a");
    }
}

4.结果

(1)执行testAop1时

around
before beginTransaction
保存用户信息
around
after commit
afterReturning :: 张三

(2)执行testAop2时

around
before beginTransaction
保存用户信息
around
after commit
afterThrowing  rollback

 5.pom文件

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

六: 引入(Introduction)

  应用场景:此时你大概已经知道通过Before、After、AfterRunning、AfterThrowing、Around 共5中通知方式为目标方法增加切面功能。比如:执行目标方法前后事务处理,打印日志等。但是如果我想在目标类中再增加一个目标方法时,该怎么办呢? 最简单的办法就是在改目标类中增加此方法。但是如果原目标类非常复杂,动一发而牵全身,而在不想改动原有接口或类的情况下,我们可以通过@DeclareParents注解实现,具体代码演示:

1.配置类

@Aspect
@Component
public class IntroductionAspect {
    //让com.lyz.report.service下的接口拥有 Introduction1Service的方法
    @DeclareParents(value = "com.lyz.report.service.*.*(..)", defaultImpl = Introduction1ServiceImpl.class)
    public Introduction1Service introduction1Service;
}

2.service代码

public interface Introduction1Service {
    void is1();
}

@Service
public class Introduction1ServiceImpl implements Introduction1Service {
    @Override
    public void is1() {
        System.out.println("我是 Introduction 1");
    }
}

public interface Introduction2Service {
    void is2();
}

@Service
public class Introduction2ServiceImpl implements Introduction2Service {
    @Override
    public void is2() {
        System.out.println("我是 Introduction 2");
    }
}

 3.测试 Controller

@RestController
@RequestMapping(value = "/report")
public class ReportController {
    @Autowired
    private Introduction2Service introduction2Service;
    
    @RequestMapping(value = "test1", method = RequestMethod.GET)
    public void test1() {
        introduction2Service.is2();
        Introduction1Service introduction1Service= (Introduction1Service) introduction2Service;
        doSthService.is1();
    }
}

 4.浏览器访问 http://192.168.99.85:5004/report/test1结果

我是 Introduction 2
我是 Introduction 1

 

posted @ 2019-12-24 16:09  刘杨钊  阅读(496)  评论(0)    收藏  举报