Java Spring-AOP动态代理

2020-01-30  本文已影响0人  鄢栋云

代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

public interface ArithmeticCalculator {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
}

public class ArithmeticCalculatorImpl implements ArithmeticCalculator{
    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}

public class ArithmeticCalculatorLoggingProxy {
    //要代理的对象
    private ArithmeticCalculator target;
    public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target){
        this.target = target;
    }
    public ArithmeticCalculator getLoggingProxy(){
        ArithmeticCalculator proxy = null;
        //代理对象由哪一个类加载器负责加载
        ClassLoader loader = target.getClass().getClassLoader();
        //代理对象的类型,即其中有哪些方法
        Class[] interfaces = new Class[]{ArithmeticCalculator.class};
        //当调用代理对象其中的方法时,该执行的代码
        InvocationHandler h = new InvocationHandler(){
           /*
            proxy:正在返回的那个代理函数。一般情况下,在invoke方法中都不使用该对象。
            method:正在被调用的方法
            args:调用方法时,传入的参数
            */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                //日志
                System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
                //执行方法
                Object result = method.invoke(target,args);
                //日志
                System.out.println("The method " + methodName + " ends with " + result);
                return result;
            }
        };
        proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
        return proxy;
    }
}

public class Main {
    public static void main(String[] args) {
//        ArithmeticCalculator arithmeticCalculator = null;
//        arithmeticCalculator = new ArithmeticCalculatorImpl();

        ArithmeticCalculator target = new ArithmeticCalculatorImpl();
        ArithmeticCalculator proxy = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy();
        int result = proxy.add(1,2);
        System.out.println("-->"+result);
        proxy.div(4,2);
        System.out.println("-->"+result);
    }
}
//输出
The method add begins with [1, 2]
The method add ends with 3
-->3
The method div begins with [4, 2]
The method div ends with 2
-->3

AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统OOP(Object-Oriented-Programming,面向对象编程)的补充。AOP的主要编程对象是切面(aspect),而切面模块化横切关注点。在应用AOP编程时,任然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类,这样一来横切关注点就被模块化到特殊的对象(切面)里。AOP的好处:

  • 每个事物逻辑位于一个位置,代码不分散,便于维护和升级。
  • 业务模块更简洁,只包含核心业务代码。

SpringAOP

1.导入aspectjweaver.jar和aspectjrt.jar

2.在配置文件中加入aop的命名空间

3.基于注解的方式

1.在配置文件中加入以下配置

    <!-- 使AspjectJ 注解起作用:自动为匹配的类生成代理对象 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

2.把横切关注点的代码抽象到切面的类中

i.切面首先是一个IOC中的bean,即加入@Component注解

ii.切面还需要加入@Aspect注解

3.在类中声明各种通知:
i.声明一个方法
ii.在方法前加入@Before注释

4.可以在通知方法中声明一个类型为JointPoint的参数。然后就能访问链接细节。如方法名称和参数值。

//把这个类声明为一个切面:需要把该类放入到IOC容器中、再声明为一个切面
@Aspect
@Component
public class LoggingAspect {
    //声明该方法是一个前置通知:在目标方法开始之前执行
    @Before("execution(public int com.cloud.spring.aop.impl.ArithmeticCalculator.*(int,int))")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("The method " + methodName + " begins with" + args);
    }
}

例如:

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

    <!-- 配置自动扫描的包 -->
    <context:component-scan base-package="com.cloud.spring.aop.impl"></context:component-scan>

    <!-- 使AspjectJ 注解起作用:自动为匹配的类生成代理对象 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>


public interface ArithmeticCalculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}

@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}

//把这个类声明为一个切面:需要把该类放入到IOC容器中、再声明为一个切面
@Aspect
@Component
public class LoggingAspect {
    //声明该方法是一个前置通知:在目标方法开始之前执行
    @Before("execution(public int com.cloud.spring.aop.impl.ArithmeticCalculator.*(int,int))")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("The method " + methodName + " begins with" + args);
    }
}

public class Main {
    public static void main(String[] args) {
        //1.创建Spring的IOC容器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.从IOC容器中获取bean的实例
        ArithmeticCalculator arithmeticCalculator = context.getBean(ArithmeticCalculator.class);
        //3.使用bean
        int result = arithmeticCalculator.add(3,6);
        System.out.println("result:" + result);

        result = arithmeticCalculator.div(9,3);
        System.out.println("result:" + result);
    }
}

//输出
The method add begins with[3, 6]
result:9
The method div begins with[9, 3]
result:3

后置通知:在目标方法执行后(无论是否发生异常),执行的通知,在后置通知中还不能访问目标方法执行的结果。

//后置通知:在目标方法执行后(无论是否发生异常),执行的通知
//在后置通知中还不能访问目标方法执行的结果
@After("execution(* com.cloud.spring.aop.impl.*.*(int,int))")
public void afterMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("The method " + methodName + " ends");
}
    
