我要造轮子之基于JDK的AOP实现

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: <div class="markdown_views"><h1 id="1-前言">1 前言</h1><p>Aspect Oriented Programing,面向切面编程。</p><p>利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。</p><p>AOP主要用于日志记录,性能统计,安

1 前言

Aspect Oriented Programing,面向切面编程。

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP主要用于日志记录,性能统计,安全控制(权限控制),事务处理,异常处理等。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

像Spring AOP一样,我们要的AOP也需要依赖于IoC容器。

我们的IoC容器实现:http://blog.csdn.net/jrainbow/article/details/50680914

本文就是通过JDK提供的反射机制来实现类似基于@AspectJ的Spring AOP的使用。

2 AOP的实现需求

基于@Aspect的Spring AOP使用:http://blog.csdn.net/jrainbow/article/details/9268637

我们通过一个基于@Aspect的Spring AOP的使用模板来看看我们需要实现哪些功能。

/*
 * 通过@Aspect把这个类标识管理一些切面
 */
@Aspect
@Component
public class FirstAspect {  

    /*
     * 定义切点及增强类型
     */
    @Before("execution(* save(..))")
    public void before(){
        System.out.println("我是前置增强...");
    }
}

从Spirng AOP的@Aspect使用代码可以看到,我们如果想实现类似这样的功能,需要对最基本的几个声明进行解析:切面、切点、增强类型。

@Before(value= "切入点表达式或命名切入点",argNames = "指定命名切入点方法参数列表参数名字,可以有多个用“,”分隔")      

切面与增强类型都是annotation。
而切点,从@Before注解看来,是一个表达式加上方法参数列表参数名字的形式。

Spring AOP中的切点表达式是结合正则表达式及它本身自有的一些规则产生的,我们这里不要太复杂,也不需要方法参数列表参数名字这个属性,直接是先考虑使用正则表达式来表示。

确定了以上的内容后,下面我们以前置增强为例开始代码实现。

3 实现过程

3.1 切面及增强类型注解

package xyz.letus.framework.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 切面
 * @ClassName: Aspect
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2016年3月7日
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
    String value() default "";
}
package xyz.letus.framework.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 前置增强注解
 * @ClassName: Before
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2016年3月7日
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Before {
    /**
     * 切点表达式
     * 
     * @Title: value
     * @Description: TODO
     * @param @return    
     * @return String    
     * @throws
     */
    String value();
}

3.2 扫描@Aspect的托管

因为我们的AOP也需要依赖于IoC容器,我们这里也使用ClassFactory类在描述basePackage包下的类时,把@Aspect描述的类找出来。

/**
 * 解析路径,并获取所要的类
 * @Title: parse
 * @Description: TODO
 * @return void    
 * @throws
 */
public void parse(){
    for (String packagePath : packages) {
        Set<Class<?>> classes = ClassLoader.getClassSet(packagePath);
        for(Class<?> clazz : classes){
            if(clazz.isAnnotationPresent(Component.class)){
                //普通类托管
                ...
            }else if(clazz.isAnnotationPresent(Aspect.class)){
                //切面类托管
                aspectClasses.put(clazz.getSimpleName(), clazz);
            }
        }
    }
}

完整的ClassFactory类:

package xyz.letus.framework.util;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import xyz.letus.framework.aop.annotation.Aspect;
import xyz.letus.framework.ioc.annotation.Component;

/**
 * 类操作助手
 * @ClassName: ClassHelper
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2015年9月19日
 *
 */
public class ClassFactory {
    private Map<String, Class<?>> componentClasses;
    private Map<String, Class<?>> aspectClasses;
    private List<String> packages;

    public ClassFactory(List<String> packages){
        this.packages = packages;
        componentClasses = new HashMap<String, Class<?>>();
        aspectClasses = new HashMap<String, Class<?>>();
        this.parse();
    }

    /**
     * 解析路径,并获取所要的类
     * @Title: parse
     * @Description: TODO
     * @return void    
     * @throws
     */
    public void parse(){
        for (String packagePath : packages) {
            Set<Class<?>> classes = ClassLoader.getClassSet(packagePath);
            for(Class<?> clazz : classes){
                if(clazz.isAnnotationPresent(Component.class)){
                    //普通类托管
                    Component component = clazz.getAnnotation(Component.class);
                    String name = clazz.getSimpleName();
                    String value = component.value();
                    if(value.length() > 0){
                        name = value;
                    }

                    componentClasses.put(name, clazz);
                }else if(clazz.isAnnotationPresent(Aspect.class)){
                    //切面类托管
                    aspectClasses.put(clazz.getSimpleName(), clazz);
                }
            }
        }
    }

