spring aop原始功能失效的情况下,自定义AOP实现

问题引入

现在spring已经是每个java开发者必知的框架之一,其面向切面的功能,给很多开发者带来的福音。Spring基于@AspectJ的类增强技术使得我们只需要简单的编写一个切点类,编写对函数的拦截方法就可以,开发者不需要关心Spring做了什么工作,采用的哪一种方式进行代理。

当有一天,你在基于公司的平台进行开发的时候,发现下面的代码根本无法响应:

package com.chuhui.aop.aspect;
@Aspect
 public class ControllerAspect {
     @Pointcut("execution(com.chuhui.aop.controller.ControllerAspect.queryData(..))")
       public void queryDataAspect(){  //do something   }

}

请求只去执行原始com.chuhui.aop.controller.ControllerAspect.queryData的函数,根本不朝设置的切点com.chuhui.aop.aspect.ControllerAspect .queryDataAspect走,怎么办?

上面的代码只是Spring所提供的4种类型的AOP支持之一,其他三种方式,也都搞不定,怎么办?

很不幸,这个问题被我遇到了。至于问题产生的原因,很抱歉,没有找到,我们现在开发所使用的Spring,是公司平台部门基于公司业务进行深入定制的版本,里面的代码被改动的面目全非,除了IoC还保留之外,AOP应该被砍掉了。看到这里可能很多人会问,为什么会被砍掉,不好意思,我也不知道,我还没有加入公司时,这个平台就已经存在n久了。

解决原理

对于我们早期的代码,真可以用一个惨不忍睹来形容,代码的分层没有,设计模式没有,接到一个新的需求,一个字,就是干,导致早期的代码出现了极度冗余,极度烂,一个类中,service,dao,controller全部搞定,一个函数500行,参数个数更甚至多达30多个,一个类多达3000多行代码,真是应了那句话,找入口函数都能找一天,至于接口、抽象类,没见过,你指望一个天天写增删改查的码农能整出多少花样,真正明白接口和抽象类的又有多少。不好意思,又扯远了。。。。。

废话不多说,既然找不到问题产生的原因,就去想解决问题的办法。

我们知道,对一个类的功能进强,有两种方式:
1. 直接修改该类的代码
2. 对该类进行继承

首先第一点,直接去改代码,这一个办法是最简单,也最容易想到的办法,但是有一点,这种方式是在是太low了。 第二点,对该类进行继承,因为这个类不知道在多少个地方被使用,需要改动的地方也不少。

在不改变原有代码的情况下对一个类进行增强,java的那帮大牛们给我们提供了一个很好的解决办法:jdk动态代理。但是jdk动态代理有其局限性,就是被代理的类必须要有接口。于是就产生了一种新的技术:cglib,该技术对任何类都能进行代理,不管你有没有接口。

对于jdk动态代理cglib的简单介绍就到这里。至于其使用方法以及其原理,笔者在其他博客中会介绍到。

对于有经验的Java开发者都知道,Spring的AOP其底层就是通过jdk动态代理cglib来产生的代理类。

直接上干货。

service和dao层的代理方法(小升初水平)

以下介绍会用到controller、service、dao三个层的类,这里笔者先进行定义出来,为了节省篇幅,就不写接口了,而代理的方式只选cglib

controller层

@Controller
@RequestMapping("customController")
public class CustomController {
    @Autowired
    private CustomService customService;
    @RequestMapping("/obtainData/{userName}")
    public @ResponseBody
    List<Map<String,Object>> obtainData(@PathVariable String userName){
        List<Map<String, Object>> result = customService.obtainDataByName(userName);
        return result;
    }
}

service

@Service("customService")
public class CustomService {

    @Autowired
    private CustomDaoImpl customDaoImpl;

    public List<Map<String,Object>> obtainDataByName(String userName){

        List<String> queryResult = customDaoImpl.queryDataByName(userName);

        if(queryResult==null ||queryResult.size()<=0)
            return new ArrayList<Map<String,Object>>();

        List<Map<String, Object>> result=new ArrayList<Map<String,Object>>(queryResult.size());
        for(String str:queryResult) {
                Map<String, Object> map=new HashMap<String, Object>();
                map.put("userId",str);
                map.put("userName", str+userName);
                result.add(map);
        }
        return result;
    }

}

dao

@Repository("customDaoImpl")
public class CustomDaoImpl {

    public List<String> queryData() {
        List<String> result = new ArrayList<String>();
        // 从10开始,30结束
        result.add("10");
        result.add("11");
        result.add("12");
        result.add("13");
        result.add("14");
        result.add("15");
        result.add("16");
        result.add("17");
        result.add("18");
        result.add("19");
        result.add("20");
        result.add("21");
        result.add("22");
        result.add("23");
        result.add("24");
        result.add("25");
        result.add("26");
        result.add("27");
        result.add("28");
        result.add("29");
        result.add("30");

        return result;

    }

}

因为这里没有接口的存在,所以对service和dao进行代理的时候,只能选择cglib,比如,现在有一个需求,将userId的值大于等于15,且小于等于25的去掉,不显示。如果不去修改代码,我们有两个可选的代理方案:对dao进行代理,过滤掉userId>=15 && userId<=25的值;对service进行代理,过滤掉userId>=15 && userId<=25的值;其实这两个方案都可以,而dao层的queryData查询出来的数值,可能在别的函数里面也被使用,而恰好别的函数不需要进行过滤,那我们对dao层过滤就显得鲁莽了。当然,在service也存在这个问题,但是一般情况下,dao层中的函数被引用的次数较多,而service层中的每一个函数在controller层中有与之相对应的调用函数,所以service层被引用的次数就少,这里只是笔者个人的经验之谈,事无绝对,请勿钻牛角尖。在这里,笔者选择对service层进行代理,代码如下:

@Service("customServiceAspect")
public class CustomServiceAspect {
    /**
     * 上限
     */
    final static Integer FLAGTOP=25;
    /**
     * 下限
     */
    final static Integer FLAGBUTTOM=15;

    public CustomService serviceProxyGen(final CustomService service ) {

        Enhancer enhancer =new Enhancer();
        enhancer.setSuperclass(service.getClass());
        enhancer.setCallback(new MethodInterceptor() {

            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
                    throws Throwable {

                if("obtainDataByName".equals(method.getName())) {//需要拦截的函数名称
                    List<Map<String, Object>> result = (List<Map<String, Object>>) method.invoke(service, args);

                    //进行数据过滤
                    for(int i=result.size()-1;i>=0;i--) {

                        Map<String, Object> map = result.get(i);
                        Integer userId=null;
                        try {
                            userId=Integer.parseInt(map.get("userId").toString());
                        }catch (Exception e) {
                            continue;
                        }

                        if(userId>=FLAGBUTTOM && userId<=FLAGTOP) 
                            result.remove(i);//这里,应该明白为什么进行倒序遍历了吧
                    }

                    return result;  
                }
                return method.invoke(service, args);
            }
        });
        CustomService proxy = (CustomService) enhancer.create();
        return  proxy;
    }

}

最后一步,我们需要在controller中进行一丢丢的修改,修改结果如下:

@Controller
@RequestMapping("customController")
public class CustomController {

    @Autowired
    private CustomService customService;

    @Autowired
    private CustomServiceAspect customServiceAspect;// 新增的代码

    @RequestMapping("/obtainData/{userName}")
    public @ResponseBody
    List<Map<String,Object>> obtainData(@PathVariable String userName){

        CustomService proxy = customServiceAspect.serviceProxyGen(customService);//新增的代码

        List<Map<String, Object>> result = proxy.obtainDataByName(userName);   //由代理去调用obtainDatbyName函数
        return result;
    }
}

在controller层中将代理类装配进来,再将函数中的代码修改一下,即可实现代理。这种方式,以修改不超过三行代码的的代价,艰难实现了功能。但,如果像前文所说的,service、dao和controller都在一个类里面,这种方式根本不顶用,因为我们不可能把代理类写进controller层的调用代码里。

办法总比问题多。我们接下来需要考虑另外一个概念,IoC。简单而言,IoC是个容器,容器里装的是对象的实例,不管它是以何种容器进行存储,树结构也好,链表也好,map也好,没关系,无所谓,我们不去关注它是以什么方式存储对象实例,我们只需要关注一点,存储对象实例之后,在对象实例产生的时候,可不可以在它把对象存储进自己的肚子里时,对对象实例做点什么小手脚嘛。。。。

对controller进行代理(初升高水平)

BeanPostProcessor 介绍

在Spring的官方文档中,有一节7.8.1 Customizing beans using a BeanPostProcessor,英文水平不好的同学请耐心,也有中文版的7.8.1 使用BeanPostProcessor定制bean,整个Spring文档还没有翻译完,个人英语水平也不咋地,别喷我就行。在该文档中第一段话:“1)BeanPostProcessor接口定义了您可以实现的回调方法,以提供您自己的(或覆盖容器的默认)实例化逻辑,依赖关系解析逻辑等等。2)如果你想在Spring容器完成实例化,配置和初始化bean之后实现一些定制逻辑,你可以插入一个或多个BeanPostProcessor实现”。

