Spring--AOP(面向切面编程)

spring AOP

AOP(Aspect Oriented Programming)面向切面编程

采用横向抽取机制,取代了传统纵向继承体系重复性代码
Spring AOP使用纯java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类织入增强代码

AOP相关术语

  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
  • Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
  • Advice(通知/增强):所谓通知是指拦截到Joinpoint之后要做的事情就是通知。
  • 通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
  • Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期间为类动态地添加一些方法或Field。
  • Target(目标对象):代理地目标对象
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类加载期织入
  • Proxy(代理):一个类被AOP织入增强后,就产生了一个结果代理类
  • Aspect(切面):是切入点和通知(引介)的结合

aop的底层实现

1、jdk的动态代理
2、cglib的动态代理

1、jdk的动态代理

1、新建接口UserDao

public interface UserDao {

    public void save();
    public void update();
    public void delete();
    public void find();
}

2、新建UserDao实现类UserDaoImpl。这里不做过多操作,只打印输出相关信息

public class UserDaoImpl implements UserDao{
    @Override
    public void save() {
        System.out.println("保存用户");
    }

    @Override
    public void update() {
        System.out.println("修改用户");
    }

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

    @Override
    public void find() {
        System.out.println("查询用户");
    }
}

3、新建一个代理类做增强操作

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyJdkProxy implements InvocationHandler {
    private UserDao userDao;
    public MyJdkProxy(UserDao userDao){
        this.userDao = userDao;
    }

    public Object createProxy(){
        Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),this);
        return proxy;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     //判断如果为save方法则进行数据校验
        if ("save".equals(method.getName())){
            System.out.println("权限校验....");
            return method.invoke(userDao,args);
        }

        return method.invoke(userDao,args);
    }
}

4、测试

    @Test
    public void demo1(){
        UserDao userDao = new UserDaoImpl();
        UserDao proxy = (UserDao) new MyJdkProxy(userDao).createProxy();
        proxy.save();
        proxy.update();
    }

2、cglib的动态代理

1、新建ProductDao类

public class ProductDao {
    public void save(){
        System.out.println("保存商品");
    }
    public void update(){
        System.out.println("修改商品");
    }
    public void delete(){
        System.out.println("删除商品");
    }
    public void find(){
        System.out.println("查询商品");
    }
}

2、创建代理类MyCglibProxy

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyCglibProxy implements MethodInterceptor {
    public ProductDao productDao;

    public MyCglibProxy(ProductDao productDao){
        this.productDao = productDao;
    }

    public Object createProxy(){
        //1、创建核心类
        Enhancer enhancer = new Enhancer();
        //2、设置父类
        enhancer.setSuperclass(productDao.getClass());
        //3、设置回调
        enhancer.setCallback(this);
        //4、生成代理
        Object proxy = enhancer.create();
        return proxy;
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if("save".equals(method.getName())){
            System.out.println("权限校验");
            return methodProxy.invokeSuper(proxy,args);
        }
        return methodProxy.invokeSuper(proxy,args);
    }
}

3、测试

 @Test
    public void demo1(){
        ProductDao productDao = new ProductDao();
        ProductDao proxy = (ProductDao) new MyCglibProxy(productDao).createProxy();
        proxy.save();
        proxy.update();
        proxy.delete();
        proxy.find();
    }

代理知识总结:

  • spring再运行期,生成动态代理对象,不需要特殊的编译器
  • Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术,为目标Bean执行横向织入
    1、若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理
    2、若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
  • 程序中应优先对接口创建代理,便于程序解耦维护
  • 标记为final的方法,不能被代理,因为无法进行覆盖
    1、JDK动态代理,是针对接口生成子类,接口中方法不能使用final修饰
    2、CGLib是针对目标类生产子类,因此类或方法不能使用final的
  • Spring只支持方法连接点,不提供属性连接点

