静态代理模式
/*
proxy意思是代理,代理模式设计的初衷是为了让类在非必要时不必初始化,通过代理人完成它的工作就行
特别注意调用者并不知道自己调用的是代理人还是本体
这里采用了打印机打印一串字符为例子,打印机初始化需要很长时间,为了使只在必须要打印时候再初始化,采用了代理模式
复习要点
1.代理思想
2.代理人实例本体的加锁方案
3.利用反射在代理类不知道本体是谁的情况下调用他
*/
//抽象出实际执行者和代理人的共同点写成接口,用以规范和主类调用
interface Printable{
void setPrinterName(String name);
String getPrinterName();
//这个print方法用于打印字符串,代理类也要有这个方法,原因是要客户端会使用这个方法,但是不能让他直接用本体
void print(String str);
}
//主方法作为测试类
public class ProxyMain {
public static void main(String[] args) {
//客户端直接调用代理人
Printable p1=new ProxyPrinter("Alice");
System.out.println("打印机名字是"+p1.getPrinterName());
p1.setPrinterName("bob");
System.out.println("打印机名字是"+p1.getPrinterName());
p1.print("hello world");
}
}
public class ProxyPrinter implements Printable{
private String name;
Printer real;
public ProxyPrinter(String name) {
this.name = name;
}
@Override
//这里的set方法是设置打印机的名字,当real已经有值,也就是这个代理人已经指向了一个打印机,就需要抛弃上一个名字改为新name了
public void setPrinterName(String name) {
if(real!=null) real.setPrinterName(name);
this.name=name;
}
@Override
public String getPrinterName() {
return name;
}
@Override
//这个打印类需要加锁,否则在多线程下可能生成多个打印机实例,但是对print方法直接加锁会导致难以并发,因此剥离出实例化操作进行加锁
public void print(String str) {
realize();
real.print(str);
}
private synchronized void realize() {
if(real==null){
real=new Printer(name);
}
}
}
public class Printer implements Printable{
private String name;
//为了模拟大型系统初始化需要很长时间,这里在构造方法中让printer类休眠五秒
public Printer(String name) {
heavyJob();
this.name = name;
}
private void heavyJob(){
System.out.print("do heavy job ");
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.print(".");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void setPrinterName(String name) {
this.name=name;
}
@Override
public String getPrinterName() {
return name;
}
@Override
public void print(String str) {
System.out.println(str);
}
}
JoinPoint,PointCut,advice等AOP基础查看spring揭秘的143页。
静态代理的缺点
在静态代理中,即便要横织入的逻辑相同,但是由于目标对象的类型不同,也需要定义多个相应的代理对象,例如要给多个功能增加限制晚间访问的横织逻辑,我们依旧要为每个功能写出相应代理类,由此增添了许多重复的逻辑,如果有成百上千的功能,就要写成百上千的代理类,给维护和扩展带来了极大的不方便。
动态代理即是为了解决上述问题而诞生的,首先看一下Java自JDK1.3就提供的通过InvocationHandler接口和Proxy类实现的动态代理。
Java Dynamic Proxy
先写两个业务,要为这两个业务横织入晚间不可以执行的逻辑。
interface Play{
void plan();
}
class PlayGame implements Play{
public void plan(){
System.out.println("msf playing game");
}
}
interface Study{
void plan();
}
class StudyEnglish implements Study{
public void plan(){
System.out.println("msf Learning english");
}
}
接下来编写advice逻辑
class SleepInvocationHandler implements InvocationHandler{
private Object target;
public SleepInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("plan")){
if(isNight()){
System.out.println("it`s sleep time");
return null;
}
return method.invoke(target,args);
}
return null;
}
//判断是不是晚上
private boolean isNight() throws ParseException {
SimpleDateFormat sf=new SimpleDateFormat("HH:mm");
Date now,before,after;
now=sf.parse(sf.format(new Date()));
before=sf.parse("07:00");
after=sf.parse("11:00");
if(now.after(after)&&now.before(before)){
return true;
}
return false;
}
}
接下来编写测试类,两个业务各执行一次
public class test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Play play = (Play) Proxy.newProxyInstance(Play.class.getClassLoader(),
new Class[]{Play.class},
new SleepInvocationHandler(new PlayGame()));
play.plan();
Study study = (Study) Proxy.newProxyInstance(Study.class.getClassLoader(),
new Class[]{Study.class},
new SleepInvocationHandler(new StudyEnglish()));
study.plan();
}
}
注意Proxy类新建代理类方法的参数,返回接口的类加载器,要返回的类的反射,还有advice。
这种方式实现动态代理传入的必须是接口,因此当目标对象没有实现接口的时候就不能采取这种方式。
对于Spring来说,它会采用动态字节码生成的方式动态生成代理对象。
CGLIB动态字节码
动态字节码生成扩展对象的原理是,我们可以对目标对象进行继承扩展,为其生成相应的子类,而子类可以通过覆写来扩展父类的行为,借助CGLIB这样的动态字节码生成库,在系统运行期间动态的为目标对象生成扩展子类。
首先需要一个实现了Callback接口的拦截类,一般实现MethodInterceptor接口即可。
class PlanCtrl implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if(method.getName().equals("plan")){
if (new SleepInvocationHandler(null).isNight()) {
System.out.println("it`s sleep time");
return null;
}
return methodProxy.invokeSuper(o,objects);
}
return null;
}
}
随后利用Enhancer类创建代理对象
public class test {
public static void main(String[] args) {
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(StudyEnglish.class);
enhancer.setCallback(new PlanCtrl());
StudyEnglish studyEnglish = (StudyEnglish) enhancer.create();
studyEnglish.plan();
}
}