先来看第一段话提到了一个接口:BeanPostProcessor。这个接口能干啥呢?这个接口定义了那您可以实现的方法,以提供您自己的实例化逻辑,定义回调函数,请看清楚哦,定义回调,竟然还能覆盖容器中默认的实例,啧啧啧。。。好接口。再来看它调用的时间,在Spring容器完成实例化,配置和初始化bean之后,这不正是我们需要对对象实例做手脚的时候嘛。现在,我们需要瞅瞅这个接口中都有啥

public interface BeanPostProcessor {

    /**
     * 在任何bean初始化回调之前,将这个BeanPostProcessor应用到给定的新bean实例
     * (类似于InitializingBean的afterPropertiesSet或 init-method)。
     *  bean已经填充了属性值。
     * 返回的bean实例可能是原来的包装器.
     * @param bean 新的bean实例
     * @param beanName bean的名称
     * @return 要使用的bean实例,要么是原始实例,要么是包装实例;
     * 如果为null,则不会调用后续的BeanPostProcessors
     */
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

    /**
     * 在任何bean初始化回调之后,将这个BeanPostProcessor应用到给定的新bean实例
     * (如InitializingBean的afterPropertiesSet或自定义的init-method)。
     * 这个bean将已经填充了属性值.
     * 返回的bean实例可能是原来的包装器.
     * <p>在FactoryBean的情况下,FactoryBean实例和由FactoryBean创建的对象(从Spring 2.0开始)都将调用此回调函数。
     * post-processor可以通过相应的bean instanceof FactoryBean检查来决定是应用于FactoryBean还是创建的对象,还是同时应用于这两个对象。
     * <p>与所有其他BeanPostProcessor回调相比,在InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation 方法触发的短路之后,也将调用此回调。
     * @param bean 新的bean实例
     * @param beanName bean的名称
     * @return 要使用的bean实例,要么是原始实例,要么是包装实例;
     * 如果为null,则不会调用后续的BeanPostProcessors
     */
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}

