前言
刚上大学那会,英雄联盟火的一塌糊涂,当时每天都想着升到30级开启排位之旅。可是升到30级需要大把的时间不说,这时候匹配到的人,水平过于参差不齐,问候你全家的事经常发生,那个时候就想要是能有个代练帮我升到30级该多好啊.....下面我们就通过代码的方式找下代练-..-
什么是代理模式
为其他对象提供一种代理以控制对这个对象的访问
uml类图
代理模式是一个使用率非常高的模式,像spring的aop,struts2的Form元素映射都采用了代理模式。下面我们来看下类图中三种角色的具体定义:
- subject:抽象主题,可以是接口也可以是抽象类,用来进行业务定义。
- realsubject:真实主题类,用来实现真正的业务逻辑。
- proxy:代理类,也叫委托类。它负责控制对真实主题类访问的控制,将所有抽象主题定义的方法委托给realsubject类实现,并在这个过程加上预处理和善后工作,已达到增强功能的目的.
静态代理
静态代理模式其实就是在类设计阶段就将代理类考虑在内,而不是和动态代理和cglib代理一样动态生成代理类。下面我们通过玩英雄联盟代练的例子来说明下:
public interface IGame {
void login();
void playLOL();
}
public class IGamePlayer implements IGame {
private String name;
public IGamePlayer(String name) {
this.name = name;
}
@Override
public void login() {
System.out.println(name+"登录游戏");
}
@Override
public void playLOL() {
System.out.println(name+"赢下了一局英雄联盟,获得了100金币");
}
}
public class IGameProxy implements IGame {
private IGamePlayer iGamePlayer;
public IGameProxy(IGamePlayer iGamePlayer) {
this.iGamePlayer = iGamePlayer;
}
@Override
public void login() {
iGamePlayer.login();
}
@Override
public void playLOL() {
iGamePlayer.playLOL();
}
}
public class Client {
public static void main(String[] args) {
IGameProxy iGameProxy = new IGameProxy(new IGamePlayer("bin"));
iGameProxy.login();
iGameProxy.playLOL();
}
}
测试结果:
bin登录游戏
bin赢下了一局英雄联盟,获得了100金币
可以这样理解,自己写代理类的方式就是静态代理。创建代理类和真实主题类,用代理类控制主题类的访问。游戏代练登录账号,打游戏升级,从而节省我的时间。
动态代理
动态代理就是在设计和实现阶段不需要关心代理谁,而是在运行时期才去指定代理哪个对象。spring的核心之一就是aop,俗称面向切面编程,其核心就是采用了动态代理机制。
- jdk动态代理
保持IGAME接口和业务逻辑类不变。
//通知(这个例子用来统计登录接口的耗时)
interface IAdvice {
void execute();
}
//前置通知
public class BeforeAdvice implements IAdvice {
@Override
public void execute() {
System.out.println("执行前时间:"+System.currentTimeMillis());
}
}
//后置通知
public class AfterAdvice implements IAdvice {
@Override
public void execute() {
System.out.println("执行后时间:"+System.currentTimeMillis());
}
}
public class DynamicProxy{
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
public Object getInstance(ClassLoader classLoader, Class[] interfaces){
return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//连接点
if (method.getName().startsWith("login")){
new BeforeAdvice().execute();
}
Object objec=method.invoke(target,args);
if (method.getName().startsWith("login")){
new AfterAdvice().execute();
}
return objec;
}
});
}
}
public class Client {
public static void main(String[] args) {
IGamePlayer gamePlayer = new IGamePlayer("bin");
DynamicProxy dynamicProxy = new DynamicProxy(gamePlayer);
IGame proxy= (IGame) dynamicProxy.getInstance(gamePlayer.getClass().getClassLoader(),gamePlayer.getClass().getInterfaces());
proxy.login();
proxy.playLOL();
}
}
测试结果:
执行前时间:1513498759281
bin登录游戏
执行后时间:1513498759282
bin赢下了一局英雄联盟,获得了100金币
在上面的例子中,我引用了一些aop的术语,例如连接点,通知。实现了一个非常简单的面向切面编程,由项目经验的可以看下springaop关于事务的配置,就会明白这样配置的含义了。
jdk动态代理是面向接口的,也就是说代理对象是根据目标对象的所有接口决定的。到底是怎么实现的呢,我们来看下这一段代码:
public Object getInstance(ClassLoader classLoader, Class[] interfaces){
return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//连接点
if (method.getName().startsWith("login")){
new BeforeAdvice().execute();
}
Object objec=method.invoke(target,args);
if (method.getName().startsWith("login")){
new AfterAdvice().execute();
}
return objec;
}
});
}
Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler()):这个方法是重新生成了一个对象,通过类加载器和该类所有的接口生成。当前生成的方法都是空的,具体逻辑都会由InvocationHandler来实现。InvocationHandler我采用了内部类的方式来实现,具体逻辑都是通过invoke来访问目标对象。其调用流程就是:client-->DynamicProxy-->InvocationHandler-->IGamePlayer .
- cglib代理
jdk提供的动态代理是通过接口实现的,那么cglib就是通过生成目标类的子类实现的。假如你的目标类没有实现接口,又想使用动态代理,那么cglib是你的不二选择。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)。Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.但是有一点需要额外注意,若目标类是final或者目标方法是static和final的,则不会被cglib方法拦截。
使用cglib需要引入cglib包或者spring-core(spring核心功能已经集成cglib)
public class IGamePlayer{
private String name;
public IGamePlayer(String name) {
this.name = name;
}
public IGamePlayer() {
}
public void login() {
System.out.println(name+"登录游戏");
}
public void playLOL() {
System.out.println(name+"赢下了一局英雄联盟,获得了100金币");
}
}
public class DynamicProxy implements MethodInterceptor {
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
public Object getProxyInstance(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//模拟事务
System.out.println("事务开始了");
method.invoke(target,objects);
System.out.println("事务结束了");
return null;
}
}
public class Client {
public static void main(String[] args) {
IGamePlayer iGamePlayer = new IGamePlayer("bin");
DynamicProxy dynamicProxy = new DynamicProxy(iGamePlayer);
IGamePlayer proxy = (IGamePlayer) dynamicProxy.getProxyInstance();
proxy.login();
proxy.playLOL();
}
}
测试结果:
事务开始了
bin登录游戏
事务结束了
事务开始了
bin赢下了一局英雄联盟,获得了100金币
事务结束了
如例子,我们定义了一个目标对象类,没有实现接口,通过cglib的Enhancer工具类指定父类和回调,创建代理类。实现MethodInterceptor,重写intercept,相当于jdk动态代理的invoke。
总结
代理模式分为静态代理和动态代理。最大的区别就是静态代理是自己写代理,而动态代理是通过运行时动态的生成代理类。动态代理是springaop的核心,又分为jdk代理和cglib代理,前者通过接口生成代理后者通过定义父类的子类来生成代理(类不能为final,方法不能为static和final)。
代理模式或许是大家接触的最多的模式,有了springaop和aspectj这样优秀的工具,我们拿来定义即可。在学习aop框架的时候,要先弄清一些专业名词,切面、切入点、通知、织入,理解这些名词,知道代理模式的原理,学起aop框架就会游刃有余了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。