代理以及aop总结

静态代理

静态代理中有接口、目标实现类、代理类,其中实现类和代理类都需要实现相同接口,因为在静态代理中,目标方法可能有很多方法,而代理类中可能需要代理很多的方法,所以需要使用一个接口对它们间进行约定。

实现:

第一步,创建接口:

public interface TargetInterface {
    void method1();
    void method2();
    int method3(Integer i);
}

第二步,创建被代理的类:

public class Target implements TargetInterface {
    @Override
    public void method1() {
        System.out.println("method1 running ...");
    }

    @Override
    public void method2() {
        System.out.println("method2 running ...");
    }

    @Override
    public int method3(Integer i) {
        System.out.println("method3 running ...");
        return i;
    }
}

第三步,创建代理类:

public class TargetProxy implements TargetInterface {

    @Override
    public void method1() {
        System.out.println("执行方法前...");
        new Target().method1();
        System.out.println("执行方法后...");
    }

    @Override
    public void method2() {
        System.out.println("执行方法前...");
        new Target().method2();
        System.out.println("执行方法后...");
    }

    @Override
    public int method3(Integer i) {
        System.out.println("执行方法前...");
        int method3 = new Target().method3(i);
        System.out.println("执行方法后...");
        return method3;
    }
}

第四步,测试:

public class TargetUser {
    public static void main(String[] args) {
        TargetInteface target = new TargetProxy();
        target.method1();
        System.out.println("-----------------------------");
        target.method2();
        System.out.println("-----------------------------");
        System.out.println(target.method3(3));
    }
}

这里可以看到,代理类对被代理类中每一个方法都进行了增强。

缺点:在运行前就已经确定了代理了哪些方法,无法动态地进行代理,这就需要频繁地对目标类创建代理对象,方法和类都会很多,目标类和代理类都需要维护,非常麻烦。

动态代理

JDK动态代理

在静态代理中,每新增一个方法,想要代理此方法时都需要重新维护一下代理类。而动态代理中是在运行时动态创建代理对象,无需编写代理类。

JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能产生代理对象

实现:

image-20210524164946901

第一步,创建接口:
public interface TargetInteface {
    void method1();
    void method2();
    int method3(Integer i);
}
第二步,创建被代理的类:
public class Target implements TargetInteface {
    @Override
    public void method1() {
        System.out.println("method1 running ...");
    }

    @Override
    public void method2() {
        System.out.println("method2 running ...");
    }

    @Override
    public int method3(Integer i) {
        System.out.println("method3 running ...");
        return i;
    }
}
第三步,创建代理类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class TargetProxy {
    public static  <T> Object getTarget(T t) {
        // JDK生成的最终真正的代理类,它继承自Proxy并实现了我们定义的TargetInteface接口
        return Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // proxy就是目标对象t,method就是调用目标对象中方法,args就是调用目标对象中方法的参数。
                //比如说:代理对象.method1(),这时proxy就是目标类,method1就是method,args就是method1方法参数。
                System.out.println("执行方法前...");
                Object invoke = method.invoke(t, args);
                System.out.println("执行方法后...");
                return invoke;
            }
        });
    }
}
第四步,测试:
public class TargetUser {

    public static void main(String[] args) {
        TargetInteface target = (TargetInteface) TargetProxy.getTarget(new Target());
        target.method1();
        System.out.println("-----------------------------");
        target.method2();
        System.out.println("-----------------------------");
        System.out.println(target.method3(3));
    }

}

JDK生成的最终真正的代理类,它继承自Proxy并实现了我们定义的TargetInteface接口。

有关jdk动态代理实现和原理,参考:https://jiankunking.blog.csdn.net/article/details/52143504

CGLIB动态代理

cglib代理相对于jdk代理,无需创建统一的接口,只需要一个非抽象类即可实现动态代理。

第一步,创建被代理的类:
public class Target {
    public void method1() {
        System.out.println("method1 running ...");
    }

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

    public int method3(Integer i) {
        System.out.println("method3 running ...");
        return i;
    }
}
第二步,创建代理类:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class TargetProxy {

    static <T> Object getProxy(T t) {
        Enhancer en = new Enhancer(); //帮我们生成代理对象
        en.setSuperclass(t.getClass());//设置要代理的目标类
        en.setCallback(new MethodInterceptor() {//代理要做什么
            @Override
            public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("执行方法前。。。");
                //调用原有方法  
                Object invoke = methodProxy.invokeSuper(object, args);
                // Object invoke = method.invoke(t, args);// 作用等同与上面。
                System.out.println("执行方法后。。。");
                return invoke;
            }
        });
        return en.create();
    }
}
第三步,测试:
public class TargetUser {

