设计模式 | 五、委派模式(任务调度和分配)[DelegatePattern]

委派模式

源码:https://github.com/GiraffePeng/design-patterns

1、定义

委派模式不属于 GOF23 种设计模式中。委派模式的基本作用就是负责任务的调用和分配任务,比如nginx的路径转发,比如spring Cloud中的网关 zuul、gateway等根据路径分发至具体的服务进行调用。委派模式在 Spring 中应用非常多,大家常用的 DispatcherServlet 其实就是用到了委派模式。

2、实现

2.1、模拟网关分发

下面我们模拟网关层的服务分发调用。
创建服务顶层接口,树立规范

//服务顶层接口
public interface IService {

	public void doSomething();
}

创建具体的两个服务,订单服务和会员服务。

public class MemberService implements IService{

	@Override
	public void doSomething() {
		System.out.println("调用会员服务");
	}
}
public class OrderService implements IService{

	@Override
	public void doSomething() {
		System.out.println("调用订单服务");
	}
}

创建网关层,负责服务的分发

public class ServiceDelegate{
	
	public static void route(String serviceKey) {
		if(serviceKey.equals("member")) {
			new MemberService().doSomething();
		}else if(serviceKey.equals("order")){
			new OrderService().doSomething();
		}else {
			throw new RuntimeException("404 not found ");
		}
	}
}

创建测试类

public class Cilent {

	public static void main(String[] args) {
		ServiceDelegate.route("member");
	}
}

控制台打印

调用会员服务

可以看到上述的网关层使用的为if…else的逻辑去分发请求,我们还可以进行改造,使用容器式服务注册与分发。

public class ServiceDelegate{
	
	private static Map<String,IService> serviceMap = new ConcurrentHashMap<String,IService>(){
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		{
			put("member", new MemberService());
			put("order", new OrderService());
		}
	};
	
	public static void route(String serviceKey) {
		IService iService = serviceMap.get(serviceKey);
		if(iService != null) {
			iService.doSomething();
		}else {
			throw new RuntimeException("404 not found ");
		}
	}
}

上述代码将服务和其key值存放在一个map中进行保存,调用时传入key值去获取对应的服务,像不像一个极简的Spring Cloud中各个服务以spring.application.name为key值,往eureka注册,然后网关通过注册的列表去找到相应的服务,只不过调用的方法是固定的,有兴趣的可以去优化下上述代码。

2.2、模拟spring的dispatcherServlet

创建登陆使用的Controller–>LoginController

public class LoginController {

	public void login(String username,String password) {
		System.out.println("登陆,账号:"+username+" 密码:"+password);
	}
}

创建用户的controller–>MemberController

public class MemberController {

	public void getMemberById(Long mid) {
		System.out.println("根据会员id查询会员信息");
	}
}

创建订单的controller–>OrderController

public class OrderController {

	public void getOrderById(Long oid) {
		System.out.println("根据订单id查询订单信息");
	}
}

然后创建DispatcherServlet来模拟Spring的dispatcherservlet

public class DispatcherServlet extends HttpServlet{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	

	protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
		this.route(req,resp);
	}

	private void route(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		String requestURI = req.getRequestURI();
		//获取用户请求url,根据url去分配至不同的controller处理请求
		if(requestURI.equals("login")) {
			new LoginController().login(req.getParameter("username"), req.getParameter("password"));
		}else if(requestURI.equals("getMemberById")) {
			new MemberController().getMemberById(Long.parseLong(req.getParameter("mid")));
		}else if(requestURI.equals("getOrderById")) {
			new OrderController().getOrderById(Long.parseLong(req.getParameter("oid")));
		}else {
			resp.getWriter().write("404 not found");
		}
	}
}

最后在web.xml中配置dispatcherservlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/j2ee"
	xmlns:javaee="http://java.sun.com/xml/ns/javaee"
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
	version="2.4">
	<servlet>
		<servlet-name>delegateServlet</servlet-name>
		<servlet-class>com.peng.dispatcher.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>delegateServlet</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>