首先,BeanPostProcessor这个接口中只有两个待实现的函数,通过上面的注释,可以清楚的看到,在bean初始化回调之前(postProcessBeforeInitialization)和之后(postProcessAfterInitialization)会调用这两个函数。而且在注释中明明白白写着,返回的bean实例可能是原来的包装器,换句话说,在一个bean的实例产生以后,即将进入IoC之前,先调用一次postProcessBeforeInitialization,在bean进入IoC之后,再调用一次postProcessAfterInitialization。简单的道理明白以后,现在需要做的,就是实现这两个函数,定制我们自己的业务逻辑。

@Service
public class CustomControllerAspect implements BeanPostProcessor {

    public Object postProcessAfterInitialization(Object bean, String beanName) 
            throws BeansException {
        return bean;
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }

}

这两个函数都有相同的参数beanbeanName,见名而知其意,分别代表着bean实例bean的名称。下面,对于这两个函数的调用次数笔者在这里简单介绍一下:

在当前IoC容器下,BeanPostProcessor会在每一个bean被初始化后调用。当前IoC容器下,这个词什么意思呢?
笔者现在基于osgi的平台进行开发,每一个bundle(简单理解为jar包)都属于一个单独的IoC容器,n个IoC容器通过服务总线进行连接,以达到所有bundle共享服务的目的。
简单而言,在A包中写一个aSevice,要想在B包中使用aService,就必须将A包中的aService通过配置发布出来,而不是将整个A包作为B的依赖包使用,因为B包中也可能会产生A包需要使用的service。听起来和dubbo比较相像,没错,就是一个RPC,只不过是笔者所在公司基于osgi规范自己实现的而已。因为A包和B包都有属于自己的IoC容器,相互之间没有任何联系。

对controller进行代理

