JAVA动态代理 和 Spring AOP 4种通知的简单实现

柔情痞子 提交于 2019-11-29 03:56:27

学习Spring AOP 之前,先要了解下JAVA的动态代理。如果不清楚动态代理的概念就百度一下吧。废话不多说,直接上代码。

我们模拟一个简单的登录

首先我们创建一个用户登录的接口

package com.proxy.test;

public interface UserLogin {
    public void login(String userName);
}

接着创建一个接口的实现类

package com.proxy.test;

public class UserLoginImpl implements UserLogin {
    public void login(String userName) {
        System.out.println("欢迎 " + userName + " 登录系统");
    }
}

在创建一个自己的处理类 实现InvocationHandler 接口

package com.proxy.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyHandler implements InvocationHandler {
    private Object obj;

    public MyHandler(Object obj) {
        this.obj = obj;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeLogin(); // 登录前处理,更具自己需要来写

        Object result = method.invoke(obj, args); // 调用真正的方法

        afterLogin(); // 登录后处理,更具自己需要来写

        return result;
    }

    public void beforeLogin() {
        System.out.println("登录前处理");
    }

    public void afterLogin() {
        System.out.println("登录后处理");
    }
}

最后写一个测试类

package com.proxy.test;

import java.lang.reflect.Proxy;

public class ProxyTest {
    public static void main(String[] args) {
        UserLoginImpl user = new UserLoginImpl(); // 得到实例对象
        MyHandler handler = new MyHandler(user); // 将对象传入自己的处理器中

        UserLogin proxy = (UserLogin) Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass()
                .getInterfaces(), handler); // 得到代理对象

        proxy.login("张三"); // 代理调用login方法
    }
}

运行结果 输出 :

登录前处理
欢迎 张三 登录系统
登录后处理
说明我们的动态代理成功了。以上就是一个动态代理的小例子。


下面说说spring AOP 中间的4种通知(前置,后置,环绕,异常),别的理论和一些东西说一下也说不明白,自己也还需要继续学习和理解所以就不在这里献丑了。

前置通知

首先我们还是先建立一个用户登录的接口

package com.aop.test;

public interface UserLogin {
    public void login(String userName);
}

然后用一个类去实现这个接口(前置,后置,环绕这3个同时都用同一个类,异常的稍微修改下,让它能抛出异常)

package com.aop.test;

public class UserLoginImpl implements UserLogin {

    public void login(String userName) {
        System.out.println(userName + " 正在登录系统");
    }
}

创建实现前置通知接口 MethodBeforeAdvice 的类

package com.aop.test;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class CheckUser implements MethodBeforeAdvice {

    public void before(Method method, Object[] objs, Object obj) throws Throwable {
        String userName = (String) objs[0]; // 获取登录名
        System.out.println("用户 " + userName + " 登录前处理");
    }

}

测试类JAVA代码版

package com.aop.test;

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;

public class BeforeAdviceTest {
    public static void main(String[] args) {
        UserLogin target = new UserLoginImpl(); // 具体的登录用户
        BeforeAdvice advice = new CheckUser(); // 前置通知
        ProxyFactory proxyFactory = new ProxyFactory(); // Spring代理工厂
        proxyFactory.setTarget(target); // 设置代理目标
        proxyFactory.addAdvice(advice); // 为代理目标添加前置通知
        UserLogin proxy = (UserLogin) proxyFactory.getProxy(); // 生成代理实例
        proxy.login("张三"); // 调用登录方法
    }
}

但是一般都是用xml文件来配置的所以在来个xml配置版

建立一个bean1.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org.dtd/spring-beans.dtd">
<beans>
    <bean id="checkuser" class="com.aop.test.CheckUser"/>
    <bean id="target" class="com.aop.test.UserLoginImpl"/>
    <!-- 使用Spring代理工厂配置一个代理 -->
    <bean id="userlogin" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 指定代理接口,如果是多个接口,可以使用List元素指定 -->
        <property name="proxyInterfaces" value="com.aop.test.UserLogin"/>
        <!-- 指定通知 -->
        <property name="interceptorNames" value="checkuser"/>
        <!-- 指定目标对象 -->    <!-- 这个地方的name 我开始按照书上写的target报错,我进ProxyFactoryBean类看 没有target属性和set它的方法,有targetName属性修改了就对了-->
        <property name="targetName" value="target"/>
    </bean>
</beans>

测试类变为

package com.aop.test;

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

public class BeforeAdviceTest1 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("/com/aop/test/bean1.xml");
        UserLogin ul = (UserLogin) ac.getBean("userlogin");
        ul.login("张三");
    }
}

运行结果在控制台输出:

用户 张三 登录前处理
张三 正在登录系统


后置通知

创建实现后置通知接口的实现类

package com.aop.test;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

public class AfterLoginAdvice implements AfterReturningAdvice {

    public void afterReturning(Object paramObject1, Method paramMethod, Object[] paramArrayOfObject, Object paramObject2)
            throws Throwable {
        String userName = (String) paramArrayOfObject[0]; // 获取登录名
        System.out.println(userName + " 登录成功");
    }

}