上述的DispatcherServlet我们还可以进行优化,从而减少if、else的使用。(这样的代码扩展性不太优雅,也不现实,因为我们实际项目中一定不止这几个 Controller,往往是成千上万个 Controller)同样,我们定义一个容器来保存所有的controller层信息,然后通过用户的url去匹配controller层的方法路径达到动态调用。
先创建自定义注解类,模拟RequestMapping,然后在三个controller上的对应的方法上加入该注解,声明方法的请求路径

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {

	public String value();
}

优化后的DispatcherServlet,通过list容器保存controller的对应的实例、方法、请求路径之间的关系,当用户请求时,循环容器,找到对应的方法,运用反射执行对应的方法。

public class DispatcherServlet extends HttpServlet{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private List<Handler> handlers = new ArrayList<Handler>();
	
	public void init() throws ServletException {
		//这里获取每个controller的class类,通过反射获取其所有的方法以及自定义注解(requestMapping)上value值
		setHandler(MemberController.class);
		setHandler(OrderController.class);
		setHandler(LoginController.class);
	}
	
	protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
		this.route(req,resp);
	}
	
	//放入容器list中
	private void setHandler(Class<?> clazz) {
		Map<String, Method> methodMappingLogin = new ConcurrentHashMap<String, Method>();
		for (Method method : clazz.getMethods()) {
			if(method.isAnnotationPresent(RequestMapping.class)) {
				RequestMapping annotation = method.getAnnotation(RequestMapping.class);
				methodMappingLogin.put(annotation.value(), method);
			}
		}
		try {
			handlers.add(new Handler(methodMappingLogin, clazz.newInstance()));
		} catch (InstantiationException | IllegalAccessException e) {
			e.printStackTrace();
		}
	}

	private void route(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		String requestURI = req.getRequestURI();
		Map parameterMap = req.getParameterMap();
		//通过用户请求的url去匹配对应的handler
		Handler handlerInstance = null;
		for (Handler handler : handlers) {
			if(handler.getMethodMapping().containsKey(requestURI)) {
				handlerInstance = handler;
				break;
			}
		}
		if(handlerInstance == null) {
			throw new RuntimeException("404 not found");
		}
		//从handler中取出对应的方法进行反射执行
		Object object = null;
		try {
			object = handlerInstance.getMethodMapping().get(requestURI).invoke(handlerInstance.getController(), parameterMap);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			e.printStackTrace();
		}
	}
	
	class Handler{
		//定义handler容器类, 将controller传入,将requestMapping与method做关联存入map中
		private Map<String,Method> methodMapping = new ConcurrentHashMap<String,Method>();
		
		private Object controller;

		public Map<String, Method> getMethodMapping() {
			return methodMapping;
		}

		public void setMethodMapping(Map<String, Method> methodMapping) {
			this.methodMapping = methodMapping;
		}

		public Object getController() {
			return controller;
		}

		public void setController(Object controller) {
			this.controller = controller;
		}

		public Handler(Map<String, Method> methodMapping, Object controller) {
			super();
			this.methodMapping = methodMapping;
			this.controller = controller;
		}

		public Handler() {
			super();
		}
	}
}

上述代码只是很粗略的模拟了spring中的DispatcherServlet对于请求的转发,重点还是突出委派模式的应用场景。
在 Spring 源码中,只要以 Delegate 结尾的都是实现了委派模式。例如:BeanDefinitionParserDelegate 根据不同类型委派不同的逻辑解析 BeanDefinition。

3、总结

通过上述的两个例子,希望大家能够理解委派模式强调的是对任务的分发和调度,属于行为型设计模式。

发布了21 篇原创文章 · 获赞 2 · 访问量 7496

猜你喜欢

转载自blog.csdn.net/qq_35551089/article/details/100204143
今日推荐