    public static void main(String[] args) {
        Target target = (Target) TargetProxy.getProxy(new Target());
        target.method1();
    }

}

总结:

AOP

Spring中的AOP是基于动态代理实现的,即 JDK动态代理和Cglib动态代理。

AOP体系(图来自【SpringBoot-3】切面AOP实现权限校验:实例演示与注解全解_云深不知处-CSDN博客):

20201016194259600

AOP各个概念所处的场景:

20201016201545227

一、Spring经典AOP实现

第一步,目标接口:

public interface TargetInteface {
    void method1();
    void method2();
    int method3(Integer i);
}

第二步,目标类:

/**
 * 目标类
 */
public class Target implements TargetInteface{

    /*
    * 需要增强的方法,连接点JoinPoint
    **/
    public void method1() {
        System.out.println("method1 running ...");
    }

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

    public int method3(Integer i) {
        System.out.println("method3 running ...");
        return i;
    }
}

第三步,通知:

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class TargetAdvice implements MethodInterceptor, MethodBeforeAdvice, AfterReturningAdvice {

    /*
    * 通知/增强
    **/
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("前置环绕通知");
        Object proceed = methodInvocation.proceed();
        System.out.println("后置环绕通知");
        return proceed;
    }

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("后置返回通知");
    }

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("前置通知");
    }
}

第四步,xml中注册到spring:

三种方式:

one:

<?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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="target" class="com.laixm.aopdemo.aop.Target"/>

    <bean id="targetAdvice" class="com.laixm.aopdemo.aop.TargetAdvice"/>

    <bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="target"/> <!--被代理的类-->
        <property name="interceptorNames" value="targetAdvice"/>  <!--如果用多种增强方式,value的值使用逗号(,)分割-->
        <property name="proxyTargetClass" value="true"/>
        <property name="interfaces" value="com.laixm.aopdemo.aop.TargetInteface"/>  <!--target实现的接口-->
    </bean>
</beans>

two:

<?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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="target" class="com.laixm.aopdemo.aop.Target"/>

    <bean id="targetAdvice" class="com.laixm.aopdemo.aop.TargetAdvice"/>

    <bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="target"/>
        <property name="interceptorNames" value="targetAdvice"/>
        <property name="proxyTargetClass" value="true"/>
    </bean>
</beans>

three:

<?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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--目标类-->
    <bean id="target" class="com.laixm.aopdemo.aop.Target"/>

    <!--通知和切点-->
    <bean id="targetAdvice" class="com.laixm.aopdemo.aop.TargetAdvice"/>
    <bean id="targetPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*method1"/>
    </bean>

    <!--制作切面-->
    <bean id="pointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="targetAdvice"/>
        <property name="pointcut" ref="targetPointcut"/>
    </bean>

    <bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="target"/>
        <property name="interceptorNames" value="pointcutAdvisor"/>
        <property name="proxyTargetClass" value="true"/>
    </bean>
</beans>

image-20210526145630141

proxyTargetClass:是否直接使用目标类代替接口进行代理,默认是false,即默认是jdk代理。如果设置成true,如果目标类实现了接口,就会创建一个实现了接口的JDK代理对象。如果目标类没有实现接口,那么就会创建一个CGLIB代理对象。

总结:设置为true的话,需要使用JDK代理的时候,目标类实现接口即可,使用的时候记得转换为接口,因为我们这里获取的是实现了接口的代理对象:

image-20210526151048585

需要使用CGLIB代理的时候,目标类不要实现接口,转换需要用类转:

image-20210526151300307

第五步,测试方法:

public class AopTest {
    public static void main(String[] args) {
        ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-aop.xml");
        TargetInteface targetProxy = (TargetInteface) appCtx.getBean("targetProxy"); // jdk动态代理需要转换为接口
        targetProxy.method1();
    }
}

控制台:

image-20210524160250529

二、Spring纯POJO切面(纯粹通过<aop:fonfig>标签配置)

常用,不可与上面的实现方式同时使用,会产生两个不同的代理对象,对增强执行两次

一、目标类

