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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/p_3er/article/details/50836314

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构架源码,不包含使用源码。

猜你喜欢

转载自blog.csdn.net/p_3er/article/details/50836314