Spring AOP增强类型

  • AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.Advice
  • Spring 按照通知Advice在目标类方法的连接点位置,可以分为5类
    • 前置通知org.springframework.aop.MethodBeforeAdvice
      在目标方法执行前实施增强
    • 后置通知org.springframework.aop.AfterReturningAdvice
      在目标方法执行后实施增强
    • 环绕通知org.aopalliance.intercept.MethodInterceptor
      在目标方法执行前后实施增强
    • 异常抛出通知 org.springframework.aop.ThrowAdvice
      在方法抛出异常后实施通知
    • 引介通知 org.springframework.aop.IntroductionInterceptor
      在目标类中添加一些新的方法和属性

Spring AOP切面类型

  • Advisor: 代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截
  • PointcutAdvisor:代表具有切入点的切面,可以指定拦截目标类哪些方法
  • IntroductionAdvisor:代表引介切面,针对引介通知而使用切面

1、Advisor一般切面

首先导入jar

  • spring-aop
  • aopalliance
    1、新建StudentDao接口
public interface StudentDao {
   public void find();
   public void update();
   public void save();
   public void delete();
}

2、新建StudentDao实现类StudentDaoImpl

public class StudentDaoImpl implements StudentDao {
    public void find() {
        System.out.println("查询学生");
    }
    public void update() {
        System.out.println("修改学生");
    }
    public void save() {
        System.out.println("保存学生");
    }
    public void delete() {
        System.out.println("删除学生");
    }
}

3、新建增强类MybeforeAdvice并实现MethBeforeAdvice

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class MybeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("这是前置增强(通知)");
    }
}

4、配置文件配置

   
    

    
    
    
    
        
        
        
        
        
        
    

5、测试

 @Test
    public void demo1(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext2.xml");
        //StudentDao studentDao = (StudentDao) applicationContext.getBean("studentDao");
        StudentDao studentDao = (StudentDao) applicationContext.getBean("studentDaoProxy");
        studentDao.save();
        studentDao.update();
        studentDao.delete();
        studentDao.find();
    }

配置文件中的属性配置

		 
        
  • target :目标类
  • interfaces :实现的接口
  • proxyTargetClass :是否对类代理而不是接口,设置为true时,使用CGLib代理
  • interceptorNames :需要织入目标的Advice
  • singleton:返回代理是否为单例,默认为单例
  • optimize:当设置为true时,强制使用CGLib

总结
使用普通Advice 作为切面,将对目标类所有方法进行拦截,不够灵活,在实际开发中常采用 带有切点的切面

2、PointcutAdvisor(具有切入点的切面)

常用PointcutAdvisor实现类

  • DefaultPointcutAdvisor最常用的切面类型,他可以通过任意Pointcut和Advice组合定义切面
  • JdkRegexpMethodPointcut构造正则表达式切点

1、新建CustomerDao

public class CustomerDao {
    public void find(){
        System.out.println("查询客户");
    }
    public void save(){
        System.out.println("保存客户");
    }
    public void update(){
        System.out.println("修改客户");
    }
    public void delete(){
        System.out.println("删除客户");
    }
}

2、新建MyAroundAdvice类继承MethodInterceptor,环绕增强

public class MyAroundAdvice implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("环绕前增强");
        //执行目标方法
        Object obj = invocation.proceed();
        System.out.println("环绕后增强");
        return obj;
    }
}

3、配置文件




    
    
    
    
    
    
        
        
       
        
      
        
        
        
    

    
    
        
        
        
    

4、测试

    @Test
    public void demo1(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext3.xml");
        //CustomerDao customerDao = (CustomerDao) applicationContext.getBean("customerDao");
        CustomerDao customerDao = (CustomerDao) applicationContext.getBean("customerDaoProxy");

        customerDao.save();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }

前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中,非常多的Bean每个都配置ProxyFactoryBean开发维护量巨大

解决办法:自动创建代理

详见下篇

你可能感兴趣的:(spring)