    public Map<String, Class<?>> getComponentClasses() {
        return componentClasses;
    }

    public Map<String, Class<?>> getAspectClasses() {
        return aspectClasses;
    }



}

注:Spring可能为了不修改原有的IoC实现,托管的注解和切面的注解是分开的,而我们这里是放一起的。
Spring要使用切面的时候,需要使用两个注解:

@Aspect
@Component
public class FirstAspect {  

3.4 代理工具类

我们这里通过JDK提供的java.lang.reflect.Proxy来创建代理对象。不过Proxy比较局限的地方在于,代理对象的目标对象必须是基于接口的实现。当然这也是本文中基于JDK的AOP实现的不足之一。

package xyz.letus.framework.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

/**
 * 代理管理器
 * @ClassName: ProxyManager
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2016年3月7日
 *
 */
public class ProxyManager {
    /**
     * 创建一个前置增强的代理对象
     * @Title: createBeforeProxy
     * @Description: TODO
     * @param @param aspect 切面对象
     * @param @param target 目标对象
     * @param @param matchMethods 匹配的方法名
     * @param @param before 前置增强方法
     * @param @return    
     * @return T    
     * @throws
     */
    @SuppressWarnings("unchecked")
    public static <T> T createBeforeProxy(final Object aspect,final Object target,final List<String> matchMethods,final Method before){
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {

            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                /**
                 * 增加前置增强
                 */
                if(matchMethods.contains(method.getName())){
                    before.invoke(aspect, args);
                }

                Object result = method.invoke(target, args);
                return result;
            }
        });
    }
}

3.4 切面管理

在获取到所有的切面类后,我们对切面类里的增强方法进行分析处理。我们判断所有托管的普通类对象中是否有符合切点表达式的方法。并通过ProxyManager来为有符合切点方法的类对象织入增强,创建代理对象,并替换掉原有的对象,并由IoC容器管理。

/**
 * 前置增强处理
 * @Title: beforeHandle
 * @Description: TODO
 * @param @param aspect 切面对象
 * @param @param beforeMethod  增强方法
 * @return void    
 * @throws
 */
private void beforeHandle(Object aspect,Method beforeMethod){
    Before before = beforeMethod.getAnnotation(Before.class);
    String execution = before.value();
    for (Entry<String,Object> entry : beanMap.entrySet()) {
        String name = entry.getKey();
        Object target = entry.getValue();
        List<String> list = getMatchMethod(execution, target);
        if(list.size() > 0){
            Object obj = ProxyManager.createBeforeProxy(aspect, target, list, beforeMethod);
            beanMap.put(name, obj);
        }
    }

}

符合切点表达式的解析比较简单,就是完整类名加方法名的正则匹配。
这里要注意的是,我们需要对目标对象的接口方法进行判断,而不是目标对象的方法进行匹配。因为如果一个对象需要织入多个增强,我们需要进行多次代理对象的替换,而代理对象的路径并不是原有路径,不能匹配之前定义的正则表达式。

/**
 * 获取匹配的方法名
 * 
 * 现只做完整类名加方法名的解析
 * @Title: getMatchMethod
 * @Description: TODO
 * @param @param before
 * @param @param target
 * @param @return    
 * @return boolean    
 * @throws
 */
private List<String> getMatchMethod(String execution,Object target){
    List<String> list = new ArrayList<String>();
    Class<?>[] classes = target.getClass().getInterfaces();

    for(Class<?> clazz : classes){
        Method[] methods = clazz.getDeclaredMethods();

        Pattern pattern = Pattern.compile(execution);

        for(Method method : methods){
            StringBuffer methodName = new StringBuffer(clazz.getName());
            methodName.append(".").append(method.getName());
            if(pattern.matcher(methodName).matches()){
                list.add(method.getName());
            }
        }
    }

    return list;
}

完整的切面管理类:

package xyz.letus.framework.aop;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import xyz.letus.framework.aop.annotation.Before;

