- 问题引入
- 解决原理
- service和dao层的代理方法(小升初水平)
- 对controller进行代理(初升高水平)
- BeanPostProcessor 介绍
- 对controller进行代理
- 终极代理方式(高考水平)
- 更高级的代理方式(大学水平)
- 结语
问题引入
现在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;
}
}
这两个函数都有相同的参数bean
和beanName
,见名而知其意,分别代表着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
函数对我们这个功能来讲,根本就是没用的,只需要让他执行默认的实现就可以了。还有就是我给你指定选择哪种代理方式,然后我自己去实现回调函数,不需要知道cglib
和jdk动态代理
是怎么产生代理对象的。
在笔者认为,只要能提出需求,就一定有解决办法。正是因为这一点,笔者现在天天做流水线,有时还兼顾搬砖、垒墙等技术难题。。。。。
我们知道,除了接口之外,还有抽象类,我们可以用一个抽象类来实现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;
}
}
但是这段代码有三点不可取:
-
genProxyFromJdkDym
的参数,是一个Object
类型。这就会产生混淆,进来的是Object
,出去的还是Object
,在进行代理代用的时候,最后method.invoke(Object proxy, Method method, Object[] args)中的proxy,传的到底是被代理之前的对象,还是被代理之后的对象呢? - 由于
CustomAopUtil
是抽象类,实现了InvocationHandler
之后,并没有重写invoke
函数,而是准备让其子类去必须重写这个invoke
函数,这点是不可取的,因为如果子类必须用cglib
的方式进行代理呢? -
Object
的getClass
返回的到底是个啥?到底是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:个人技术水平与表达能力有限,可能在某些地方讲解的有问题或者不透彻,还请不吝赐教,笔者一定会虚心接受。