代理模式
概述
问题:
- 代理模式是什么?
- 使用代理模式有什么好处?
- 如何使用代理模式?
代理模式:为一个对象(即是目标对象)提供一个替身(即是代理对象),来控制对该对象的访问。即通过代理对象来访问目标对象。
这样做的好处是在目标对象实现的基础上,增强额外的操作,即扩展了目标对象的功能。
被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。
代理模式有不同的形式,主要有三种:静态代理、动态代理(JDK代理、接口代理)和cglib代理。
静态代理
静态代理的基本介绍
静态代理在使用时,需要定义接口或父类,被代理对象(目标对象)与代理对象一起实现相同的接口或者继承相同的父类。
应用实例
具体要求:
- (1)定义一个TeacherDaoInterface接口,该类定义了方法名。
- (2)目标对象TeacherDao类实现接口TeacherDaoInterface。
- (3)使用静态代理方式,需要在代理对象TeacherDaoProxy中也实现TeacherDaoInterface中的方法,并可以对方法进行一些增强。(注:在该代理对象类中要调用目标对象的方法,所以可以通过构造器传参数来实现)
- (4)在客户端类或测试类中调用的时候,通过调用代理对象的方法来调用目标对象。(注:实例化代理对象类,来调用该对象的方法)
(注:代理对象与目标对象的方法要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。)
接口类TeacherDaoInterface.java
/**
* 接口,定义方法,让目标对象与代理对象实现该接口的方法
*/
public interface TeacherDaoInterface {
void teach(); // 该接口有一个teach()方法,表示授课
}
实现接口的目标对象类TeacherDao.java
/**
* 目标对象,实现TeacherDaoInterface接口的所有方法
*/
public class TeacherDao implements TeacherDaoInterface {
@Override
public void teach() {
System.out.println(" 老师授课中 。。。。。");
}
}
实现接口的代理对象类TeacherDaoProxy.java
/**
* 代理对象,实现TeacherDaoInterface接口的所有方法,并对代理对象的方法进行一定程度上增强
*/
public class TeacherDaoProxy implements TeacherDaoInterface {
private TeacherDaoInterface target; // 目标对象,通过接口来聚合
//构造器
public TeacherDaoProxy(TeacherDaoInterface target) {
this.target = target;
}
// 增强方法的功能
// 但需要调用目标对象TeacherDao.java中的teach()方法,该如何调用呢?就是通过TeacherDaoInterface.java来作中间媒介进行调用
@Override
public void teach() {
System.out.println("开始代理,完成某些操作。。。。。 ");//也可以调用些方法
target.teach();
System.out.println("提交。。。。。");//也可以调用些方法
}
}
测试类Test.java来测试功能的实现
public class Test {
public static void main(String[] args) {
//创建目标对象(被代理对象)
TeacherDao teacherDao = new TeacherDao();
//创建代理对象, 同时将被代理对象传递给代理对象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
//通过代理对象,调用到被代理对象的方法
//即:执行的是代理对象的方法,代理对象再去调用目标对象的方法
teacherDaoProxy.teach();
}
}
/**
* 打印结果:
* 开始代理,完成某些操作。。。。。
* 老师授课中 。。。。。
* 提交。。。。。
*/
URL图
静态代理的使用总结:
- 第一步:创建一个接口类,定义要实现的方法。
- 第二步:创建目标对象类,实现接口中的方法。
- 第三步:创建代理对象类,也实现接口中的方法,不过要增强目标对象的方法因此需要调用目标对象的方法,所以使用构造器传入参数。
- 第四步:创建测试类,实例化代理对象来调用目标对象的方法。
静态代理的优缺点
优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展。
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。并且一旦接口增加方法,目标对象与代理对象类都要维护。
动态代理
动态代理的基本介绍
代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。
代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象。
动态代理也叫做:JDK 代理、接口代理。
JDK 实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
应用实例
将上面的需求改为动态代理。
接口类TeacherInterface.java
/**
* 接口,定义方法,让目标对象与代理对象实现该接口的方法
*/
public interface TeacherInterface {
/**
* 教师授课
*/
void teach();
/**
* 教师让某一位学生回答问题
*
* @param name 学生名字
* @return 学生回答的内容
*/
String ask(String name);
}
实现接口的目标类对象TeacherTarget.java
/**
* 目标对象,实现接口方法
*/
public class TeacherTarget implements TeacherInterface {
@Override
public void teach() {
System.out.println("教师授课中。。。。。。");
}
@Override
public String ask(String name) {
System.out.println("教师请[" + name + "]起来回答问题。。。。。。");
return "回答一";
}
}
生成代理对象的类TeacherProxyFactory.java
/**
* 工厂类,可以不断动态生成代理对象
*/
public class TeacherProxyFactory {
// 维护一个目标对象Object
private Object target;
// 但要调用该对象的方法,所以需要通过构造器传入参数
public TeacherProxyFactory(Object target) {
this.target = target;
}
// 给目标对象,生成一个代理对象
// 调用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成代理对象
// 参数说明:
// ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
// Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
// InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入
// 这三个参数是固定写法,简述如下:
// 1. 类加载器:目标对象.getClass().getClassLoader()
// 2. 接口数组:目标对象.getClass().getInterfaces()
// 3. 处理器:new InvocationHandler()
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
/*
代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
参数:
1. proxy:代理对象
2. method:代理对象调用的方法,被封装为的对象,这里即是TeacherInterface.java中的方法。
3. args:代理对象调用的方法时,传递的实际参数,这里即是ask(name)方法中的name参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// System.out.println(method.getName()); // method.getName() 可以被调用方法的方法名,例如:ask、teach等
// System.out.println(args[0]);// 即被调用方法中的参数
// 判断是否是ask方法被调用了
if (method.getName().equals("ask")) {
// 1.可以增强方法参数
String name = (String) args[0];
String newName = name + ", 王五, 赵六";
// 2.反射机制调用目标对象的方法
System.out.println("JDK代理开始。。。");
String returnValue = (String) method.invoke(target, newName);// 调用方法后的返回结果,这里是调用ask()方法后返回的值
System.out.println("JDK代理提交。。。");
// 3.可以增强返回值
return returnValue + ";补充回答二。";
}
// 如果不是ask方法而是其他方法,则调用对应目标对象的方法
Object returnValue = method.invoke(target, args);
return returnValue;
}
});
}
}
测试类Test.java
public class Test {
public static void main(String[] args) {
// 创建目标对象
TeacherInterface target = new TeacherTarget();
// 给目标对象创建代理对象
TeacherInterface proxy = (TeacherInterface) new TeacherProxyFactory(target).getProxyInstance();
// proxy.teach();
// 通过代理对象,调用目标对象的方法
String result = proxy.ask("李四");
System.out.println(result);
}
}
/**
* 打印结果:
* 教师请[李四, 王五, 赵六]起来回答问题。。。。。。
* 回答一;补充回答二。
*/
UML图
动态代理总结:
- 第一步:创建接口,写需要实现的方法名。
- 第二步:创建目标类对象,实现接口中的方法。
- 第三步:创建能够动态生成代理对象类,通过构造器能够传入目标对象,写一个getProxyInstance()方法,返回值为Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成代理对象。
- 第四步:利用代理对象生成类来生成代理对象,然后调用目标对象的方法进行测试。
cglib代理
cglib代理的基本介绍
- 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理——这就是 Cglib 代理
- Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib 代理归属到动态代理。
- Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截。
- 在 AOP 编程中如何选择代理模式:
- 目标对象需要实现接口,用 JDK 代理
- 目标对象不需要实现接口,用 Cglib 代理
- Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类。
cglib代理的使用注意事项
- 需要引入cglib的jar包。
- 注意代理的类不能为final修饰。
- 目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
应用实例
将上面的需求改为cglib代理。
目标对象类TeacherTarget.java
/**
* 目标对象,实现接口方法
*/
public class TeacherTarget implements TeacherInterface {
@Override
public void teach() {
System.out.println("教师授课中。。。。。。");
}
@Override
public String ask(String name) {
System.out.println("教师请[" + name + "]起来回答问题。。。。。。");
return "回答一";
}
}
生成代理对象类TeacherProxyFactory.java
public class TeacherProxyFactory implements MethodInterceptor {
private Object target;// 该对象是目标类对象
//构造器,传入一个被代理的对象
public TeacherProxyFactory(Object target){
this.target=target;
}
//返回一个代理对象: 是target对象的代理对象
public Object getProxyInstance() {
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
/**
* 重写intercept方法,会调用目标对象的方法
* @param obj 指被代理的对象
* @param method 指被调用的方法
* @param args 该方法调用时所需要的参数
* @param methodProxy JDK的java.lang.reflect.Method类的代理类,可以实现对源对象方法的调用
* @return 返回该方法被调用的返回值
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws InvocationTargetException, IllegalAccessException {
if(method.getName().equals("ask")){
// 1.参数增强
String name= (String) args[0];
String newName=name+",李四,王五";
// 2.调用目标对象的方法,通过反射机制,返回的是该方法的返回值
System.out.println("cglib代理开始。。。");
Object returnValue = method.invoke(target, newName);
System.out.println("cglib代理提交。。。");
// 3.返回值增强
returnValue+="补充答案二。";
return returnValue;
}
return method.invoke(target,args);
}
}
测试类Test.java
public class Test {
public static void main(String[] args) {
// 创建目标对象
TeacherTarget target = new TeacherTarget();
// 给目标对象创建代理对象
TeacherTarget proxy = (TeacherTarget) new TeacherProxyFactory(target).getProxyInstance();
// proxy.teach();
// 通过代理对象,调用目标对象的方法
String result = proxy.ask("李四");// 调用方法
System.out.println(result);// 打印返回值
}
}
/**
* 打印结果:
* cglib代理开始。。。
* 教师请[李四,李四,王五]起来回答问题。。。。。。
* cglib代理提交。。。
* 回答一补充答案二。
*/
UML图
cglib代理总结:
- 第一步:创建目标对象类,是要被代理的对象。
- 第二步:创建生成代理对象类,该类用来生成代理对象,并且重写intercept()方法,是对被代理方法的增强。
- 第三步:测试使用代理对象,通过代理对象调用目标对象的方法。
实例
需求:过滤敏感词。
动态代理实现
DemoServlet.java
@WebServlet("/demo")
public class DemoServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
String msg = req.getParameter("msg");
System.out.println(msg);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
}
过滤器类SensitiveWordsFilter.java
/**
* 敏感词汇过滤器
*/
@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {
private List<String> list = new ArrayList<String>();//敏感词汇集合
@Override
public void init(FilterConfig filterConfig) throws ServletException {
list.addAll(Arrays.asList("坏蛋", "混蛋", "傻瓜", "蠢货"));
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
ServletRequest req=(ServletRequest)Proxy.newProxyInstance(request.getClass().getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强getParameter方法
// 判断是否是getParameter方法
if(method.getName().equals("getParameter")){
//增强返回值
//获取返回值
String returnVal = (String)method.invoke(request, args);
if(returnVal!=null){
for (String s : list) {
if(returnVal.contains(s)){
returnVal=returnVal.replaceAll(s,"***");
}
}
}
return returnVal;
}
return method.invoke(request,args);
}
});
}
@Override
public void destroy() {
}
}
浏览器输入地址:http://localhost:8080/demo?msg=你是个大傻瓜
控制台输出
静态代理实现
还可以使用静态代理实现,即创建一个代理对象类实现HttpServletRequest接口中的方法,需要实现该接口的所有方法,所以代码较多,不推荐。
public class ServletRequestProxy implements HttpServletRequest {
private HttpServletRequest target;
public ServletRequestProxy(HttpServletRequest target) {
this.target = target;
}
@Override
public String getAuthType() {
return target.getAuthType();
}
@Override
public Cookie[] getCookies() {
return target.getCookies();
}
@Override
public long getDateHeader(String s) {
return target.getDateHeader(s);
}
@Override
public String getHeader(String s) {
return target.getHeader(s);
}
@Override
public Enumeration<String> getHeaders(String s) {
return target.getHeaders(s);
}
@Override
public Enumeration<String> getHeaderNames() {
return target.getHeaderNames();
}
@Override
public int getIntHeader(String s) {
return target.getIntHeader(s);
}
@Override
public String getMethod() {
return target.getMethod();
}
@Override
public String getPathInfo() {
return target.getPathInfo();
}
@Override
public String getPathTranslated() {
return target.getPathTranslated();
}
@Override
public String getContextPath() {
return target.getContextPath();
}
@Override
public String getQueryString() {
return target.getQueryString();
}
@Override
public String getRemoteUser() {
return target.getRemoteUser();
}
@Override
public boolean isUserInRole(String s) {
return target.isUserInRole(s);
}
@Override
public Principal getUserPrincipal() {
return target.getUserPrincipal();
}
@Override
public String getRequestedSessionId() {
return target.getRequestedSessionId();
}
@Override
public String getRequestURI() {
return target.getRequestURI();
}
@Override
public StringBuffer getRequestURL() {
return target.getRequestURL();
}
@Override
public String getServletPath() {
return target.getServletPath();
}
@Override
public HttpSession getSession(boolean b) {
return target.getSession(b);
}
@Override
public HttpSession getSession() {
return target.getSession();
}
@Override
public String changeSessionId() {
return target.changeSessionId();
}
@Override
public boolean isRequestedSessionIdValid() {
return target.isRequestedSessionIdValid();
}
@Override
public boolean isRequestedSessionIdFromCookie() {
return target.isRequestedSessionIdFromCookie();
}
@Override
public boolean isRequestedSessionIdFromURL() {
return target.isRequestedSessionIdFromURL();
}
@Override
public boolean isRequestedSessionIdFromUrl() {
return target.isRequestedSessionIdFromUrl();
}
@Override
public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException {
return target.authenticate(httpServletResponse);
}
@Override
public void login(String s, String s1) throws ServletException {
target.login(s, s1);
}
@Override
public void logout() throws ServletException {
target.logout();
}
@Override
public Collection<Part> getParts() throws IOException, ServletException {
return target.getParts();
}
@Override
public Part getPart(String s) throws IOException, ServletException {
return target.getPart(s);
}
@Override
public <T extends HttpUpgradeHandler> T upgrade(Class<T> aClass) throws IOException, ServletException {
return target.upgrade(aClass);
}
@Override
public Object getAttribute(String s) {
return target.getAttribute(s);
}
@Override
public Enumeration<String> getAttributeNames() {
return target.getAttributeNames();
}
@Override
public String getCharacterEncoding() {
return target.getCharacterEncoding();
}
@Override
public void setCharacterEncoding(String s) throws UnsupportedEncodingException {
target.setCharacterEncoding(s);
}
@Override
public int getContentLength() {
return target.getContentLength();
}
@Override
public long getContentLengthLong() {
return target.getContentLengthLong();
}
@Override
public String getContentType() {
return target.getContentType();
}
@Override
public ServletInputStream getInputStream() throws IOException {
return target.getInputStream();
}
@Override
public String getParameter(String s) {
List<String> list = new ArrayList<String>();//敏感词汇集合
list.addAll(Arrays.asList("坏蛋", "混蛋", "傻瓜", "蠢货"));
String returnVal = target.getParameter(s);
if (returnVal != null) {
for (String str : list) {
if (returnVal.contains(str)) {
returnVal = returnVal.replaceAll(str, "***");
}
}
}
return returnVal;
}
@Override
public Enumeration<String> getParameterNames() {
return target.getParameterNames();
}
@Override
public String[] getParameterValues(String s) {
return target.getParameterValues(s);
}
@Override
public Map<String, String[]> getParameterMap() {
return target.getParameterMap();
}
@Override
public String getProtocol() {
return target.getProtocol();
}
@Override
public String getScheme() {
return target.getScheme();
}
@Override
public String getServerName() {
return target.getServerName();
}
@Override
public int getServerPort() {
return target.getServerPort();
}
@Override
public BufferedReader getReader() throws IOException {
return target.getReader();
}
@Override
public String getRemoteAddr() {
return target.getRemoteAddr();
}
@Override
public String getRemoteHost() {
return target.getRemoteHost();
}
@Override
public void setAttribute(String s, Object o) {
target.setAttribute(s, o);
}
@Override
public void removeAttribute(String s) {
target.removeAttribute(s);
}
@Override
public Locale getLocale() {
return target.getLocale();
}
@Override
public Enumeration<Locale> getLocales() {
return target.getLocales();
}
@Override
public boolean isSecure() {
return target.isSecure();
}
@Override
public RequestDispatcher getRequestDispatcher(String s) {
return target.getRequestDispatcher(s);
}
@Override
public String getRealPath(String s) {
return target.getRealPath(s);
}
@Override
public int getRemotePort() {
return target.getRemotePort();
}
@Override
public String getLocalName() {
return target.getLocalName();
}
@Override
public String getLocalAddr() {
return target.getLocalAddr();
}
@Override
public int getLocalPort() {
return target.getLocalPort();
}
@Override
public ServletContext getServletContext() {
return target.getServletContext();
}
@Override
public AsyncContext startAsync() throws IllegalStateException {
return target.startAsync();
}
@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
return target.startAsync(servletRequest, servletResponse);
}
@Override
public boolean isAsyncStarted() {
return target.isAsyncStarted();
}
@Override
public boolean isAsyncSupported() {
return target.isAsyncSupported();
}
@Override
public AsyncContext getAsyncContext() {
return target.getAsyncContext();
}
@Override
public DispatcherType getDispatcherType() {
return target.getDispatcherType();
}
}
而SensitiveWordsFilter.java为如下:
/**
* 敏感词汇过滤器
*/
@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {
private List<String> list = new ArrayList<String>();//敏感词汇集合
@Override
public void init(FilterConfig filterConfig) throws ServletException {
list.addAll(Arrays.asList("坏蛋", "混蛋", "傻瓜", "蠢货"));
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
// 静态代理
ServletRequestProxy requestProxy = new ServletRequestProxy((HttpServletRequest) request);
filterChain.doFilter(requestProxy, response);
}
@Override
public void destroy() {
}
}
效果同样。
至于cglib代理,暂时无法实现。
该实例源码地址:GitHub的Demo