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

转自 https://my.oschina.net/u/1444899/blog/217739


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


学习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种 通知的简单例子就分享到这里啦。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值