既然在每一个IoC被初始化后,都会调用一次,那么我们应该怎么获取自己的欲代理的类呢(这里是CustomController)?

bean instanceof CustomController

或者

beanName.equals("customController") //customController 是 CustomController的 bean的名称

到了这一步,就该搞我们的代理了。

@Service
public class CustomControllerAspect implements BeanPostProcessor {

    public Object postProcessBeforeInitialization (Object bean, String beanName) 
            throws BeansException {

        //对 CustomController设置代理
        if(bean instanceof CustomController) {
            final CustomController controller=(CustomController) bean;
            Enhancer enhancer =new Enhancer();
            enhancer.setSuperclass(controller.getClass());
            enhancer.setCallback(new MethodInterceptor() {

                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    if("obtainData".equals(method.getName())) {//obtainData,需要拦截的函数名称
                        //do something ,这里,做具体的事情
                    }
                    return method.invoke(controller, args);
                }
            });
            bean =  enhancer.create();
        }
        return bean;
    }
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }
}

截止到现在,controller层的代理也已经搞定了,而且和之前的代理方式有很大的不同,controller层的代理对原代码的更改行数为零,更符合了面向切面编程的思想。

终极代理方式(高考水平)

对controller层进行代理一章中我们可以看到,实现BeanPostProcessor的函数,不仅可以对controller层进行代理,根据这个思想,对service、dao同样可以操作,但是,问题来了,对于开发人员来讲,代码行数写的越少越好,BeanPostProcessor中的postProcessAfterInitialization函数对我们这个功能来讲,根本就是没用的,只需要让他执行默认的实现就可以了。还有就是我给你指定选择哪种代理方式,然后我自己去实现回调函数,不需要知道cglibjdk动态代理是怎么产生代理对象的。

在笔者认为,只要能提出需求,就一定有解决办法。正是因为这一点,笔者现在天天做流水线,有时还兼顾搬砖、垒墙等技术难题。。。。。

我们知道,除了接口之外,还有抽象类,我们可以用一个抽象类来实现BeanPostProcessor,这样上述所说的让postProcessAfterInitialization执行默认实现,就有了可行性,可以这样干:

public abstract class CustomAopUtil  implements BeanPostProcessor{

    public abstract Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException;

    public   Object postProcessAfterInitialization(Object bean, String beanName) 
            throws BeansException {
        return bean;
    }
}

虽然在上文中,笔者将postProcessBeforeInitialization定义为了抽象函数,但是postProcessAfterInitialization也是有其自己的用处的,假如有一天需要在bean存储进IoC容器后执行回调,难不成再改代码?这是不可取的,所以,将两个函数都搞成默认实现。则,CustomAopUtil中的postProcessBeforeInitialization函数就不再能定义成抽象的,也让其拥有自己的默认实现,如下:

public  Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException{
        return bean;
    }

现在来看,我们可以根据自己的需要在postProcessAfterInitialization或者postProcessBeforeInitialization之中做手脚了。但是下一个问题,指定代理方式,自己实现回调函数,不关心代理是如何产生代理对象。这一点如何实现呢?
比如说,现在我们需要使用jdk动态代理来进行代理,不让开发人员去写Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)这个函数,那么就必须要我们来整了。既然这样,那么就有了下面的代码:

public abstract class CustomAopUtil  implements BeanPostProcessor,InvocationHandler{

    public  Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException{
        return bean;
    }

    public   Object postProcessAfterInitialization(Object bean, String beanName) 
            throws BeansException {
        return bean;
    }

    /**
     * 从JDK获取代理
     * @param orginService
     * @return
     */
    protected final  Object genProxyFromJdkDym(final Object orginService) {
        Object proxy = Proxy.newProxyInstance(orginService.getClass().getClassLoader(), 
                orginService.getClass().getInterfaces(),
                this);

        return proxy;
    }
}

但是这段代码有三点不可取:

  1. genProxyFromJdkDym的参数,是一个Object类型。这就会产生混淆,进来的是Object,出去的还是Object,在进行代理代用的时候,最后method.invoke(Object proxy, Method method, Object[] args)中的proxy,传的到底是被代理之前的对象,还是被代理之后的对象呢?
  2. 由于CustomAopUtil是抽象类,实现了InvocationHandler之后,并没有重写invoke函数,而是准备让其子类去必须重写这个invoke函数,这点是不可取的,因为如果子类必须用cglib的方式进行代理呢?
  3. ObjectgetClass返回的到底是个啥?到底是Object的class,还是Object所代表的 那个类的class?虽然我们知道,这个orginService这个参数指的是其他类,但是当orginService.getClass.getClassLoader()之后,我敢保证,肯定结果肯定不是你想要的。

