SSM框架学习-3

AOP Proxy 面向切面

Posted by lvyonghao on April 8, 2019

SSM框架学习-3

  • AOP的概述
  • AOP的底层实现
  • Spring的传统AOP 不带切入点的切面 带切入点的切面

  • Spring的传统AOP的自动代理 基于Bean名称的自动代理 基于切面信息的自动代理

1.什么是AOP

  • AOP为Aspect Oriented Progtamming的缩写,意为:面向切面编程,通过预编译方式,和运行期动态代理实现程序功能的统一维护的一种技术,是面向对象的增强。
  • AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视,事务管理,安全检查,缓存)。
  • Spring AOP使用纯Jaca实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类组织入增强代码。

2.AOP相关术语

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

3.JDK动态代理

下面看一个例子来了解什么是JDK动态代理,通常我们在开发时通常会先创建接口类似这样的:

package com.snake_lvyonghao.aop.demo1;

public interface UserDao {

    public void save();

    public void update();

    public void delete();

    public void find();
}

假设它有增删改查的功能,接下来就要用实例类来实现接口功能,这个实现需要反射包里的proxy代理,首先要先创建一个接口实例,构造器构造增强类,生成代理类(Proxy.newProxyInstance方法),在方法的incovationHandler参数内可以使用匿名内部类来实现增强,或者在外部继承InvocationHandler接口,在invoke方法中重写要增强的方法:

package com.snake_lvyonghao.aop.demo1;

public class UserDaoImpl implements UserDao{

    public void save() {
        System.out.println("save");
    }

    public void update() {
        System.out.println("update");
    }

    public void delete() {
        System.out.println("delete");
    }

    public void find() {
        System.out.println("find");
    }
}

下面我们需要写一个代理类来实现对类的增强:

package com.snake_lvyonghao.aop.demo1;

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(){
        return Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        if("save".equals(method.getName())){
            System.out.println("权限校验===========");
            return method.invoke(userDao,objects);
        }
        return method.invoke(userDao,objects);
    }
}

好了功能的实现我们用输出文本作为代替,接下来写一个测试文件,记得在配置文件中加上junit:

package com.snake_lvyonghao.aop.demo1;

import org.junit.Test;

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

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

这部分只需要了解如何使用代理实现对类的增强,在Spring中我们将会使用配置的方式来实现代理


4.CGLIB生成代理

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

同样这是代理的底层原理只需要了解就好了,下面放上实现的代码,和JDK类似,这次使用了Spring,所以要在配置文件加上Spring基础的4个文件。

业务类:

package com.snake_lvyonghao.demo2;

public class ProductDao {

    public void save() {

        System.out.println("save");
    }

    public void update() {
        System.out.println("update");
    }

    public void delete() {
        System.out.println("delete");
    }

    public void find() {

        System.out.println("find");
    }
}


MyCglib:

package com.snake_lvyonghao.demo2;

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 MyCglib implements MethodInterceptor {

    private ProductDao productDao;

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

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        if("save".equals(method.getName())){
            System.out.println("方法校验=================");
            return methodProxy.invokeSuper(o,objects);
        }
        return methodProxy.invokeSuper(o,objects);
    }

    public Object createProxy(){
        //1.创建核心类
        Enhancer enhancer = new Enhancer();

        //2。设置父类
        enhancer.setSuperclass(productDao.getClass());

        //3.设置代理
        enhancer.setCallback(this);

        //4.生成代理
        return enhancer.create();

    }
}


Text:

package com.snake_lvyonghao.demo2;

import org.junit.Test;

public class SpringDemo2 {
    @Test
    public void demo1(){
        ProductDao productDao = new ProductDao();
        ProductDao proxy = (ProductDao)new MyCglib(productDao).createProxy();

        proxy.delete();

        proxy.find();

        proxy.save();

        proxy.update();
    }
}

5.代理总结

  • Spring在运行期间,生成动态代理,不需要特殊的编译器。
  • Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术,为Bean执行横向织入。
  • 程序中应有限对接口创建代理,便于程序维护
  • 标记为final的方法,不能被代理,因为无法覆盖
  • Spring只支持方法连接点,不提供属性连接点。

6.Spring AOP增强类型(通知类型)

  • AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.Advice
  • Spring按通知Advice在目标类方法的连接点位置,可以分为五类:

    前置通知org.springframework.aop.MethodBeforeAdvice(在目标方法执行前实施增强,比如权限验证) 后置通知org.springframework.aop.AfterReturningAdvice(在目标方法执行后实施增强,比如日志记录) 环绕通知org.aopalliance.intercept.MethodInterceptor(在目标方法执行前后实施增强) 异常跑出通知org.springframework.aop.ThrowsAdvice(在方法抛出异常后实施增强) *引介通知org.springframework.aop.IntroductionInterceptor(咋目标类中添加一些新的方法和属性)


7.Spring AOP切面类型

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

8.Advisor切面案例

Advisor增强使用Spring自动代理的方式,不需要手动去写JDK或者CGLib代理。增强可以在创建之前,创建之后,这里我们选择前置通知作为演示:

  • ProFactoryBean常用可配置属性:

    target:代理目标对象 proxyInterfaces:代理要实现的接口


