静态代理
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。
代理模式中的角色:
- 代理类(代理主题)
- 目标类(真实主题)
- 代理类和目标类的公共接口(抽象主题):客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口。
静态代理如何实现主业务功能?
在代理对象(是接口实现类,比如周润发的代理,实现场地等问题)中引入主业务(也是接口实现类,比如周润发唱歌)作为属性(关联关系)
唱歌必须要周润发来唱(主业务来,不能让代理唱歌!!代理只是做增强功能的,比如场地费用),但是两者都实现了唱歌接口,这样对于用户来说,只负责调用唱歌方法,不知道是代理还是周润发。
代码结构1
- 共同接口
public interface OrderService {
//生成订单
int generate();
//修改订单
int modify();
}
- 主业务,目标对象
@Service(value = "orderServiceImpl")//其实就只是将这个类交给spring管理,让spring创建这个类而已
public class OrderServiceImpl implements OrderService{
public int generate() {
System.out.println("订单正在生成");
return 1;
}
public int modify() {
System.out.println("订单修改完成");
return 1;
}
public int detail() {
System.out.println("订单详情");
return 1;
}
}
- 代理对象
package cn.edu.uestc.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service(value = "staticProxy")
public class StaticProxy implements OrderService{
//首先,代理类和目标类实现公共接口(抽象主题)
//第二,关联关系,代理代替周星驰唱歌肯定是不行的,要有周星驰的关联对象,请周星驰来唱歌
@Autowired
@Qualifier(value = "orderServiceImpl")//注入的是实现类,接口指向实现类
OrderService orderService;
public int generate() {
long currentTimeMillis = System.currentTimeMillis();
orderService.generate();
System.out.println("完成主业务功能:订单生成");
long currentTimeMillis1 = System.currentTimeMillis();
System.out.println("完成增强功能,程序执行耗时:" + (currentTimeMillis1-currentTimeMillis));
return 1;
}
public int modify() {
long currentTimeMillis = System.currentTimeMillis();
orderService.modify();
System.out.println("完成主业务功能:订单修改");
long currentTimeMillis1 = System.currentTimeMillis();
System.out.println("完成增强功能,程序执行耗时:" + (currentTimeMillis1-currentTimeMillis));
return 1;
}
}
代理结构2(复杂版)
- service主业务接口
public interface Service {
void buy();
}
买书的主业务
public class BookServiceImpl implements Service{
@Override
public void buy() {
System.out.println("图书购买完成");
}
}
买商品的主业务
public class ProductServiceImpl implements Service{
@Override
public void buy() {
System.out.println("商品购买业务完成~");
}
}
- 切面接口(在代理中,不仅主业务作为属性,现在切面也作为属性,目的是更加灵活。因为想指定切面的具体哪个实现类切入的时候,只要用户在测试类中new出对应的实现类传过去就行了,这就是接口执行具体实现)
/**
* 接口在定义的时候,+default 可以不必实现所有的方法,想中哪个就实现哪个
* 比如在日志里面,只要重写一个before就够了。但是在事务中,需要代理所有的方法
*
*/
public interface AOP {
default void before(){};
default void after(){};
default void exception(){};
}
日志切面
public class logAopImpl implements AOP{
@Override
public void before() {
System.out.println("before模块完成日志记录");
}
}
事物切面
public class TransAopImpl implements AOP{
@Override
public void before() {
System.out.println("before阶段完成事务注册...");
}
@Override
public void after() {
System.out.println("事务结束...");
}
@Override
public void exception() {
System.out.println("事务回滚");
}
}
- 静态代理
package cn.edu.uestc.Proxy4_static;
/**
* 两套功能,一套接口
* 设计成员变量的类型为接口,为了灵活切换目标对象,到时候想代理谁的主业务功能,传入就好(刘德华、周深)
* 问题是切面太固定了,不能动态地修改切面的功能,不能加切面等等
* 解决思路类似,代理类中的成员变量设置为接口,来一个切面的接口,用构造方法传入切面接口的具体值(比如是要事务,还是要日志)
*
* 静态代理实现了不同用户的Impl代理,能代理不同业务(既可以代理刘德华,又可以代理周深),
* 动态代理能做到:任意的方法来,都能代理(不止是buy,sing(),show()...)内部就是反射拿到method,用method.invoke(对象,参数)进行实现的!
*/
public class Agent implements Service {
//传入目标业务对象,切面对象,作为类中成员变量
public Service target;
public AOP aop;
public Agent(Service target, AOP aop) { //测试类中会传入具体实现类,以达到我们想代理谁就代理谁!!!!!!
this.target = target;
this.aop = aop;
}
public void buy(){
try {
//切面
aop.before();
//事务
target.buy();
//切面
aop.after();
}catch (Exception e){
//切面
aop.exception();
}
}
}
- 测试类
@Test
public void testProxy4(){
//Agent agent = new Agent(new BookServiceImpl(),new TransAopImpl());
Service agent = new Agent(new BookServiceImpl(),new TransAopImpl());//接口指向实现!!
//实现套多个接口
Service agent1 = new Agent(agent,new logAopImpl());//此时target=agent
agent1.buy();
}
静态代理总结
-
比如实现 两套功能,一套接口
-
设计代理类的成员变量的类型为接口,为了灵活切换目标对象,到时候想代理谁的主业务功能,传入就好(刘德华唱歌、周深唱歌),这就是接口的好处。
-
问题是切面太固定了,不能动态地修改切面的功能,不能加切面等等
解决思路类似,代理类中的成员变量设置为接口,来一个切面的接口,用构造方法传入切面接口的具体值(比如是要事务,还是要日志,都说用户在测试类中传的) ,也可以用注解进行依赖注入,注入对应的实现类。 -
静态代理实现了不同用户的Impl代理,能代理不同业务(既可以代理刘德华,又可以代理周深)。
OrderService接口是代理类和目标类的共同接口。OrderServiceImpl是目标类。OrderServiceProxy是代理类。 如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?动态代理可以解决。因为在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用。
动态代理能做到:任意的方法来,都能代理(不止是buy,sing(),show()…)内部就是反射拿到method,用method.invoke(对象,参数)进行实现的!
动态代理
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
在内存当中动态生成类的技术常见的包括:
- JDK动态代理技术:只能代理接口。
- CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
- Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
注意联系:为什么Mybatis 框架中不需要写XXXMapper的实现类呢?----原因就是mybatis使用代理模式,动态地为我们生成实现类。
总结,不仅Spring中AOP使用了动态代理,减少了代理类地数量;在mybatis中也使用了动态代理,自动生成数据连接层的实现类。
package cn.edu.uestc.Proxy5_dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 和静态代理的区别是:静态代理使用 成员变量 完成业务方法:buy() 后如果还想sing()、、show(),就要改代码
* 动态代理用反射自动去找方法
* 而且不用继承Service方法!
*/
public class ProxyFactory {
//获取动态代理对象,和静态代理区分,这里是以参数的形式传入
public static Object getAgent(Service target,AOP aop){
//类加载器
return Proxy.newProxyInstance(
//类加载器
target.getClass().getClassLoader(),
//目标对象实现的所有接口
target.getClass().getInterfaces(),
//代理功能的实现
new InvocationHandler() {
@Override
public Object invoke(
//类加载器
Object proxy,
//正在被调用的方法,buy() show() sing()...
Method method,
//目标方法的参数
Object[] args) throws Throwable {
Object obj = null;
try {
//切面
aop.before(); // 事务 日志...
//业务
obj = method.invoke(target,args);
//切面
aop.after();
}catch (Exception e){
//切面
aop.exception();
}
return obj;
}
}
);
}
}