【Spring】AOP及动态代理

AOP之前学习过但是一直没有记下了,今天趁机会赶紧写一篇:
AOP:

AOP利用”横切”技术,剖解开封装的对象内部,将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
———————————————————————————————————————
使用”横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

概念性知识

接下来写一个切面类:

 /**
     * 定义名称为addMethod的pointcut,只是一个标识,不进行调用
     * (* add*(..)) 匹配没有返回值,以add开头的方法 、且参数任意
     */
    @Pointcut("execution(* add*(..))")
    private void addMethod(){};

    /**
     * 利用上面的标识:addMethod
     * 表示在调用(* add*(..))方法Before要执行这个方法
     * 在advice中添加JoinPoint参数可以获取客户端的方法名及参数
     */
    @Before("addMethod()")
    private void checkSecurity(JoinPoint joinPoint){
        for (int i = 0; i < joinPoint.getArgs().length; i++) {
            System.out.println(joinPoint.getArgs()[i]);//可以获取参数
        }
        joinPoint.getSignature().getName();//方法名
        System.out.println("你好—————————————");
    }

切面类中的切入点可以灵活配置:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?) 
除了返回类型模式、方法名模式和参数模式外,其它项都是可选的;
  ·返回类型模式决定了方法的返回类型,必须一次匹配一个连接点
  .名字模式匹配的是方法名,可用通配符*
  .参数模式()不接受参数、(..)接受任意数量的参数方法、(*)接受一个任何类型的参 数的方法

//execution 返回值类型  包名/方法名
 @Pointcut("execution(* add*(..)) || execution(* com.mjx.net.*.del*(..))")
 private void showPointcutTags(){}

白话Spring(基础篇)—AOP(execution表达式)
Spring AOP中pointcut expression表达式解析 及匹配多个条件

通过applicationContext.xml文件将类加载到spring容器中

    <!--启用aspect对annotation的支持-->
    <aop:aspectj-authoproxy/>
    <!--业务处理-->
    <bean id="userManagerImpl" class="com.mjx.study.UserManagerImpl"/>
    <!--切面,含切点和方法-->
    <bean id="annotationAOP" class="com.mjx.study.AnnotationAOP"/>

业务处理类:add*

public class UserManagerImpl implements UserManager {
    public void addUser(String usrname, String password) {
        System.out.println("add--------add------");
    }
}

测试类:

 @Test
    public void test() {
        ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext("applicationContext.xml");//读取配置文件
        UserManager userManager = (UserManager) factory.getBean("userManager");//从容器中获取userManager对应的类
        userManager.addUser("张三","124");//执行方法,切面切入
    }

补充:

  <!--方式二,将类中的切面、方法写到配置文件中-->
    <aop:config>
        <aop:aspect id="securityAspect" ref="annotationAOP">
            <aop:pointcut id = "addMethod" expression="execution(* add*(..))"/>
            <aop:before method="checkSecutiry" pointbut-ref="addMethod"/>
        </aop:aspect>
    </aop:config>

AOP和动态代理的结合:

JDK动态代理:
      在运行时使用字节码动态生成的代理类(代理类的字节码在运行时生成并载入当前代理的ClassLoader)
      通过反射类Proxy以及InvocationHandler回调接口实现的 == 只能对该类所实现接口中定义的方法进行代理

1、如果目标对象实现了接口,默认情况下才有JDK的动态代理AOP,也可以强制使用CGLIB生成的代理实现APP

  • 强制使用CGLIB
引入jar包、配置文件中:
<aop:aspectj-autoproxy proxy-target-class="true"/>

示例:

public class TestCglib {
    public static void main(String[] args) {
        //字节码增强器,http://blog.sina.com.cn/s/blog_790c59140102w7y3.html
        // 在Java字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修改
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TargetObject.class);//被代理类TargetObject设置成父类

        //方法拦截器
        CallbackFilter callbackFilter = new TargetMethodCallbackFilter();
        Callback noopCb = NoOp.INSTANCE;//即什么操作也不做,代理类直接调用被代理的方法不进行拦截。
        Callback callback = (Callback) new TargetInterceptor();//调用目标拦截器
        Callback fixedValue = (Callback) new TargetResultFixed ();//锁定方法返回值:无论被代理类的方法返回什么值,回调方法都返回固定值
        Callback[] callbacks = new Callback[]{callback,noopCb,fixedValue};
        enhancer.setCallback(new TargetInterceptor());//设置回调
        enhancer.setCallbacks((net.sf.cglib.proxy.Callback[]) callbacks);
        enhancer.setCallbackFilter(callbackFilter);
        TargetObject targetObject2 = (TargetObject) enhancer.create();//生成代理对象

        enhancer.setCallback(new TargetInterceptor());//设置拦截器TargetInterceptor
        TargetObject targetObject = (TargetObject) enhancer.create();//动态生成一个代理类,转型成父类型TargetObject
        System.out.println(targetObject);//在代理类上调用方法时会被拦截器拦截
        System.out.println(targetObject.method1("mmm"));
        System.out.println(targetObject.method2(0001));
        System.out.println(targetObject.method2(0002));

    }
}

目标拦截器:

public class TargetInterceptor implements MethodInterceptor {

    /**
     * 重写方法拦截
     *
     * @param o           目标对象,动态生成的代理类实例
     * @param method      目标方法,上文中实体类所调用的被代理的方法引用
     * @param objects     参数
     * @param methodProxy 代理对象,生成的代理类对方法的代理引用
     * @return
     * @throws Throwable
     */
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("调用前");
        Object result = methodProxy.invokeSuper(o, objects);//目标对象和参数
        System.out.println("调用后" + result);
        return result;
    }
}

锁定方法返回值:

/**
 * 表示锁定方法返回值,无论被代理类的方法返回什么值,回调方法都返回固定值
 * Created by phoebeM on 2018/04/15.
 */
public class TargetResultFixed implements FixedValue {
    public Object loadObject() throws Exception {
        System.out.println("锁定结果");
        Object object = 999;
        return object;
    }
}

申请的callback长什么样呐?
callback

一个接口?一个接口!
关键都在绿色箭头上,很多实现这个接口的方法

2、如果目标对象没有实现接口,必须引入CGLIB,spring会在JDK的动态代理和CGLIB代理间切换

JDK和CGLIB的区别:
1、JDK动态代理 处理 对实现了接口的类
2、CGLIB代理可以对类代理,为指定的类生成子类(继承,目标最好不要用final声明)

说了这么些,什么是CGLIB呐?
        一个代码生成包,如上所说其会动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法,然后在子类中采用方法拦截技术拦截所有父类方法的调用,顺势织入横切逻辑。比使用java反射的JDK动态代理要快(底层采用ASM字节码生成框架)。

    ASM是java字节码操纵框架,从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类(动态生成类或者增强既有类的功能)。



后语:
很简单的一个例子,出去前置还有后置与环绕,想起了装饰者模式,spring真是很强大,查资料看到一句话、分享给大家[一知半解,就是给自己挖坑]



感谢分享:
独具匠心:Spring AOP详解

猜你喜欢

转载自blog.csdn.net/ma15732625261/article/details/79947020