对于Spring AOP的一点理解

  • AOP的概述

AOP Aspect Oriented Programming 面向切面编程

AOP采取了横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)

Spring AOP采用纯JAVA实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码。

  • AOP相关术语

Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点

Pointcut(切入点):所谓切入点是指我们要对哪些JoinPoint进行拦截的定义。

Advice(通知/增强):所谓通知是指拦截到JoinPoint之后所要做的事情就是通知。

通知分为前置通知、后置通知、异常通知、最终通知、环绕通知(切面要完成的功能)

Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下,

Introduction可以在运行期为类动态地添加一些方法或Field。

Target(目标对象):代理的目标对象

Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。

Spring采用动态代理织入,而AspectJ采用编译期间织入和类装载期织入

Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类

Aspect(切面):是切入点和通知(引介)的结合

Spring有两种代理机制分别为JDK动态代理和CGLIB生成代理,下面我分别用代码实现下

JDK动态代理实现代码:

首先创建一个UserDao接口,代码如下所示:



public interface UserDao {
    public void save();

    public void update();

    public void delete();

    public void find();
}

然后创建接口实现类UserDaoImpl,代码如下所示:
 



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

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

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

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

创建JDK的动态代理类MyJDKProxy类,代码如下所示:



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 {
        if("save".equals(method.getName())){
            System.out.println("权限校验...");
            return method.invoke(userDao,args);
        }
        return method.invoke(userDao,args);
    }
}

然后创建一个测试单元进行测试,代码如下:


import org.junit.Test;

public class SpringDemo1 {
    @Test
    public void demo1(){
        UserDao userDao = new UserDaoImpl();

        UserDao proxy = (UserDao)new MyJdkProxy(userDao).createProxy();
        proxy.save();
        proxy.update();
        proxy.delete();
        proxy.find();
    }
}

使用cglib生成代理

  • 对于不使用接口的业务类,无法使用JDK动态代理
  • CGlib采用非常底层字节码技术,可以为一个类创建子类,解决无接口代理问题

cglib的动态代理实现:

首先创建一个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("查询商品...");
    }
}

然后创建cglib实现类,代码实现如下:


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{

    private 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;
    }

    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);
    }
}

最后,创建测试单元进行测试,代码如下:

import org.junit.Test;

public class SpringDemo2 {

    @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的方法,不能被代理,因为无法覆盖

JDK动态代理,是针对接口生成子类,接口中方法不能使用final修饰

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.ThrowsAdvice 在方法抛出异常后实施增强
  • 引介通知 org.springframework.aop.IntroductionInterceptor 在目标类中添加一些新的方法和属性

下面我用具体的demo实现演示下AOP增强类型的概念:

创建一个StudentDao接口,代码如下:


public interface StudentDao {
    public void find();

    public void save();

    public void update();

    public void delete();
}

接着,创建StudentDao接口的实现类StudentDaoImpl,代码如下:



public interface StudentDao {
    public void find();

    public void save();

    public void update();

    public void delete();
}

接着,我们创建一个xml配置文件,代码如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--配置目标类=======================-->
    <bean id="studentDao" class="com.imooc.aop.demo3.StudentDaoImpl"/>

    <!--前置通知类型=====================-->
    <bean id="myBeforeAdvice" class="com.imooc.aop.demo3.MyBeforeAdvice"/>

    <!--Spring的AOP 产生代理对象-->
    <bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--配置目标类-->
        <property name="target" ref="studentDao"/>
        <!--实现的接口-->
        <property name="proxyInterfaces" value="com.imooc.aop.demo3.StudentDao"/>
        <!--采用拦截的名称-->
        <property name="interceptorNames" value="myBeforeAdvice"/>
        <property name="optimize" value="true"></property>
    </bean>
</beans>

然后,我们创建一个通知类型类,代码如下所示:

package com.imooc.aop.demo3;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class MyBeforeAdvice implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("前置增强======================");
    }
}

最后,我们创建一个测试单元进行测试,代码如下所示:

package com.imooc.aop.demo3;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo3 {

    // @Resource(name="studentDao")
    @Resource(name="studentDaoProxy")
    private StudentDao studentDao;

    @Test
    public void demo1(){
        studentDao.find();
        studentDao.save();
        studentDao.update();
        studentDao.delete();
    }
}

Spring AOP切面类型

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

代码实现

首先,创建一个customerDao类,代码实现如下:

package com.imooc.aop.demo4;

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("删除客户...");
    }
}

接着,创建一个xml配置文件把custormerDao放入到Spring容器中

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--配置目标类============-->
    <bean id="customerDao" class="com.imooc.aop.demo4.CustomerDao"/>

    <!--配置通知============== -->
    <bean id="myAroundAdvice" class="com.imooc.aop.demo4.MyAroundAdvice"/>

    <!--一般的切面是使用通知作为切面的,因为要对目标类的某个方法进行增强就需要配置一个带有切入点的切面-->
    <bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!--pattern中配置正则表达式:.任意字符  *任意次数 -->
    <!--<property name="pattern" value=".*save.*"/>-->
        <property name="patterns" value=".*save.*,.*delete.*"/>
    <property name="advice" ref="myAroundAdvice"/>
</bean>

<!-- 配置产生代理 -->
    <bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="customerDao"/>
        <property name="proxyTargetClass" value="true"/>
        <property name="interceptorNames" value="myAdvisor"/>
    </bean>
</beans>

创建一个增强类型类,代码如下:
 

package com.imooc.aop.demo4;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAroundAdvice implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("环绕前增强===================");

        Object obj = invocation.proceed();

        System.out.println("环绕后增强===================");
        return obj;
    }
}

最后,创建一个测试单元类,进行测试:

package com.imooc.aop.demo4;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringDemo4 {

    // @Resource(name="customerDao")
    @Resource(name="customerDaoProxy")
    private CustomerDao customerDao;

    @Test
    public void demo1(){
        customerDao.find();
        customerDao.save();
        customerDao.update();
        customerDao.delete();
    }
}

ProxyFactoryBean常用的可配置属性

——target:代理的目标对象

——proxyInterfaces:代理要实现的接口

如果多个接口可以使用一下格式赋值

<list>

<value></value>

...

</list>

——proxyTargetClass:是否对类代理而不是接口,设置为true时,使用CGLib代理

——interceptorNames:需要织入目标的Advice

——singleton:返回代理是否为单实例,默认为单例

——optimize:当设置为true时,强制使用CGLib

PointcutAdvisor切点切面

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

常用PointcutAdvisor实现类

——DefaultPointcutAdvisor最常用的切面类型,它可以通过任意Pointcut和Advice组合定义切面

猜你喜欢

转载自blog.csdn.net/qq_41338249/article/details/88795496
今日推荐