Spring已经为我们实现了AOP技术,我们在使用的过程中只要进行简单配置就可以使用了,但在工作中我发现只会使用这些东西是远远不够的,特别是我们使用注解配置AOP的时候,虽然方便简单,但是原理我们根本不懂,这有时候会影响我们的开发效率,不懂原理,我们永远是初级。现在我就当自己根本不知道Spring的AOP,一点点往下分析:
现在我们在项目中有一个需求,就是如果我们要访问某一个类中的所有方法,那么必须在访问之前打印一句话,比方说:
public class CardServiceImpl implements CardService{
public void delete() {
System.out.println("delete");
update();
}
public void save() {
System.out.println("save");
}
public void update() {
System.out.println("update");
}
}
现在我们让访问CardServiceImpl类中的任何方法的时候都向控制台输出一句话,这个时候也许我们立即会想到在每个方法里加上打印语句,这种方法当然可行。那如果我又说不想打印了,那你怎么办?又一句句的删除?是不是有些麻烦?当然麻烦了。现在大家都流行AOP编程,AOP是如何实现上面的功能的了?AOP是通过访问目标对象的代理对象来实现的,代理对象在方法中做了手脚,怎么去生成代理对象了?我们可以使用java中的Proxy类,如下说明:
动态代理类(以下简称为代理类)是一个实现在创建类时在运行时指定的接口列表的类,该类具有下面描述的行为。 代理接口 是代理类实现的一个接口。 代理实例 是代理类的一个实例。 每个代理实例都有一个关联的调用处理程序 对象,它可以实现接口 InvocationHandler。通过其中一个代理接口的代理实例上的方法调用将被指派到实例的调用处理程序的 Invoke 方法,并传递代理实例、识别调用方法的 java.lang.reflect.Method 对象以及包含参数的 Object 类型的数组。调用处理程序以适当的方式处理编码的方法调用,并且它返回的结果将作为代理实例上方法调用的结果返回。
上面是在JDK中的说明,我们得知,在使用代理类是必须是面向接口编程的,这一点是前提条件,下面是我们使用Proxy模拟的代理类工厂:
public class ProxyClassFactory implements InvocationHandler{
private Object targetObject;
public Object createProxyInstance(Object targetObject){
this.targetObject=targetObject;
return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
this.targetObject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("进入方法");
Object result=method.invoke(targetObject,args);
System.out.println("方法结束");
return result;
}
}
先简单介绍一下上面的类:
Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
this.targetObject.getClass().getInterfaces(), this);
该方法返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。指定接口我们可以得到,类加载器也可以得到,只要写处理程序就可以了,上面我们把指定程序调到本类上,而我们的ProxyClassFactory又实现了InvocationHandler接口,实现了接口中的invoke方法,我们在调用方法之前和之后分别打印提示语句。接下来使用下我们的代理对象工厂类。
public interface CardService {
public void save();
public void delete();
public void update();
}
public class CardServiceImpl implements CardService{
public void delete() {
System.out.println("delete");
update();
}
public void save() {
System.out.println("save");
}
public void update() {
System.out.println("update");
}
}
public class Test {
public static void main(String[] args) {
ProxyClassFactory proxy=new ProxyClassFactory();
CardService card=(CardService)proxy.createProxyInstance(new CardServiceImpl());
card.save();
card.delete();
System.out.println(card.getClass().getName());
}
}
Main方法输出如下:
进入方法
save
方法结束
进入方法
delete
update
方法结束
$Proxy0
从上面可以看出来,在进入方法的时候会打印相应语句,在运行结束方法的时候会打印相应语句。同样我们打印card对象可以得到他的真实本质(代理类)。还有就是我在原来的文章中说过的:
我们在delete方法中调用了update方法,发现update方法并没有打印相应的进入方法提示和退出方法提示,这也就证明了我们在spring管理事务的时候为什么同一个service调用同一个service中的方法事务注解不管用。
我们知道使用Proxy实现代理必须有接口,这也是spring默认的实现AOP的方法,如果不用默认的JDK提供的动态代理来实现AOP,我们还可以配置spring,使用cglib(第三方jar包)来实现代理,具体如下:
public class CGLIBProxy implements MethodInterceptor {
private Object targetObject;
public Object createProxyInstance(Object targetObject){
this.targetObject = targetObject;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.targetObject.getClass());
enhancer.setCallback(this);//设置回调用对象为本身,所以我们类中实现类MethodInterceptor接口
return enhancer.create();
}
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(this.targetObject, args);
}
}
使用cglib来实现代理,不需要接口,生成的代理类就是我们目标类的子类,代理类将重写我们目标类的非final的方法。
注意spring是默认的是使用JDK来实现动态代理的,如果不进行特殊配置,都是使用这种代理方式来实现的,也就是spring为什么要提倡面向接口编程的原因之一。比方说我们的service层使用spring来进行事务管理,事务管理也是通过AOP来实现的。也就有了下面的这个例子:< xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />
@Service("transactionService")
@Transactional
public class TransactionServiceImpl implements TransactionService{
//业务实现
}
我们进行一下测试:
ApplicationContext ac=new ClassPathXmlApplicationContext("spring/applicationContext-common.xml");
transactionService=(TransactionServiceImpl)ac.getBean("transactionService");
这样的结果是得不到transactionService的,会报$Proxy19 cannot be cast to XXX之类的错误,为什么了?因为我们使用了事务注解,而事务是通过代理来实现的,也就是我们生成了TransactionServiceImpl的一个代理对象。这个代理对象按照我们的模拟来看是该类的父类。所以肯定会有强制转型错误了。所以正确的写法是:
transactionService=(TransactionService)ac.getBean("transactionService");
如果不加事务注解的话,怎么写都没错误了,因为他根本没有代理之类的事情。。。