/*
* 目标类
**/
public class Target {

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

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

    /*
    * 连接点JoinPoint
    **/
    public int method3(Integer i) {
        System.out.println("method3 running ...");
        int i1 = 1 / i;
        return i;
    }
}

目标类中包含着连接点JoinPoint

二、切面类

/*
* 切面类
**/
public class TargetAspect {

    /*
    * 前置通知/增强
    **/
    public void before() {
        System.out.println("前置通知");
    }

    public void after() {
        System.out.println("后置通知");
    }

    public void afterReturning() {
        System.out.println("后置返回通知");
    }

    public void afterThrowing(Exception ex) throws Exception {
        System.out.println("异常通知");
        System.out.println(ex.getMessage());
    }

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object proceed = null;
        if (!"".equals("admin")) {
            System.out.println("环绕前置");
            proceed = pjp.proceed(pjp.getArgs());
            System.out.println("环绕后置");
        }
        return proceed;
    }
}

切面由(Aspect)切入点(Piontcut)和通知(Advice)组成,切入点在xml中配置,也可以使用注解配置

三、配置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" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="target" class="com.laixm.aopdemo.confaop.Target"/>

    <bean id="targetAspect" class="com.laixm.aopdemo.confaop.TargetAspect"/>

    <!--proxy-target-class="true" 表示使用cglib代理.默认为false,创建有接口的jdk代理-->
    <aop:config proxy-target-class="true">
        <!--切面:由切入点和通知组成-->
        <aop:aspect ref="targetAspect">
            <!--切入点 pointcut-->
            <aop:pointcut id="pointcut" expression="execution(* com.laixm.aopdemo.confaop.*.*(..))"/>
            <!--增强/通知 advice-->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
            <aop:around method="around" pointcut-ref="pointcut"/>
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
            <aop:after-throwing method="afterThrowing" throwing="ex" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

这种方式实现的aop可读性较高

四、测试

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

public class AopTest {
    public static void main(String[] args) {
        ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-confaop.xml");
        Target targetProxy = (Target) appCtx.getBean("target");
        System.out.println(targetProxy.method3(0));
    }
}

使用切入点表达式匹配切点:todo

三、Spring注解实现AOP

自定义注解

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation{
}

切面类

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/*
* 切面类
**/
@Aspect
@Component
public class AnnotationAspect {

    // 定义一个切点:所有被TestAnnotation注解修饰的方法会织入advice
    @Pointcut("@annotation(TestAnnotation)")
    private void advicePointcut() {}

    /*
    * 前置通知
    **/
    @Before("execution(* com.laixm.controller.*.*(..))")
    public void before() {
        System.out.println("annotation前置通知");
    }

    @After("advicePointcut()")
    public void after() {
        System.out.println("annotation后置通知");
    }

    @AfterReturning(pointcut = "advicePointcut()")
    public void afterReturning() {
        System.out.println("annotation后置返回通知");
    }

    @AfterThrowing(pointcut = "advicePointcut()", throwing = "ex")
    public void afterThrowing(Exception ex) throws Exception {
        System.out.println("annotation异常通知");
        System.out.println(ex.getMessage());
    }

    @Around("advicePointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object proceed = null;
        if (!"".equals("admin")) {
            System.out.println("annotation环绕前置");
            proceed = pjp.proceed(pjp.getArgs());
            System.out.println("annotation环绕后置");
        }
        return proceed;
    }
}

配置

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

    <!--先开启cglib代理,开启 exposeProxy = true,暴露代理对象-->
    <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
    <!--扫包-->
    <context:component-scan base-package="com.laixm.aopdemo.annotationaop"/>

</beans>

测试

@Controller
public class TestController {

    @RequestMapping("/test.do")
    @ResponseBody
    public String testController() {
        TestController o = (TestController) AopContext.currentProxy();
        o.test();
        return "ok";
    }

    @TestAnnotation
    public void test() {
        System.out.println("test running");
    }

}

image-20210526165310830

**注意:**配置中需要开启:<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>,获取对象的时候需要使用AopContext.currentProxy(); 否则可能导致aop失效。所以建议使用上面第二种方式实现Spring中的aop

之前的代码可查看:https://gitee.com/ilusymon/spring-aop

四、SpringBoot实现注解AOP

https://gitee.com/ilusymon/spring-boot-aop

Q.E.D.


一蓑烟雨任平生