后置通知 只需要修改以下配置的xml文件

为了方便我就新建立一个bean2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org.dtd/spring-beans.dtd">
<beans>
    <bean id="target" class="com.aop.test.UserLoginImpl"/>
    <bean id="afterlogin" class="com.aop.test.AfterLoginAdvice"/>
    <!-- 使用Spring代理工厂配置一个代理 -->
    <bean id="userlogin" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 指定代理接口,如果是多个接口,可以使用List元素指定 -->
        <property name="proxyInterfaces" value="com.aop.test.UserLogin"/>
        <!-- 指定通知 -->
        <property name="interceptorNames" value="afterlogin"/>
        <!-- 指定目标对象 -->    <!-- 这个地方的name 我开始按照书上写的target报错,我进ProxyFactoryBean类看 没有target属性和set它的方法,有targetName属性修改了就对了-->
        <property name="targetName" value="target"/>
    </bean>
</beans>

测试类

package com.aop.test;

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

public class BeforeAdviceTest2 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("/com/aop/test/bean2.xml");
        UserLogin ul = (UserLogin) ac.getBean("userlogin");
        ul.login("张三");
    }
}

控制台打印出:

张三 正在登录系统
张三 登录成功



环绕通知

创建实现环绕通知的类

package com.aop.test;

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

public class MyAroundAdvice implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object[] objs = invocation.getArguments();
        String userName = (String) objs[0];

        // 在目标方法执行前调用
        System.out.println("正在对" + userName + "进行登录验证");
        // 通过反射调用执行方法
        Object obj = invocation.proceed();
        // 在目标方法执行之后调用
        System.out.println(userName + "登录成功");

        return obj;
    }

}

是不是感觉这个类和动态代理的处理类很像呢?

建立bean3.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org.dtd/spring-beans.dtd">
<beans>
    <bean id="myaroundadvice" class="com.aop.test.MyAroundAdvice"/>
    <bean id="target" class="com.aop.test.UserLoginImpl"/>
    <!-- 使用Spring代理工厂配置一个代理 -->
    <bean id="userlogin" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 指定代理接口,如果是多个接口,可以使用List元素指定 -->
        <property name="proxyInterfaces" value="com.aop.test.UserLogin"/>
        <!-- 指定通知 -->
        <property name="interceptorNames" value="myaroundadvice"/>
        <!-- 指定目标对象 -->    <!-- 这个地方的name 我开始按照书上写的target报错,我进ProxyFactoryBean类看 没有target属性和set它的方法,有targetName属性修改了就对了-->
        <property name="targetName" value="target"/>
    </bean>
</beans>

测试类

package com.aop.test;

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

public class BeforeAdviceTest3 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("/com/aop/test/bean3.xml");
        UserLogin ul = (UserLogin) ac.getBean("userlogin");
        ul.login("张三");
    }
}

控制台输出:

正在对张三进行登录验证
张三 正在登录系统
张三登录成功


异常通知

我们修改下UserLoginImpl类

package com.aop.test;

public class UserLoginImpl implements UserLogin {

    public void login(String userName) {
        if ("张三".equals(userName)) {
            System.out.println(userName + " 正在登录系统");
        } else {
            throw new RuntimeException("用户名不正确");
        }
    }
}

创建实现异常通知的类

package com.aop.test;

import java.lang.reflect.Method;

import org.springframework.aop.ThrowsAdvice;

public class ExceptionAdvice implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] objs, Object target, Exception ex) {
        System.out.println("Method:" + method.getName() + "抛出异常: " + ex.getMessage());
    }
}

创建bean4.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org.dtd/spring-beans.dtd">
<beans>
    <bean id="exceptionadvice" class="com.aop.test.ExceptionAdvice"/>
    <bean id="target" class="com.aop.test.UserLoginImpl"/>
    <!-- 使用Spring代理工厂配置一个代理 -->
    <bean id="userlogin" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 指定代理接口,如果是多个接口,可以使用List元素指定 -->
        <property name="proxyInterfaces" value="com.aop.test.UserLogin"/>
        <!-- 指定通知 -->
        <property name="interceptorNames" value="exceptionadvice"/>
        <!-- 指定目标对象 -->    <!-- 这个地方的name 我开始按照书上写的target报错,我进ProxyFactoryBean类看 没有target属性和set它的方法,有targetName属性修改了就对了-->
        <property name="targetName" value="target"/>
    </bean>
</beans>

测试类

package com.aop.test;

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

public class BeforeAdviceTest4 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("/com/aop/test/bean4.xml");
        UserLogin ul = (UserLogin) ac.getBean("userlogin");
        ul.login("张ss");
    }
}

输入非 张三 就打印出

Method:login抛出异常: 用户名不正确  并且抛出 java.lang.RuntimeException: 用户名不正确

输入 张三就 打印出

张三 正在登录系统


java动态代理,和spring aop的4种 通知的简单例子就分享到这里啦。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!