针对以上三点问题,又有了一个新的思路:

public abstract class CustomAopUtil<T>  implements BeanPostProcessor,InvocationHandler{

    protected T service;//接收原始的service

    public  Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException{
        return bean;
    }

    public   Object postProcessAfterInitialization(Object bean, String beanName) 
            throws BeansException {
        return bean;
    }

    /**
     * 从JDK获取代理
     * @param orginService
     * @return
     */
    @SuppressWarnings("unchecked")
    protected final  T genProxyFromJdkDym(final T orginService) {
        service=orginService;//保存一下原始的service
        T proxy = (T) Proxy.newProxyInstance(orginService.getClass().getClassLoader(), 
                orginService.getClass().getInterfaces(),
                this);

        return proxy;
    }
    /**
     * 默认的invoke函数
     */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable{
        return method.invoke(service, args);//service使用调用的是原始的service,而不是代理之后的service
    }

}

这样,就比较完美一点了。。。当然,在这个类里面,可以依样画葫芦,把cglib的实现也贴上去,最后的的代码如下:


public abstract class CustomAopUtil<T>  implements BeanPostProcessor,InvocationHandler,MethodInterceptor{

    protected T service;

    public  Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException{
        return bean;
    }

    public   Object postProcessAfterInitialization(Object bean, String beanName) 
            throws BeansException {
        return bean;
    }

    /**
     * 从JDK获取代理
     * @param orginService
     * @return
     */
    @SuppressWarnings("unchecked")
    protected final  T genProxyFromJdkDym(final T orginService) {
        service=orginService;//接收原始的service

        T proxy = (T) Proxy.newProxyInstance(orginService.getClass().getClassLoader(), 
                orginService.getClass().getInterfaces(),
                this);

        return proxy;
    }

    /**
     * 从cglib获取代理
     * @param orginService
     * @return
     */
    @SuppressWarnings("unchecked")
    protected  final T genProxyFromCglib(final T orginService) {
        service=orginService;//接收原始的service

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(orginService.getClass());
        enhancer.setCallback(this);
        T proxy = (T) enhancer.create();
        return proxy;
    }

    /**
     *  默认的intercept函数 cglib
     */
     public Object intercept(Object newService, Method method,
            Object[] args, MethodProxy proxy)
            throws Throwable{

        return method.invoke(service, args);//service使用调用的是原始的service,而不是代理之后的service
    }

    /**
     * 默认的invoke函数 jdk动态代理
     */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable{
        return method.invoke(service, args);//service使用调用的是原始的service,而不是代理之后的service
    }

}

这个类的具体使用方式,一下只演示cglib

@Service
public class CustomControllerForUtil extends CustomAopUtil<CustomController> {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof CustomController) {

            bean=genProxyFromCglib((CustomController) bean);
            //如果使用jdk动态代理,则
            //bean=genProxyFromJdkDym((CustomController) bean);
        }
        return bean;
    }

    //若是使用jdk动态代理,则重写invoke函数。。。。
    @Override
    public Object intercept(Object newService, Method method, Object[] args, MethodProxy proxy) throws Throwable {

        if("obtainData".equals(method.getName())) {//需要拦截的函数名称

            Object invoke = method.invoke(this.service, args); //调用原始函数时,传的必须是代理之前的对象实例
            //do something ....
            return invoke ;
        }
        return super.intercept(this.service, method, args, proxy);
    }

}

怎么一个爽字了得

更高级的代理方式(大学水平)

个人水平受限,想不出来更好的办法了。肯定有更好的实现方式,但是现在我还搞不定。。。而且上述的解决方法,也存在瑕疵,希望能看到同行能提出更多,更简便的解决办法。

结语

对于上述的代码,因为某些原因,就不上传了,可以yunchu131125@outlook.com找我索要。至于上面所提到的其他技术文档,笔者会不定期进行更新。
ps:个人技术水平与表达能力有限,可能在某些地方讲解的有问题或者不透彻,还请不吝赐教,笔者一定会虚心接受。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值