Day49
代理模式proxy
概念: 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式,即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
代理模式分为静态代理和动态代理两种 。
静态代理
思路:对于同一个接口,代理类和被代理类都实现了这个接口,代理类中将被代理类对象持有为自己的属性,这样在使用方法的时候就可以在被代理类的方法前后加上增强,即自己的方法逻辑。
以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; } @Override public void speak() { System.out.println("增强处理"); speaker.speak(); System.out.println("增强处理"); } }
可以看出,这种写法每代理一个真实类就需要写一个代理类,对于AmericaSpeaker,同样要写一个AmericaSpeakerProxy。如果对于功能增强的内容完全相同,就可以使用一个对于Speaker接口通用的代理类CommonSpeakerProxy,利用多态完成代理。
public class CommonSpeakerProxy implements Speaker { private Speaker speaker; public CommonSpeakerProxy(Speaker speaker) { this.speaker = speaker; } @Override public void speak() { System.out.println("前置功能增强"); speaker.speak(); System.out.println("后置功能增强"); } }
除了这种写法外,还可以利用反射的思想来写,利用多态通过接口实现类拿到方法,通过有参传入的类对象,用method.invoke()方法完成代理。
假设这时再添加了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(); } } @Override public void sell() { try { System.out.println("前置功能增强"); method.invoke(seller); System.out.println("后置功能增强"); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } }
可以看出,静态代理每实现一个真实类的代理就需要写一个代理类,如果代理的功能不同,就需要针对代理功能编写多个类,十分复杂。那么有没有一种方式使得在使用到代理的时候再去编写代理的逻辑功能,而不是每次都去多写一个类呢?这就是动态代理的思想。
动态代理
动态代理又根据代理对象进行划分:
为接口做代理:JDK动态代理
为类做代理:CGLIB动态代理
JDK动态代理
由于动态代理和静态代理差别较大,这里从静态代理开始进阶优化,直到达到动态代理的范畴。
**静态代理进阶:**思路:写一个接口,接口中定义了代理重写的方法,在代理类中以匿名内部类的方式创建一个类对象作为自己持有的属性,并用全参构造要求使用时创建这个匿名内部类(即重写代理方法内容),而代理类中就只需要调用接口的实现类的方法就行,不需要再写明方法逻辑。
这个接口是jdk自带的接口,在这里自己写一遍,以更好地明白逻辑:
MethodInvocationHandler接口:
public interface MethodInvocationHandler { Object handle(Object target, Method method,Object[] args) throws Exception; }
注意:这个方法的本质是反射,利用method.invoke()方法进行调用真实类的方法,再加上代理类的方法,因此参数为method.invoke()的参数。
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; } //如果有多个需要代理的方法就都要进行重写 @Override public void speak() { try { handler.handle(speaker,method,null);//由于真实类中的方法是无参的,所以这里的参数数组为空 } 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() { @Override public Object handle(Object target, Method method, Object[] args) throws Exception { System.out.println("功能增强"); method.invoke(target,args); System.out.println("功能增强"); return null; } }); chineseSpeakerProxy.speak(); }
至此,对于同一个代理类的多个功能,实现了让用户自己写增强方法的目的。但是对于一个真实类,如果要实现其代理,那还是要写一个代理类,如果真实类很多,那就需要写很多的代理类,代理繁多的问题依然存在。
如果这些代理类能够使用代码来生成,然后再编译,再加载至
JVM
中,那么再多的代理也就不是问题了。动态代理:
手动写一个能够自动创建代理类源码的类,然后手动完成编译、加载的过程。(这些功能jdk的接口都实现了,这里只手写一个自动创建代理类源码的类以便深刻理解)
因此,可以手动写一个能够自动创建代理类源码的类,然后手动完成编译、加载的过程。(这些功能jdk的接口都实现了,这里只手写一个自动创建代理类源码的类以便深刻理解)
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); // } }
这个类完成了自动创建类源码的功能,其实现的思路就是将一个代理类中的特定类利用反射和object去替换,然后将整个类写成字符串放进StringBuilder中。
然后是编译代理类源文件、加载编译好的代理类(利用类加载器)、编写创建代理实例的方法。这些底层就不手写了。
接下来对比一下两种写法的区别(这里都实现jdk自带的InvocationHandler接口,接口内容和上面手写的MethodInvocationHandler一致,只是方法名字为invoke,我写的是handle):
手动创建ChineseSpeakerProxy(此时仍然为静态代理,因为代理类是在编译时明确定义的,并且代理类的代码是手动编写的。相对于动态代理,静态代理类在运行时不会自动生成,而是在编译时就已经存在。 ):
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; } @Override public 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() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { method.invoke(proxy,args); System.out.println("方法增强"); return null; } }); chineseSpeakerProxy.speak(); }
使用jdk自带的Proxy类静态方法创建(动态代理):
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() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法增强"); method.invoke(chineseSpeaker,args); return null; } }); ChineseSpeakerProxy.speak(); }
注意:使用Proxy类实现动态代理的时候,method.invoke()的第一个参数不是形参给的,而是被代理的类对象! 因为使用 Proxy.newProxyInstance动态生成的代理类会把代理实例本身传递给 InvocationHandler.invoke方法的 proxy 参数。因此,如果在 invoke 方法中再次调用 method.invoke(proxy, args),就会导致递归调用形成死循环。
总结
静态代理:
写法一:真实类和代理类实现同一个接口,代理类持有真实类对象作为属性,并在重写接口方法的时候调用其方法,再添加方法增强。
写法二:如果多个真实类需要进行的方法增强相同,则可以写一个通用的代理类,实现和写法一相同。
写法三:利用反射的思想,代理类持有真实类对象属性,并利用反射拿到方法,重写的时候用invoke(),并添加方法增强。
写法四(进阶):利用接口(这里自己写的是MethodInvocationHandler)定义重写的方法,在代理类中创建接口的实现类(匿名内部类),并通过全参构造让用户自己传入真实类对象和接口实现类(并重写代理方法),调用实现类的方法。
动态代理:自动创建代理类源码,然后完成编译、加载。 代理类在运行时根据目标对象和增强逻辑动态生成。 在使用的时候利用Proxy类的静态方法newProxyInstance()创建代理类,并 通过 InvocationHandler 接口的 invoke 方法在运行时拦截方法调用,执行增强逻辑。
RESTful风格
RESTful风格不是标准,但是在企业中经常使用RESTful风格来完成功能的开发。
REST = Representational State Transfer(表属性状态转移)
简单来说就是在编写Servlet的时候重写doGet,doPost,doPut,doDelete实现增查改删功能。
Spring IOC
Spring简介
Spring 是目前主流的 Java 开发框架,是 Java 世界最为成功的框架。其目的是用于简化企业级应用程序开发的难度和周期,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。
什么是框架? 框架是一个半成品,提供了基本的运行功能,但具体业务实现需要我们去编写。
Spring体系结构
IOC概念:
IOC全称为 Inverse Of Control,表示控制反转。指的是程序员使用硬编码创建的对象转为由Spring容器来创建,对于对象生命周期的控制交给Spring容器来管理。控制反转解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健
依赖注入
DI全称为Dependency Injection,表示依赖注入。指的是在Spring创建对象的同时,为其属性赋值
设值注入:
创建一个类:
@Data public class Student { private String name; private String sex; private int age; private Date birthday; }
在xml配置文件中利用设值注入创建对象:
常见数据类型:
<!--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="男" /> <!--这里需要注意:日期类型的默认格式yyyy/MM/dd--> <property name="birthday" value="2021/10/10" /> </bean>
使用:
@Test public void studentTest(){ //应用上下文使用的是类路径下XML文档作为当前应用上下文 ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); //从上下文中根据bean的名称或者ID获取bean对象 Student stu = context.getBean("stu", Student.class); System.out.println(stu); }
注意:这里的name虽然是user,但是并不是指的user这个类,而是指向set方法!(理解为去掉set后的名字)设值注入本质就是通过set方法为属性注入值。设值注入必须保证存在无参构造,否则将报错。
注入数组类型:
spring 提供了 array 标签来进行数组类型的属性值的注入。
@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> <!--引用数据类型 可以使用bean标签创建bean对象注入值--> <!--<bean class=""></bean>--> <!--引用数据类型 可以使用ref标签引用bean对象注入值--> <ref bean="s" /> <ref bean="stu" /> <!--常用数据类型 可以使用value标签直接注入值--> <!-- <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="男" /> <!--这里需要注意:日期类型的默认格式yyyy/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"> <!--这里按照顺序为属性注入值--> <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">我有两个女朋友</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">我有两个女朋友</prop> </props> </property> </bean>
构造注入
构造注入指的是通过构造放入为属性注入值。
@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">
<!--这里按照顺序为属性注入值-->
<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(){
//应用上下文使用的是类路径下XML文档作为当前应用上下文
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
//从上下文中根据bean的名称或者ID获取bean对象
Student stu = context.getBean("s", Student.class);
System.out.println(stu);
}