一句话描述
代理模式是一种设计模式,简单说即是在不改变源码的情况下,实现对目标对象的功能扩展。
Java的三种代理模式
1. 静态代理
/**
* 创建接口
*/
public interface ISinger {
void sing();
}
/**
* 目标对象实现了某一接口
*/
public class Singer implements ISinger {
public void sing() {
System.out.println("唱一首歌");
}
}
/**
* 代理对象和目标对象实现相同的接口
*/
public class SingerProxy implements ISinger {
// 接收目标对象,以便调用sing方法
private ISinger target;
public UserDaoProxy(ISinger target){
this.target=target;
}
// 对目标对象的sing方法进行功能扩展
public void sing() {
System.out.println("方法执行前");
target.sing();
System.out.println("方法执行后");
}
}
总结:其实这里做的事情无非就是,创建一个代理类SingerProxy,继承了ISinger接口并实现了其中的方法。只不过这种实现特意包含了目标对象的方法,正是这种特征使得看起来像是**“扩展”了目标对象的方法。假使代理对象中只是简单地对sing方法做了另一种实现而没有包含目标对象的方法,也就不能算作代理模式了。所以这里的包含**是关键。
缺点:这种实现方式很直观也很简单,但其缺点是代理对象必须提前写出,如果接口层发生了变化,代理对象的代码也要进行维护。如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼,不过运行时的效率必定受到影响。这种方式就是接下来的动态代理。
2. 动态代理(也叫JDK代理)
JDK提供了动态代理实现"Proxy代理类"通过反射的方式在程序运行时动态生成(编译器不存在)
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强。
public interface ISinger {
void sing();
}
/**
* 目标对象实现了某一接口
*/
public class Singer implements ISinger {
public void sing() {
System.out.println("唱一首歌");
}
}
class Test {
public static void mian(String args[]){
Singer target = new Singer();
ISinger proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new InvocationHandler(){
@Override
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
System.out.println("方法执行前");
Object returnValue = method.invoke(target,args);
System.out.println("方法执行后");
return returnValue;
}
});
proxy.sing();
}
}
**总结:**以上代码只有标黄的部分是需要自己写出,其余部分全都是固定代码。由于java封装了newProxyInstance这个方法的实现细节,所以使用起来才能这么方便,具体的底层原理将会在下一小节说明。
**缺点:**可以看出静态代理和JDK代理有一个共同的缺点,就是目标对象必须实现一个或多个接口,假如没有,则可以使用Cglib代理。
3. CGLIB代理
CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口
Cglib代理,也叫做子类代理,它是在内存中构件一个子类对象,从而实现对目标对象的功能拓展。
Cglib是强大的高性能的代码生成包,它可以在运行期间拓展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception
(拦截)。
前提条件:
-
需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-3.2.5.jar
-
目标类不能为final
public class Dog{
final public void run(String name) {
System.out.println("狗"+name+"----run");
}
public void eat() {
System.out.println("狗----eat");
}
}
public class MyMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("这里是对目标类进行增强!!!");
//注意这里的方法调用,不是用反射哦!!!
Object object = proxy.invokeSuper(obj, args);
return object;
}
}
public class CgLibProxy {
public static void main(String[] args) {
//在指定目录下生成动态代理类,我们可以反编译看一下里面到底是一些什么东西
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\java\\java_workapace");
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer = new Enhancer();
//设置目标类的字节码文件
enhancer.setSuperclass(Dog.class);
//设置回调函数
enhancer.setCallback(new MyMethodInterceptor());
//这里的creat方法就是正式创建代理类
Dog proxyDog = (Dog)enhancer.create();
//调用代理类的eat方法
proxyDog.eat();
}
}
AOP面向切面编程
转自 https://www.zhihu.com/question/24863332
深入理解 : https://www.baeldung.com/spring-aop-vs-aspectj
看到一高赞回答有Bug,所以还是简单说几句吧。
先上结论:AOP不一定都像Spring AOP那样,是在运行时生成代理对象来织入的,还可以在编译期、类加载期织入,比如AspectJ。
下面再慢慢聊AOP。
什么时候要用到面向切面AOP呢?
举个例子,你想给你的网站加上鉴权,
对某些url,你认为不需要鉴权就可以访问,
对于某些url,你认为需要有特定权限的用户才能访问
如果你依然使用OOP,面向对象,
那你只能在那些url对应的Controller代码里面,一个一个写上鉴权的代码
而如果你使用了AOP呢?
那就像使用Spring Security进行安全管理一样简单(更新:Spring Security的拦截是基于Servlet的Filter的,不是aop,不过两者在使用方式上类似):
protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/static","/register").permitAll() .antMatchers("/user/**").hasRoles("USER", "ADMIN")
这样的做法,对原有代码毫无入侵性,这就是AOP的好处了,把和主业务无关的事情,放到代码外面去做。
所以当你下次发现某一行代码经常在你的Controller里出现,比如方法入口日志打印,那就要考虑使用AOP来精简你的代码了。
聊完了AOP是啥,现在再来聊聊实现原理。
所以当你下次发现某一行代码经常在你的Controller里出现,比如方法入口日志打印,那就要考虑使用AOP来精简你的代码了。
聊完了AOP是啥,现在再来聊聊实现原理。
AOP像OOP一样,只是一种编程范式,AOP并没有规定说,实现AOP协议的代码,要用什么方式去实现。
比如上面的鉴权的例子,假设我要给UserController的saveUser()方法加入鉴权,
第一种方式,我可以采用代理模式,
什么是代理模式,就是我再生成一个代理类,去代理UserController的saveUser()方法,代码大概就长这样:
class UserControllerProxy { private UserController userController; public void saveUser() { checkAuth(); userController.saveUser(); } }
这样在实际调用saveUser()时,我调用的是代理对象的saveUser()方法,从而实现了鉴权。
代理分为静态代理和动态代理,静态代理,顾名思义,就是你自己写代理对象,动态代理,则是在运行期,生成一个代理对象。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用JDK Proxy去进行代理了(为啥?你写一个JDK Proxy的demo就知道了),这时候Spring AOP会使用Cglib,生成一个被代理对象的子类,来作为代理,放一张图出来就明白了:
好,上面讲的是AOP的第一种实现,运行时织入。
但是不是所有AOP的实现都是在运行时进行织入的,因为这样效率太低了,而且只能针对方法进行AOP,无法针对构造函数、字段进行AOP。
我完全可以在编译成class时就织入啊,比如AspectJ,当然AspectJ还提供了后编译器织入和类加载期织入,这里我就不展开讨论了,我只是来澄清一下大家对AOP的误解