//输出
The method add begins with[3, 6]
The method add ends
result:9
The method div begins with[9, 3]
The method div ends
result:3

后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候,下面的后置通知记录了方法的终止,一个切面可以包括一个或者多个通知。

返回通知:在方法正常结束受执行的代码,返回通知是可以访问到方法的返回值的。

    /*
    * 在方法正常结束之后执行的代码
    * 返回通知是可以访问到方法的返回值的!
    */
    @AfterReturning(value = "execution(* com.cloud.spring.aop.impl.*.*(..))",returning = "result")
    public void afterReturing(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends with " + result);
    }
//输出
The method add begins with[3, 6]
The method add ends
The method add ends with 9
result:9
The method div begins with[9, 3]
The method div ends
The method div ends with 3
result:3

异常通知:在方法抛出异常之后

     /*
     *  在目标方法出现异常时会执行的代码
     *  可以访问到异常对象,且可以指定在出现特定异常时再执行通知代码
     */
    @AfterThrowing(value = "execution(* com.cloud.spring.aop.impl.*.*(..))",throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint,Exception ex){
        String  methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " occurs exception with " + ex);
    }
//输出
The method add begins with[3, 6]
Exception in thread "main" java.lang.ArithmeticException: / by zero
The method add ends
The method add ends with 9
    at com.cloud.spring.aop.impl.ArithmeticCalculatorImpl.div(ArithmeticCalculatorImpl.java:32)
result:9
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
The method div begins with[9, 0]
The method div ends
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
The method div occurs exception with java.lang.ArithmeticException: / by zero
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)

环绕通知:围绕着方法执行

    public static void main(String[] args) {
        //1.创建Spring的IOC容器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.从IOC容器中获取bean的实例
        ArithmeticCalculator arithmeticCalculator = context.getBean(ArithmeticCalculator.class);
        //3.使用bean
        int result = arithmeticCalculator.add(3,6);
        System.out.println("result:" + result);

        result = arithmeticCalculator.div(9,3);
        System.out.println("result:" + result);
    }
/*
    * 环绕通知需要携带ProceedingJointPoint 类型的参数
    * 环绕通知类似于动态代理的全过程:ProceedingJointPoint 类型的参数可以决定是否执行目标方法。
    * 且环绕通知必须有返回值,返回值即为目标方法的返回值
    */
    @Around("execution(* com.cloud.spring.aop.impl.*.*(..))")
    public Object aroundMethod(ProceedingJoinPoint pjd){
        Object result = null;
        String methodName = pjd.getSignature().getName();
        try {
            //前置通知
            System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
            //执行目标方法
            result = pjd.proceed();
            //返回通知
            System.out.println("The method " + methodName + " ends with " + result);
        }catch (Throwable e){
            //异常通知
            System.out.println("The method " + methodName + " occurs exception: " + e);
            throw new RuntimeException(e);
        }
        System.out.println("The method " + methodName + " ends ");
        return result;
    }
//输出
The method add begins with [3, 6]
The method add ends with 9
The method add ends 
result:9
The method div begins with [9, 3]
The method div ends with 3
The method div ends 
result:3


    public static void main(String[] args) {
        //1.创建Spring的IOC容器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.从IOC容器中获取bean的实例
        ArithmeticCalculator arithmeticCalculator = context.getBean(ArithmeticCalculator.class);
        //3.使用bean
        int result = arithmeticCalculator.add(3,6);
        System.out.println("result:" + result);

        result = arithmeticCalculator.div(9,0);
        System.out.println("result:" + result);
    }
    
/*
    * 环绕通知需要携带ProceedingJointPoint 类型的参数
    * 环绕通知类似于动态代理的全过程:ProceedingJointPoint 类型的参数可以决定是否执行目标方法。
    * 且环绕通知必须有返回值,返回值即为目标方法的返回值
    */
    @Around("execution(* com.cloud.spring.aop.impl.*.*(..))")
    public Object aroundMethod(ProceedingJoinPoint pjd){
        Object result = null;
        String methodName = pjd.getSignature().getName();
        try {
            //前置通知
            System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
            //执行目标方法
            result = pjd.proceed();
            //返回通知
            System.out.println("The method " + methodName + " ends with " + result);
        }catch (Throwable e){
            //异常通知
            System.out.println("The method " + methodName + " occurs exception: " + e);
            throw new RuntimeException(e);
        }
        System.out.println("The method " + methodName + " ends ");
        return result;
    }
//输出
The method add begins with [3, 6]
The method add ends with 9
The method add ends 
result:9
The method div begins with [9, 0]
The method div occurs exception: java.lang.ArithmeticException: / by zero
上一篇下一篇

猜你喜欢

热点阅读