(四)Spring-AOP


(四)Spring-AOP

一、AOP背景知识

1.1 相关概念

1 横切关注点:

跨越应用程序多个模块的方法或功能。

即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点,如日志,安全,缓存,事务等等...

2 切面(ASPECT)

横切关注点,被模块化的特殊对象。即,它是一个类。比如,专注于完成日志的Log类

3 通知(Advice)

切面必须要完成的工作。即,它是类中的一个方法。比如,专注于完成日志的Log类的一个方法。

4 目标(Target)

被通知对象。

5 代理(Proxy)

向目标对象应用通知之后创建的对象。就是我们生成的代理类。

6 切入点(PointCut)

切面通知执行的“地点”的定义。说白了就是在哪个地方执行,如jdk动态代理中InvocationHandler的invoke()方法

7 连接点(JointPoint)

与切入点匹配的执行点。

1.2 Spring中5种Advice

在spring中,通过advice定义横切逻辑,spring中支持5种类型的advice

通知类型 连接点 实现接口
前置通知 方法前 org.springframework.aop.MethodBeforeAdvice
后置通知 方法后 org.springframework.aop.AfterReturningAdvice
环绕通知 方法前后 org.aopalliance.intercept.MethodInterceptor
异常抛出通知 方法抛出异常 org.springframework.aop.ThrowsAdvice
引介通知 类中增强新的方法属性 org.springframework.aop.IntroductionIntercepter

二、使用Spring实现AOP

2.1 导入相关依赖或者包

使用AOP织入,需要导入一个依赖包


    org.aspectj
    aspectjweaver
    1.8.9

2.2 实现AOP的3个方式

2.2.1 方式一:使用spring的AOP接口

在application-context注册对象和aop切面以及切点,spring会自动帮拦截目标方法

1 定义目标(被代理)的接口
package com.happy.service;

public interface UserService {
    void add();
    void delete();
    void update();
    void query();

}

2 编写目标类,实现接口
package com.happy.service.impl;

import com.happy.service.UserService;

public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("增加了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void update() {
        System.out.println("更新了一个用户");
    }

    @Override
    public void query() {
        System.out.println("查询了一个用户");
    }
}

3 编写切面(类)和advice(方法)
package com.happy.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

//这个类是切面
public class BeforeLog implements MethodBeforeAdvice {

    //这个方法是通知
    //spring会在我们执行目标方法前自动调用
    //method为要执行的目标对象的方法
    //target标志目标对象
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass()+"的"+method.getName()+"开始执行前记录日志");
    }
}

package com.happy.log;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

//一个切面
public class AfterLog implements AfterReturningAdvice {


    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass()+"的"+method.getName()+"执行结束后记录日志");
        System.out.println("返回结果为->"+returnValue);
    }
}

4 注册目标类(被代理类)、切面以及切点

在spring的xml上下文中注册 以及切面、切点

<?xml version="1.0" encoding="UTF-8"?>


    
    
    

    

    
    
    
        
        
        

        
        
        
    
    

5 执行目标方法
package com.happy.service;

import com.happy.service.impl.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserServiceTest {

    @Test
    public void test() {

        ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();

        System.out.println("==============自己new对象=================");
        //必须用容器管理的bean才能执行advice方法
        UserServiceImpl userService2 = new UserServiceImpl();
        userService2.add();

    }
}

2.2.2 方式二:使用自定义切面

  • 可以自定义一个切面
  • 但没有方式一强大, 因为自定义切面的advice只是普通方法,拿不到method等上下文
1 定义目标(被代理)的接口

同方式一

2 编写目标类

同方式一

3 编写自定义切面Aspect
package com.happy.diyAspect;

public class MyAspect {

    public void before(){
        System.out.println("div myApsect============方法执行前");
    }

    public void after(){
        System.out.println("diy myApsect============方法执行后");
    }
}

4 注册目标类、切面以及切点

同方式一,也在xml中配置,当稍微配置不同

<?xml version="1.0" encoding="UTF-8"?>


    
    
    

    

    
    
    


    
    
    
        
            
            
            
        
    
    

5 执行目标方法
package com.happy.service;

import com.happy.service.impl.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserServiceTest {

    @Test
    public void test() {

        ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();

        System.out.println("==============自己new对象=================");
        //必须用容器管理的bean才能执行advice方法
        UserServiceImpl userService2 = new UserServiceImpl();
        userService2.add();

    }
}

2.2.3 方式三:使用注解

1 编写注解模式的自定义切面
package com.happy.annotationAspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAnnotationAspect {

    @Before("execution(* com.happy.service.impl.UserServiceImpl.* (..))")
    public void before(){
        System.out.println("=="+this.getClass().getName()+"执行before方法");
    }

    @After("execution(* com.happy.service.impl.UserServiceImpl.* (..))")
    public void after(){
        System.out.println("=="+this.getClass().getName()+"执行after方法");
    }

    @Around("execution(* com.happy.service.impl.UserServiceImpl.* (..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("===="+this.getClass().getName()+"执行环绕around方法1====");
        System.out.println("signature->"+joinPoint.getSignature());
        Object proceed = joinPoint.proceed();
        System.out.println("===="+this.getClass().getName()+"执行环绕around方法2====");

    }
}

2 开启注解模式的aop
<?xml version="1.0" encoding="UTF-8"?>


    
    
    

    

    
    
    
    

3 执行目标方法
4 spring支持的两种动态代理模式

proxy-target-class="false"-->jdk,默认!

proxy-target-class="true"-->cglib


区别在于cglib不要求被代理类实现接口。而jdk要求被代理类必须先定义核心业务接口,再实现核心业务接口。