//多个接口可以使用以下格式赋值
<list>
    <value></value>
    ...
<list>


初始接口和目标类

package com.snake_lvyonghao.demo3;

public interface StudentDao {

    public void find();

    public void save();

    public void update();

    public void delete();
}
---------------------------------------
package com.snake_lvyonghao.demo3;

public class StudentDaoImp implements StudentDao {
    public void find() {
        System.out.println("学生查询。。");
    }

    public void save() {
        System.out.println("学生保存");
    }

    public void update() {
        System.out.println("学生修改");
    }

    public void delete() {
        System.out.println("学生删除");
    }
}

设置增强内容

package com.snake_lvyonghao.demo3;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

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

*配置文件,先设置接口以及增强类Bean,使用ProFactoryBean配置代理

        <bean id="studentDao" class="com.snake_lvyonghao.demo3.StudentDaoImp"/>
        <!--前置通知-->
        <bean class="com.snake_lvyonghao.demo3.MyBeforeAdvice" id="myBeforeAdvice"/>

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

测试类

package com.snake_lvyonghao.demo3;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDemo3 {

    @Test
    public void demo1(){

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        StudentDao studentDao = (StudentDao) applicationContext.getBean("studentDaoProxy");

        studentDao.find();
        studentDao.delete();
        studentDao.save();
        studentDao.update();
    }
}
  • proxyTargetClass:是否对类代理而不是接口,设置为ture,使用CGLib代理
  • interceptorNames:需要织入目标的Advice
  • singleton:返回代理是否为单实例,默认单例
  • optimize:当设置为true时,强制使用CGLib(怎么感觉没卵用)

9.PointcutAdvisor 切点切面

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

  • 常用PointcutAdvisor 实现类:

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

配置文件通过class=”org.springframework.aop.support.RegexpMethodPointcutAdvisor 构造正则表达式,具体看代码注释写的很清楚了:

    <!--目标类-->
    <bean class="com.snake_lvyonghao.demo4.CustomerDao" id="customerDao"/>
    <!--配置通知-->
    <bean id="MyAroundAdvice" class="com.snake_lvyonghao.demo4.MyAroundAdvice"/>

    <!--一般的切面使用通知作为切面,因为要对目标类的某些方法进行增强,配置带有切入点的切面-->
    <bean id="MyAdvice" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!--patten正则表达式匹配save-->
        <property name="pattern" value=".*save.*"/>
        <property name="advice" ref="MyAroundAdvice"/>
    </bean>

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

环绕增强内容:

package com.snake_lvyonghao.demo4;

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

public class MyAroundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {

        System.out.println("环绕方法-前");

        //执行目标方法(可以不执行)

        Object obj = methodInvocation.proceed();
        System.out.println("环绕方法-后");
        return obj;
    }
}


目标类(没什么区别)

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


测试类

package com.snake_lvyonghao.demo4;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDemo4 {

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

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

10.创建自动代理

简化配置,不需要每次增强都配置代理

  • 前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际 开发中,非常多的Bean每个都配置ProxyFactoryBean开发维护量巨大
  • 解决方案:自动创建代理

    BeanNameAutoProxyCreator根据Bean名称创建代理 DefaultAdvisorAutoProxyCreator根据Advisor本身包含信息创建代理 AnnotationAwareAspectJAutoProxyCreator 基于Bean中的AspectJ 注解进行自动代理


BeanNameAutoProxyCreator举例

  • 对所有以DAO结尾Bean所有方法使用代理

代码太多了,这里只放上配置文件的内容(其他的已经写过很多次了),运行后发现,他确实可以自动的生成代理,具体代码还是参考Spring_AOP的demo5吧

<?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 class="com.snake_lvyonghao.demo5.StudentDaoImp" id="studentDao"/>
    <bean class="com.snake_lvyonghao.demo5.CustomerDao" id="customerDao"/>

    <!--配置增强-->
    <bean id="myBeforeAdvice" class="com.snake_lvyonghao.demo5.MyBeforeAdvice"></bean>
    <bean id="myAroundAdvice" class="com.snake_lvyonghao.demo5.MyAroundAdvice"/>

    <!--配置代理-->
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames" value="*Dao"/>
        <property name="interceptorNames" value="myAroundAdvice"/>
    </bean>
</beans>

DefaultAdvisorAutoProxyCreator举例

之前可以自动的设置代理类,但是无法实现切面代理,这里我们使用 DefaultAdvisorAutoProxyCreator来实现切面代理

同样就不放上完整代码了,详情请看Spring——AOP的Demo6把,这里只放上一段配置文件的内容: ```

<!--配置增强-->
<bean id="myBeforeAdvice" class="com.snake_lvyonghao.demo6.MyBeforeAdvice"></bean>
<bean id="myAroundAdvice" class="com.snake_lvyonghao.demo6.MyAroundAdvice"/>

<!--配置切面-->
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice" ref="myBeforeAdvice"/>
    <property name="pattern" value=".*save.*"/>
</bean>

<!--基于切面信息自动配置代理-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> ```

SSM框架源码地址