2018網(wǎng)站外鏈怎么做谷歌seo顧問
Day49
代理模式proxy
概念: 代理(Proxy)是一種設(shè)計(jì)模式,提供了對(duì)目標(biāo)對(duì)象另外的訪問方式,即通過代理對(duì)象訪問目標(biāo)對(duì)象.這樣做的好處是:可以在目標(biāo)對(duì)象實(shí)現(xiàn)的基礎(chǔ)上,增強(qiáng)額外的功能操作,即擴(kuò)展目標(biāo)對(duì)象的功能.
代理模式分為靜態(tài)代理和動(dòng)態(tài)代理兩種 。
靜態(tài)代理
思路:對(duì)于同一個(gè)接口,代理類和被代理類都實(shí)現(xiàn)了這個(gè)接口,代理類中將被代理類對(duì)象持有為自己的屬性,這樣在使用方法的時(shí)候就可以在被代理類的方法前后加上增強(qiáng),即自己的方法邏輯。
以Speaker接口、ChineseSpeaker、AmericaSpeaker、ChinsesSpeakerProxy、AmericaSpeakerProxy為例,其中ChinsesSpeakerProxy、AmericaSpeakerProxy分別是ChineseSpeaker、AmericaSpeaker的代理類:
Speaker:
public interface Speaker {void speak(); }
ChineseSpeaker:
public class ChineseSpeaker implements Speaker{public void speak() {System.out.println("中文演講");} }
ChinsesSpeakerProxy:
public class ChineseSpeakerProxy implements Speaker{private ChineseSpeaker speaker;public ChineseSpeakerProxy(ChineseSpeaker speaker) {this.speaker = speaker;}@Overridepublic void speak() {System.out.println("增強(qiáng)處理");speaker.speak();System.out.println("增強(qiáng)處理");} }
可以看出,這種寫法每代理一個(gè)真實(shí)類就需要寫一個(gè)代理類,對(duì)于AmericaSpeaker,同樣要寫一個(gè)AmericaSpeakerProxy。如果對(duì)于功能增強(qiáng)的內(nèi)容完全相同,就可以使用一個(gè)對(duì)于Speaker接口通用的代理類CommonSpeakerProxy,利用多態(tài)完成代理。
public class CommonSpeakerProxy implements Speaker {private Speaker speaker;public CommonSpeakerProxy(Speaker speaker) {this.speaker = speaker;}@Overridepublic void speak() {System.out.println("前置功能增強(qiáng)");speaker.speak();System.out.println("后置功能增強(qiáng)");} }
除了這種寫法外,還可以利用反射的思想來寫,利用多態(tài)通過接口實(shí)現(xiàn)類拿到方法,通過有參傳入的類對(duì)象,用method.invoke()方法完成代理。
假設(shè)這時(shí)再添加了Seller接口、ChineseSeller、AmericaSeller類及其代理類CommonSellerProxy
public class CommonSellerProxy implements Seller{private static Method method;private Object seller;public CommonSellerProxy(Object seller){this.seller = seller;}static {try {method = Seller.class.getMethod("sell");} catch (NoSuchMethodException e) {e.printStackTrace();}}@Overridepublic void sell() {try {System.out.println("前置功能增強(qiáng)");method.invoke(seller);System.out.println("后置功能增強(qiáng)");} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);}} }
可以看出,靜態(tài)代理每實(shí)現(xiàn)一個(gè)真實(shí)類的代理就需要寫一個(gè)代理類,如果代理的功能不同,就需要針對(duì)代理功能編寫多個(gè)類,十分復(fù)雜。那么有沒有一種方式使得在使用到代理的時(shí)候再去編寫代理的邏輯功能,而不是每次都去多寫一個(gè)類呢?這就是動(dòng)態(tài)代理的思想。
動(dòng)態(tài)代理
動(dòng)態(tài)代理又根據(jù)代理對(duì)象進(jìn)行劃分:
為接口做代理:JDK動(dòng)態(tài)代理
為類做代理:CGLIB動(dòng)態(tài)代理
JDK動(dòng)態(tài)代理
由于動(dòng)態(tài)代理和靜態(tài)代理差別較大,這里從靜態(tài)代理開始進(jìn)階優(yōu)化,直到達(dá)到動(dòng)態(tài)代理的范疇。
**靜態(tài)代理進(jìn)階:**思路:寫一個(gè)接口,接口中定義了代理重寫的方法,在代理類中以匿名內(nèi)部類的方式創(chuàng)建一個(gè)類對(duì)象作為自己持有的屬性,并用全參構(gòu)造要求使用時(shí)創(chuàng)建這個(gè)匿名內(nèi)部類(即重寫代理方法內(nèi)容),而代理類中就只需要調(diào)用接口的實(shí)現(xiàn)類的方法就行,不需要再寫明方法邏輯。
這個(gè)接口是jdk自帶的接口,在這里自己寫一遍,以更好地明白邏輯:
MethodInvocationHandler接口:
public interface MethodInvocationHandler {Object handle(Object target, Method method,Object[] args) throws Exception; }
注意:這個(gè)方法的本質(zhì)是反射,利用method.invoke()方法進(jìn)行調(diào)用真實(shí)類的方法,再加上代理類的方法,因此參數(shù)為method.invoke()的參數(shù)。
ChineseSpeakerProxy代理類:
public class ChineseSpeakerProxy implements Speaker{private Speaker speaker;private MethodInvocationHandler handler;private static Method method;static {try {method = Speaker.class.getMethod("speak");} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}public ChineseSpeakerProxy(Speaker speaker, MethodInvocationHandler handler) {this.speaker = speaker;this.handler = handler;}//如果有多個(gè)需要代理的方法就都要進(jìn)行重寫@Overridepublic void speak() {try {handler.handle(speaker,method,null);//由于真實(shí)類中的方法是無參的,所以這里的參數(shù)數(shù)組為空} catch (Exception e) {throw new RuntimeException(e);}} }
使用:
public class Test01 {public static void main(String[] args) {ChineseSpeaker chineseSpeaker = new ChineseSpeaker();ChineseSpeakerProxy chineseSpeakerProxy = new ChineseSpeakerProxy(chineseSpeaker, new MethodInvocationHandler() {@Overridepublic Object handle(Object target, Method method, Object[] args) throws Exception {System.out.println("功能增強(qiáng)");method.invoke(target,args);System.out.println("功能增強(qiáng)");return null;}});chineseSpeakerProxy.speak();}
至此,對(duì)于同一個(gè)代理類的多個(gè)功能,實(shí)現(xiàn)了讓用戶自己寫增強(qiáng)方法的目的。但是對(duì)于一個(gè)真實(shí)類,如果要實(shí)現(xiàn)其代理,那還是要寫一個(gè)代理類,如果真實(shí)類很多,那就需要寫很多的代理類,代理繁多的問題依然存在。
如果這些代理類能夠使用代碼來生成,然后再編譯,再加載至
JVM
中,那么再多的代理也就不是問題了。動(dòng)態(tài)代理:
手動(dòng)寫一個(gè)能夠自動(dòng)創(chuàng)建代理類源碼的類,然后手動(dòng)完成編譯、加載的過程。(這些功能jdk的接口都實(shí)現(xiàn)了,這里只手寫一個(gè)自動(dòng)創(chuàng)建代理類源碼的類以便深刻理解)
因此,可以手動(dòng)寫一個(gè)能夠自動(dòng)創(chuàng)建代理類源碼的類,然后手動(dòng)完成編譯、加載的過程。(這些功能jdk的接口都實(shí)現(xiàn)了,這里只手寫一個(gè)自動(dòng)創(chuàng)建代理類源碼的類以便深刻理解)
package com.qf.proxy;import com.qf.proxy.dynamic.MethodInvocationHandler;import java.lang.reflect.Method; import java.lang.reflect.Parameter;public class MyProxy {private static String generateProxyClass(Class<?> clazz){if(!clazz.isInterface()) throw new IllegalArgumentException(clazz.getName() + " 不是接口");StringBuilder builder = new StringBuilder();builder.append("package ").append(clazz.getPackage().getName()).append(";\n");builder.append("import ").append(Method.class.getName()).append(";\n");builder.append("import ").append(MethodInvocationHandler.class.getName()).append(";\n");builder.append("import ").append(MyProxy.class.getName()).append(";\n");builder.append("public class $proxy0 extends MyProxy implements ").append(clazz.getSimpleName()).append("{\n");StringBuilder staticBuilder = new StringBuilder();staticBuilder.append("static {\n");staticBuilder.append("try {\n");StringBuilder overrideMethodBuilder = new StringBuilder();Method[] methods = clazz.getMethods();for(int i=0; i<methods.length; i++){builder.append("private static Method m").append(i).append(";\n");staticBuilder.append("m").append(i).append("=Class.forName(\"").append(clazz.getName()).append("\").getMethod(\"").append(methods[i].getName()).append("\",");overrideMethodBuilder.append("\n@Override\n");overrideMethodBuilder.append("public ").append(methods[i].getReturnType().getSimpleName()).append(" ").append(methods[i].getName()).append("(");Parameter[] parameters = methods[i].getParameters();for(Parameter parameter : parameters){staticBuilder.append(parameter.getType().getSimpleName()).append(".class,");overrideMethodBuilder.append(parameter.getType().getSimpleName()).append(" ").append(parameter.getName()).append(",");}staticBuilder.deleteCharAt(staticBuilder.length()-1);staticBuilder.append(");\n");if(parameters.length > 0)overrideMethodBuilder.deleteCharAt(overrideMethodBuilder.length()-1);overrideMethodBuilder.append("){\n");Class returnType = methods[i].getReturnType();if(returnType != Void.class && returnType != void.class)overrideMethodBuilder.append("return (").append(methods[i].getReturnType().getSimpleName()).append(")");overrideMethodBuilder.append("handler.handle(m").append(i).append(",new Object[]{");for(Parameter parameter : parameters){overrideMethodBuilder.append(parameter.getName()).append(",");}if(parameters.length > 0)overrideMethodBuilder.deleteCharAt(overrideMethodBuilder.length()-1);overrideMethodBuilder.append("});\n}");}staticBuilder.append("} catch (NoSuchMethodException e) {\ne.printStackTrace();\n}catch (ClassNotFoundException e) {\ne.printStackTrace();\n}\n");staticBuilder.append("}\n");builder.append(staticBuilder);builder.append("protected $proxy0(MethodInvocationHandler handler) {\nsuper(handler);\n}\n");builder.append(overrideMethodBuilder);builder.append("\n}");System.out.println(builder);return builder.toString();}// public static void main(String[] args) { // generateProxyClass(Seller.class); // } }
這個(gè)類完成了自動(dòng)創(chuàng)建類源碼的功能,其實(shí)現(xiàn)的思路就是將一個(gè)代理類中的特定類利用反射和object去替換,然后將整個(gè)類寫成字符串放進(jìn)StringBuilder中。
然后是編譯代理類源文件、加載編譯好的代理類(利用類加載器)、編寫創(chuàng)建代理實(shí)例的方法。這些底層就不手寫了。
接下來對(duì)比一下兩種寫法的區(qū)別(這里都實(shí)現(xiàn)jdk自帶的InvocationHandler接口,接口內(nèi)容和上面手寫的MethodInvocationHandler一致,只是方法名字為invoke,我寫的是handle):
手動(dòng)創(chuàng)建ChineseSpeakerProxy(此時(shí)仍然為靜態(tài)代理,因?yàn)榇眍愂窃诰幾g時(shí)明確定義的,并且代理類的代碼是手動(dòng)編寫的。相對(duì)于動(dòng)態(tài)代理,靜態(tài)代理類在運(yùn)行時(shí)不會(huì)自動(dòng)生成,而是在編譯時(shí)就已經(jīng)存在。 ):
public class ChineseSpeakerProxy implements Speaker{private Speaker speaker;private InvocationHandler handler;private static Method method;static {try {method = Speaker.class.getMethod("speak");} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}public ChineseSpeakerProxy(Speaker speaker, InvocationHandler handler) {this.speaker = speaker;this.handler = handler;}@Overridepublic void speak() {try {handler.invoke(speaker,method,null);} catch (Throwable e) {throw new RuntimeException(e);}} }public class Test01 {public static void main(String[] args) {ChineseSpeaker chineseSpeaker = new ChineseSpeaker();ChineseSpeakerProxy chineseSpeakerProxy = new ChineseSpeakerProxy(chineseSpeaker, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {method.invoke(proxy,args);System.out.println("方法增強(qiáng)");return null;}});chineseSpeakerProxy.speak();}
使用jdk自帶的Proxy類靜態(tài)方法創(chuàng)建(動(dòng)態(tài)代理):
public class Test01 {public static void main(String[] args) {ChineseSpeaker chineseSpeaker = new ChineseSpeaker();Speaker ChineseSpeakerProxy = (Speaker) Proxy.newProxyInstance(ChineseSpeaker.class.getClassLoader(), ChineseSpeaker.class.getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("方法增強(qiáng)");method.invoke(chineseSpeaker,args);return null;}});ChineseSpeakerProxy.speak();}
注意:使用Proxy類實(shí)現(xiàn)動(dòng)態(tài)代理的時(shí)候,method.invoke()的第一個(gè)參數(shù)不是形參給的,而是被代理的類對(duì)象! 因?yàn)槭褂?Proxy.newProxyInstance動(dòng)態(tài)生成的代理類會(huì)把代理實(shí)例本身傳遞給 InvocationHandler.invoke方法的 proxy 參數(shù)。因此,如果在 invoke 方法中再次調(diào)用 method.invoke(proxy, args),就會(huì)導(dǎo)致遞歸調(diào)用形成死循環(huán)。
總結(jié)
靜態(tài)代理:
寫法一:真實(shí)類和代理類實(shí)現(xiàn)同一個(gè)接口,代理類持有真實(shí)類對(duì)象作為屬性,并在重寫接口方法的時(shí)候調(diào)用其方法,再添加方法增強(qiáng)。
寫法二:如果多個(gè)真實(shí)類需要進(jìn)行的方法增強(qiáng)相同,則可以寫一個(gè)通用的代理類,實(shí)現(xiàn)和寫法一相同。
寫法三:利用反射的思想,代理類持有真實(shí)類對(duì)象屬性,并利用反射拿到方法,重寫的時(shí)候用invoke(),并添加方法增強(qiáng)。
寫法四(進(jìn)階):利用接口(這里自己寫的是MethodInvocationHandler)定義重寫的方法,在代理類中創(chuàng)建接口的實(shí)現(xiàn)類(匿名內(nèi)部類),并通過全參構(gòu)造讓用戶自己傳入真實(shí)類對(duì)象和接口實(shí)現(xiàn)類(并重寫代理方法),調(diào)用實(shí)現(xiàn)類的方法。
動(dòng)態(tài)代理:自動(dòng)創(chuàng)建代理類源碼,然后完成編譯、加載。 代理類在運(yùn)行時(shí)根據(jù)目標(biāo)對(duì)象和增強(qiáng)邏輯動(dòng)態(tài)生成。 在使用的時(shí)候利用Proxy類的靜態(tài)方法newProxyInstance()創(chuàng)建代理類,并 通過 InvocationHandler 接口的 invoke 方法在運(yùn)行時(shí)攔截方法調(diào)用,執(zhí)行增強(qiáng)邏輯。
RESTful風(fēng)格
RESTful風(fēng)格不是標(biāo)準(zhǔn),但是在企業(yè)中經(jīng)常使用RESTful風(fēng)格來完成功能的開發(fā)。
REST = Representational State Transfer(表屬性狀態(tài)轉(zhuǎn)移)
簡單來說就是在編寫Servlet的時(shí)候重寫doGet,doPost,doPut,doDelete實(shí)現(xiàn)增查改刪功能。
Spring IOC
Spring簡介
Spring 是目前主流的 Java 開發(fā)框架,是 Java 世界最為成功的框架。其目的是用于簡化企業(yè)級(jí)應(yīng)用程序開發(fā)的難度和周期,任何 Java 應(yīng)用都可以從 Spring 中受益。Spring 框架還是一個(gè)超級(jí)粘合平臺(tái),除了自己提供功能外,還提供粘合其他技術(shù)和框架的能力。
什么是框架? 框架是一個(gè)半成品,提供了基本的運(yùn)行功能,但具體業(yè)務(wù)實(shí)現(xiàn)需要我們?nèi)ゾ帉憽?/p>
Spring體系結(jié)構(gòu)
IOC概念:
IOC全稱為 Inverse Of Control,表示控制反轉(zhuǎn)。指的是程序員使用硬編碼創(chuàng)建的對(duì)象轉(zhuǎn)為由Spring容器來創(chuàng)建,對(duì)于對(duì)象生命周期的控制交給Spring容器來管理。控制反轉(zhuǎn)解決了具有依賴關(guān)系的組件之間的強(qiáng)耦合,使得項(xiàng)目形態(tài)更加穩(wěn)健
依賴注入
DI全稱為Dependency Injection,表示依賴注入。指的是在Spring創(chuàng)建對(duì)象的同時(shí),為其屬性賦值
設(shè)值注入:
創(chuàng)建一個(gè)類:
@Data public class Student {private String name;private String sex;private int age;private Date birthday; }
在xml配置文件中利用設(shè)值注入創(chuàng)建對(duì)象:
常見數(shù)據(jù)類型:
<!--application.xml--> <bean name="stu" class="com.qf.spring.ioc.model.Student"><property name="name" value="張三" /><property name="age" value="20" /><property name="sex" value="男" /><!--這里需要注意:日期類型的默認(rèn)格式y(tǒng)yyy/MM/dd--><property name="birthday" value="2021/10/10" /> </bean>
使用:
@Test public void studentTest(){//應(yīng)用上下文使用的是類路徑下XML文檔作為當(dāng)前應(yīng)用上下文ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");//從上下文中根據(jù)bean的名稱或者ID獲取bean對(duì)象Student stu = context.getBean("stu", Student.class);System.out.println(stu); }
注意:這里的name雖然是user,但是并不是指的user這個(gè)類,而是指向set方法!(理解為去掉set后的名字)設(shè)值注入本質(zhì)就是通過set方法為屬性注入值。設(shè)值注入必須保證存在無參構(gòu)造,否則將報(bào)錯(cuò)。
注入數(shù)組類型:
spring 提供了 array 標(biāo)簽來進(jìn)行數(shù)組類型的屬性值的注入。
@Data public class Clazz {private int id;private String name;private Student[] students; }
<bean name="clazz" class="com.qf.spring.ioc.model.Clazz"><property name="id" value="1" /><property name="name" value="張三" /><property name="students"><array><!--引用數(shù)據(jù)類型 可以使用bean標(biāo)簽創(chuàng)建bean對(duì)象注入值--><!--<bean class=""></bean>--><!--引用數(shù)據(jù)類型 可以使用ref標(biāo)簽引用bean對(duì)象注入值--><ref bean="s" /><ref bean="stu" /><!--常用數(shù)據(jù)類型 可以使用value標(biāo)簽直接注入值--><!-- <value></value>--></array></property> </bean>
注入集合類型:
List集合
@Data @AllArgsConstructor @NoArgsConstructor public class Student {private String name;private String sex;private int age;private Date birthday;private List<Double> scores; }
<bean name="stu" class="com.qf.spring.ioc.model.Student"><property name="name" value="張三" /><property name="age" value="20" /><property name="sex" value="男" /><!--這里需要注意:日期類型的默認(rèn)格式y(tǒng)yyy/MM/dd--><property name="birthday" value="2021/10/10" /><property name="scores"><list><value>80.0</value><value>90.0</value><value>81.0</value><value>82.0</value></list></property> </bean><bean name="s" class="com.qf.spring.ioc.model.Student"><!--這里按照順序?yàn)閷傩宰⑷胫?-><constructor-arg index="0" value="李四" /><constructor-arg index="1" value="女" /><constructor-arg index="2" value="22" /><constructor-arg index="3" value="2020/05/05" /><constructor-arg index="4"><list><value>80.0</value><value>90.0</value><value>81.0</value><value>82.0</value></list></constructor-arg> </bean>
Set集合
@Data public class Person {private String name;private Set<String> friendNames; }
<bean name="p" class="com.qf.spring.ioc.model.Person"><property name="name" value="李剛" /><property name="friendNames"><set><value>李四</value><value>王五</value></set></property> </bean>
注入Map
@Data public class Person {private String name;private List<String> friendNames;private Map<String, Object> map; }
<bean name="p" class="com.qf.spring.ioc.model.Person"><property name="name" value="李剛" /><property name="friendNames"><set><value>李四</value><value>王五</value></set></property><property name="map"><map><entry key="hobby" value="聊天" /><entry key="clazz" value-ref="clazz"/></map></property><property name="props"><props><prop key="desc">我很帥</prop><prop key="secret">我有兩個(gè)女朋友</prop></props></property> </bean>
注入Properties
@Data public class Person {private String name;private List<String> friendNames;private Properties props; }
<bean name="p" class="com.qf.spring.ioc.model.Person"><property name="name" value="李剛" /><property name="friendNames"><set><value>李四</value><value>王五</value></set></property><property name="props"><props><prop key="desc">我很帥</prop><prop key="secret">我有兩個(gè)女朋友</prop></props></property> </bean>
構(gòu)造注入
構(gòu)造注入指的是通過構(gòu)造放入為屬性注入值。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {private String name;private String sex;private int age;private Date birthday;
}
<!--application.xml-->
<bean name="s" class="com.qf.spring.ioc.model.Student"><!--這里按照順序?yàn)閷傩宰⑷胫?-><constructor-arg index="0" value="李四" /><constructor-arg index="1" value="女" /><constructor-arg index="2" value="22" /><constructor-arg index="3" value="2020/05/05" />
</bean>
@Test
public void studentConstructorTest(){//應(yīng)用上下文使用的是類路徑下XML文檔作為當(dāng)前應(yīng)用上下文ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");//從上下文中根據(jù)bean的名稱或者ID獲取bean對(duì)象Student stu = context.getBean("s", Student.class);System.out.println(stu);
}