代理
在现实生活中,我们如果想要卖房,大都不会去自己亲自去跑业务、找买主、谈买卖,而是会找一些第三方的中介,由中介为我们处理卖房前后的一些事务,这个“中介”就是我们的代理人,在代码中,我们很多时候也经常不想让执行的对象直接去处理某些业务逻辑,故使用到了代理。
静态代理
代理的使用有三大要素:共同接口、真实对象、代理对象。例如,我们想实现一个输出功能,并在输出前后打印出日志,我们可以这样写。
首先,定义一个共同接口:
public interface Action{
public void exec();
}
然后我们再让一个类对这个接口进行实现,作为真实对象:
public class RealObject implements Action{
@Override
public void exec(){
System.out.println("hello world");
}
}
很明显,我们想让这个真实对象专注于进行输出功能,所以日志打印的功能对它来说就是重复、冗余的了,这时我们可以再定义一个类,作为代理对象,同样实现Action接口。
public class ProxyObject implements Action{
private RealObject realObject;
public ProxyObject(RealObject realObject){
this.realObject = realObject;
}
@Override
public void exec(){
logger.i("开始执行输出");
realObject.exec();
logger.i("输出结束");
}
}
可以看出,代理对象在这里做的主要是在内部维护一个真实对象,并对真实对象的前后进行拦截,当然我们也可以在代理对象的相应方法中对真实对象的返回值进行一系列的业务操作。
我们在使用时可以直接执行ProxyObject对象中的exec()。
public class Main{
public static void main(String[]args){
ProxyObject proxy = new ProxyObject(new RealObject());
proxy.exec();
}
}
动态代理
静态代理能够让我们很方便的进行统一化管理,但很多时候静态代理并不能满足我们的需求。例如,如果我们有一个卖车方法sellCar(),也有一个卖房方法sellHouse(),我们想让代理在卖车和卖房前后都加上开始卖和结束卖的提示,我们就需要在代理类中这样处理
public class ProxyHuman implements Action{
private RealSeller realSeller;
public ProxyHuman(RealSeller realSeller){
this.realSeller = realSeller;
}
@Override
public void sellCar(){
System.out.println("开始卖");
realSeller.sellCar();
System.out.println("结束卖");
}
@Override
public void sellerHouse(){
System.out.println("开始卖");
realSeller.sellHouse();
System.out.println("结束卖");
}
}
很明显,分别在代理类中定义两个方法是非常冗余的,而且这还是理想状态,当我们想让代理类代理的不再是实现一个接口的一个类的不同方法,而是实现不同接口的不同类的不同方法,这会使我们的业务量指数增加。
这时,java给我们提供了动态代理的方案,而动态代理又分为jdk动态代理和CGLib动态代理。
jdk动态代理
jdk动态代理是java原生提供给我们的动态代理解决方案,其创建的代理类在程序运行到调用代理类对象的时候才被jvm动态创建,jvm会动态创建一个class文件,并通过代理类对象进行执行。我们只需要专注于其前后调用与预处理即可。
同样,我们需要事先创建一个共同接口
public interface Action{
public void sellCar();
public void sellHouse();
}
然后实现一个真实对象
public class RealSeller implements Action{
@Override
public void sellCar() {
System.out.println("正在卖车");
}
@Override
public void sellHouse() {
System.out.println("正在卖房");
}
}
在我们创建代理类时,就不是事先共同接口了,而是实现一个InvocationHandler接口来事先动态代理功能。
public class ProxyHuman implements InvocationHandler{
private Object target;
public Object bind(Object target) {
this.target = target;
//创建一个新的代理目标,分别传入类、类加载器与接口信息
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=null;
System.out.println("开始卖");
result=method.invoke(target, args);
System.out.println("结束卖");
return result;
}
}
其中invoke是InvocationHandler提供的需要我们进行实现的方法,在其中我们可以专注于其前后业务的拦截,不需要在意具体传入的到底是什么类对象。
这时我们进行调用
public class Main {
public static void main(String[] args) throws InterruptedException {
RealSeller realSeller = new RealSeller();
ProxyHuman proxyHuman = new ProxyHuman();
Action action = (Action) proxyHuman.bind(realSeller);
action.sellCar();
action.sellHouse();
}
}
运行结果如下
开始卖
正在卖车
结束卖
开始卖
正在卖房
结束卖
因为我们的动态代理类无视了传入的类,故任意实现接口的类均可传入其中进行此前后业务的拦截。
CGLib动态代理
如果说jdk动态代理是针对方法进行代理,那么CGLib动态代理就是针对类进行动态代理。它的实现原理是动态生成一个子类,然后用我们的前后拦截的逻辑去覆盖原本的业务方法。因为其原理为继承,所以不能对final修饰的类进行代理。
在那之前,因为CGLib并不是java库原生提供给我们的动态代理方案,所以我们在创建代理之前先要导入jar包或者在maven中添加依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
CGLib的优势在于不用创建接口,直接创建类
class RealSeller implements Action{
public void sellCar() {
System.out.println("正在卖车");
}
public void sellHouse() {
System.out.println("正在卖房");
}
}
创建代理类时要实现的是MethodInterceptor接口
class ProxyHuman implements MethodInterceptor {
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer(); //创建加强器,用来创建动态代理类
enhancer.setSuperclass(this.target.getClass()); //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
//设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
enhancer.setCallback(this);
// 创建动态代理类对象并返回
return enhancer.create();
}
// 实现回调方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始卖");
proxy.invokeSuper(obj, args); //调用业务类(父类中)的方法
System.out.println("结束卖");
return null;
}
}
在调用的时候我们实际上是新建了一个由动态代理包装过的子类对象
public class Main {
public static void main(String[] args) throws InterruptedException {
RealSeller realSeller = new RealSeller();
ProxyHuman proxyHuman = new ProxyHuman();
RealSeller realSeller1 = (RealSeller) proxyHuman.getInstance(realSeller);
realSeller1.sellCar();
realSeller1.sellHouse();
}
}
运行结果同样是
开始卖
正在卖车
结束卖
开始卖
正在卖房
结束卖
Spring中的动态代理
众所周知,Spring中的AOP特性是基于动态代理实现的,其主要遵循以下几点:
- 默认使用jdk动态代理
- 未实现接口的类需要导入外部包来使用CGLib动态代理
- 也可强制使用配置文件指定使用CGLib动态代理