/**
 * 切面管理
 * @ClassName: AspectManager
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2016年3月7日
 *
 */
public class AspectManager {
    private Map<String, Object> beanMap;

    public AspectManager(Map<String, Object> beanMap){
        this.beanMap = beanMap;
    }

    /**
     * 对所有切面进行解析
     * @Title: parse
     * @Description: TODO
     * @param @param aspectClasses    
     * @return void    
     * @throws
     */
    public void parse(Map<String, Class<?>> aspectClasses) {
        for (Entry<String, Class<?>> entry : aspectClasses.entrySet()) {
            parse(entry.getValue());
        }
    }

    /**
     * 解析一个切面
     * @Title: parse
     * @Description: TODO
     * @param @param clazz    
     * @return void    
     * @throws
     */
    private void parse(Class<?> clazz) {
        try {
            Method[] methods = clazz.getMethods();
            Object aspect = clazz.newInstance();
            for(Method method : methods){
                if(method.isAnnotationPresent(Before.class)){
                    beforeHandle(aspect,method);
                }
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }

    /**
     * 获取匹配的方法名
     * 
     * 现只做完整类名加方法名的解析
     * @Title: getMatchMethod
     * @Description: TODO
     * @param @param before
     * @param @param target
     * @param @return    
     * @return boolean    
     * @throws
     */
    private List<String> getMatchMethod(String execution,Object target){
        List<String> list = new ArrayList<String>();
        Class<?>[] classes = target.getClass().getInterfaces();

        for(Class<?> clazz : classes){
            Method[] methods = clazz.getDeclaredMethods();

            Pattern pattern = Pattern.compile(execution);

            for(Method method : methods){
                StringBuffer methodName = new StringBuffer(clazz.getName());
                methodName.append(".").append(method.getName());
                if(pattern.matcher(methodName).matches()){
                    list.add(method.getName());
                }
            }
        }

        return list;
    }


    /**
     * 前置增强处理
     * @Title: beforeHandle
     * @Description: TODO
     * @param @param aspect 切面对象
     * @param @param beforeMethod  增强方法
     * @return void    
     * @throws
     */
    private void beforeHandle(Object aspect,Method beforeMethod){
        Before before = beforeMethod.getAnnotation(Before.class);
        String execution = before.value();
        for (Entry<String,Object> entry : beanMap.entrySet()) {
            String name = entry.getKey();
            Object target = entry.getValue();
            List<String> list = getMatchMethod(execution, target);
            if(list.size() > 0){
                Object obj = ProxyManager.createBeforeProxy(aspect, target, list, beforeMethod);
                beanMap.put(name, obj);
            }
        }

    }

    public Map<String, Object> getBeanMap() {
        return beanMap;
    }
}

3.5 替换BEAN_MAP

把拥有代理对象的map替换到BeanFactory中去。
完整的BeanFactory:

package xyz.letus.framework.ioc;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import xyz.letus.framework.aop.AspectManager;
import xyz.letus.framework.util.ClassFactory;
import xyz.letus.framework.util.ReflectionFactory;


/**
 * Bean助手类
 * @ClassName: BeanHelper
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2015年9月19日
 *
 */
public class BeanFactory {
    private static Map<String, Object> BEAN_MAP = new HashMap<String, Object>();

    /**
     * 创建所有托管的实例
     * @Title: createInstance
     * @Description: TODO
     * @param @param packages    
     * @return void    
     * @throws
     */
    public static void createInstance(List<String> packages){
        ClassFactory classFactory = new ClassFactory(packages);
        Map<String, Class<?>> componentClasses = classFactory.getComponentClasses();
        Map<String, Class<?>> aspectClasses = classFactory.getAspectClasses();

        //对普通的托管类进行处理
        for (Entry<String, Class<?>> entry : componentClasses.entrySet()) {
            Object obj = ReflectionFactory.newInstance(entry.getValue());

            BEAN_MAP.put(entry.getKey(), obj);
        }

        //依赖注入
        IocHelper.inject(BEAN_MAP);

        //切面处理
        AspectManager aspectManager = new AspectManager(BEAN_MAP);
        aspectManager.parse(aspectClasses);
        BEAN_MAP = aspectManager.getBeanMap();

    }


    /**
     * 获取Bean实例
     * @Title: getBean
     * @Description: TODO
     * @param @param name
     * @param @return    
     * @return T    
     * @throws
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name){
        if(!BEAN_MAP.containsKey(name)){
            throw new RuntimeException("can not get bean by className:"+name);
        }

        return (T) BEAN_MAP.get(name);
    }
}

4 框架使用

与IoC容器框架的使用类似。
我们对之前的Service类,让它实现一个接口。

package xyz.letus.demo.service;

public interface IService {
    public void say();
    public void play();
}
package xyz.letus.demo.service;

import xyz.letus.demo.dao.Dao;
import xyz.letus.framework.ioc.annotation.Component;
import xyz.letus.framework.ioc.annotation.Inject;

@Component("service")
public class Service implements IService{
    @Inject("a")
    private Dao dao;

    public void say(){
        dao.say();
        System.out.println("Service say something.");
    }

    public void play() {
        System.out.println("Service playing.");
    }
}
  • 通过框架注解定义一个切面
package xyz.letus.demo.aspect;

import xyz.letus.framework.aop.annotation.Aspect;
import xyz.letus.framework.aop.annotation.Before;

@Aspect
public class BeforeAspect {
    @Before("xyz.letus.demo.service.*.*")
    public void beforeA(){
        System.out.println("beforeA");
    }

    @Before("xyz.letus.demo.service.*.say")
    public void beforeB(){
        System.out.println("beforeB");
    }
}

切面中定义了两个前置增强,一个增强是针对xyz.letus.demo.service包下的所有接口方法,另一个是针对xyz.letus.demo.service包下的所有接口的say方法。

  • 资源文件context.properties没变化,依然只需配置需扫描的主包。
scanPackage=xyz.letus.demo
  • 启动IoC容器,并获取service实例
ApplicationContext context = ApplicationContext.getContext("context.properties");
IService service = context.getBean("service");
service.say();
service.play();
  • 输出结果
beforeA
beforeB
Dao say something.
Service say something.
====================
beforeA
Service playing.

可以看到say方法成功织入了两个前置增强,而play方法也成功织入了一个增强。

5 不足

  1. 通过JDK提供的java.lang.reflect.Proxy来创建代理对象的目标对象必须是基于接口的实现。
  2. 如果有多个增强针对同一个类的话,需要多次创建代理对象。
  3. 由于为了快速开发的关系,之前的IoC容器和本文的AOP框架没有解耦。

6 源码下载

https://github.com/benben-ren/wheel/tree/36e8a8d84896947349291675c9db37ce8701f590

注:源码中只有IoC与AOP构架源码,不包含使用源码。

目录
相关文章
|
5月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
2月前
|
安全 Java 开发者
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
74 4
|
8月前
|
设计模式 安全 Java
深入理解Spring Boot AOP:CGLIB代理与JDK动态代理的完全指南
深入理解Spring Boot AOP:CGLIB代理与JDK动态代理的完全指南
2001 1
|
Java Spring 容器
Spring AOP基础&动态代理&基于JDK动态代理实现
Spring AOP基础&动态代理&基于JDK动态代理实现
122 0
|
Java
AOP(JDK动态代理实现)
采用JDK动态代理实现AOP必须要使代理类和被代理类实现同一个接口
106 0
AOP(JDK动态代理实现)
|
设计模式 Java 编译器
Spring AOP【AOP的基本实现与动态代理JDK Proxy 和 CGLIB区别】
Spring AOP【AOP的基本实现与动态代理JDK Proxy 和 CGLIB区别】
Spring AOP【AOP的基本实现与动态代理JDK Proxy 和 CGLIB区别】
|
XML Java 数据格式
从零开始造Spring09---实现AOP的JDK代理
接上一篇从零开始造Spring08—AOP(介绍以及实现ReflectiveMethodInvocation和AopProxyFactory),这篇文章我们接着来讲Spring的AOP的JDK代理,这是学习刘欣老师的《从零开始造Spring》的学习笔记。
80 0
|
Java 数据库连接 Spring
JDK动态代理机制(AOP)
这篇文章主要个大家分享JDK动态代理(AOP)
JDK动态代理机制(AOP)
|
Java C++ Spring
Spring - AOP之底层实现(动态代理 JDK VS CGLIB)
Spring - AOP之底层实现(动态代理 JDK VS CGLIB)
131 0
Spring - AOP之底层实现(动态代理 JDK VS CGLIB)