servlet(10)MVC,反射API,注解.SmartMVC 框架开发

本文介绍了SmartMVC框架的开发过程,涉及MVC模式、注解和反射API的使用。通过拆分控制器为前端控制器和子控制器,利用request参数共享数据到JSP,并通过注解映射URL到控制器方法,实现框架的功能。同时,详细阐述了注解的运行时保留、反射API的应用,以及如何利用反射调用方法。
摘要由CSDN通过智能技术生成

## SmartMVC 自定义框架

MVC 模式: 解决用户界面问题的标准模式(套路)!

M Model 模型,封装业务逻辑
V View  视图,代表显示界面
C Controller 控制器,是用于连接整合 M和V

Sun给出web用户界面的建议:

1. 使用Java Bean 作为Model,处理业务逻辑
2. 使用JSP 作为视图,显示数据
3. 使用Servlet作为控制器,整合JSP和JavaBean

一般情况将如上建议封装为框架(工具)减少后续编程量

框架实现步骤:

### 1. 拆分控制器为 前端控制器和子控制器

拆分的目的是将Web编程逻辑业务处理逻辑进行剥离,将Web编程逻辑封装到前端控制器,达到简化子控制器逻辑,复用前端控制器的目的。

原理:

### 2. 利用request参数将控制器中的数据共享到JSP

为了避免用户直接访问JSP视图组件,将JSP保存到 /WEB-INF/jsp 文件夹中。

 

 

 

 

 ### 3. 利用注解将用户请求URL映射到子控制器的方法上

## 注解

1. Java 5 提供了一种代码标注功能。
2. 利用注解可以实现动态代码执行功能。
3. 在软件运行期间动态解析注解动态执行方法。
4. 使用注解:利用反射API解析使用注解。

案例:

@Retention(RUNTIME) //注解一值保留到运行期
	@Target(METHOD) //此注解只能写在方法上
	public @interface RequestMapping {
		String value(); //利用value()可以给注解添加默认参数
	}

标注注解:

public class Controller {
		/**
		 * 子控制器方法
		 * @param request 用于在控制器和JSP直接传递数据
		 * @return 转发的目标JSP页面
		 * @throws Exception
		 */
		@RequestMapping("/list.do") 
		public String execute(
				HttpServletRequest request) 
				throws Exception {
			UserDAO dao = new UserDAO();
			List<User> users = dao.findAll();
			//将users数据传递到JSP
			request.setAttribute("users", users); 
			return "list";
		}
		
		@RequestMapping("/add.do") 
		public String add(HttpServletRequest request) 
				throws Exception {
			return "add";
		}
	}

## 反射API

1. Java 提供的核心API
    1. java.lang.reflect 包中的系列API
2. 提供功能:    
    1. 动态解析对象的类型和内部结构,解析注解
    2. 动态加载类型
    3. 动态创建对象
    4. 动态执行方法 

案例: 解析对象的内部结构

public class Test3 {
	
		public static void main(String[] args) {
			/**
			 * 利用反射检查对象的类型和内部结构
			 */
			test("ABC");
			test(5);
			ArrayList list = new ArrayList();
			Iterator i = list.iterator();
			test(i);
		}
		
		public static void test(Object obj) {
			//obj 引用对象的类型,以及内部结构是什么?
			//利用反射就可以动态检查对象的类型和内部结构
			
			// getClass() 获取当对象的实际类型
			Class cls = obj.getClass();
			System.out.println(cls); 
			//检查类型的内部结构
			// Declared 声明的  Field 字段、属性
			Field[] fields=cls.getDeclaredFields();
			for (Field field : fields) {
				System.out.println(field);			
			}
			//Method: 方法 检查类型中声明的方法
			Method[] methods = cls.getDeclaredMethods();
			for (Method method : methods) {
				System.out.println(method); 
			}
			
		}
	
	}

案例: 利用反射API解析方法的注解

public class Demo {
		
		@RequestMapping("/list.do")
		public String list() {
			return "list";
		}
	
		@RequestMapping("/add.do")
		public String add() {
			return "add";
		}
		
	}
public class Test4 {
	
		public static void main(String[] args) 
			throws Exception{
			/**
			 * 动态解析类方法上标注的注解
			 */
			Scanner in= new Scanner(System.in);
			System.out.print("输入类名:");
			String className=in.nextLine();
	
			//获取Demo的类型信息
			//Class cls = Demo.class;
			
			//forName() 可以动态加载一个类到内存中
			Class cls = Class.forName(className);
			
			//获取类型上全部的方法
			Method[] methods=cls.getDeclaredMethods();
			for (Method method : methods) {
				System.out.println(method);
				//Annotation:注解
				//method.getAnnotations() 获取在方法上
				//标注的注解。此注解必须是 RUNTIME 注解
				Annotation[] anns=method.getAnnotations();
				//方法上只标注了一个注解,获取第一个注解
				Annotation ann = anns[0];
				System.out.println(ann);
				//读取注解上的value值
				if(ann instanceof RequestMapping) {
					RequestMapping rm=(RequestMapping)ann;
					System.out.println(rm.value()); 
				}
			}
		}
	}

利用反射调用方法:

1. 加载类
2. 在类找到要执行的方法
3. 创建对象
4. 在对象上执行方法

案例:

public class Test5 {
	
		public static void main(String[] args) 
			throws Exception{
			/*
			 * 利用反射执行方法
			1. 加载类
			2. 在类找到要执行的方法
			3. 创建对象
			4. 在对象上执行方法
			 */
			Scanner in = new Scanner(System.in);
			System.out.print("输入类名:");
			String className = in.nextLine();
			
			//Class cls 是反射API的使用入口 
			//动态加载类
			Class cls = Class.forName(className);
			//输入方法名
			System.out.print("输入方法名:");
			String name = in.nextLine();
			//动态找到类上声明的方法,如果找不到就抛出异常!
			Method method = cls.getDeclaredMethod(name);
			//创建对象:动态创建对象, Instance实例
			//newInstance的使用前提是类型必须包含无参构造器
			Object obj = cls.newInstance();
			//在对象obj上调用method方法:
			//如果参数错误,或者对象上没有方法,或者方法执行
			//期间出现故障,都会抛出异常。
			Object val = method.invoke(obj);
			System.out.println(val); 
		}
	
	}

# SmartMVC 框架开发

## 1. 将前端控制器与子控制器进行分离

将web控制逻辑封装到 DispatchServlet 中,将业务功能提取到子控制中。

### 1.1 前端控制器

前端控制器: 

/**
	 * 核心前端控制器,处理任何的 *.do 请求
	 * 前端控制器处理全部的Web功能 
	 **/
	public class DispatchServlet extends HttpServlet {
		private static final long serialVersionUID = 1L;
	
		protected void service(
				HttpServletRequest request, 
				HttpServletResponse response) throws ServletException, IOException {
			//创建子控制器
			//执行子控制器方法
			//根据子控制器的返回值,转发到JSP页面
			request.setCharacterEncoding("UTF-8");	
			try {
				//创建子控制器
				Controller controller=new Controller();
				//调用子控制器的方法(封装业务逻辑)
				String path = controller.execute(request);
				//   path=            "list"
				path = "/WEB-INF/jsp/"+path+".jsp";
				request.getRequestDispatcher(path)
				.forward(request, response);
			} catch (Exception e) {
				e.printStackTrace(); 
				response.setContentType(
						"text/html; charset=UTF-8");
				PrintWriter out=response.getWriter();
				out.println("系统故障:"+e.getMessage()); 
			}
		}
	}

将作为视图组件的JSP文件放在 /WEB-INF/JSP中, 避免用户直接访问。

配置前端控制器:将所有 *.do 请求转到前端控制器进行处理。

  <servlet>
	    <description></description>
	    <display-name>DispatchServlet</display-name>
	    <servlet-name>DispatchServlet</servlet-name>
	    <servlet-class>mvc.DispatchServlet</servlet-class>
	  </servlet>
	  <servlet-mapping>
	    <servlet-name>DispatchServlet</servlet-name>
	    <url-pattern>*.do</url-pattern>
	  </servlet-mapping>

### 1.2 编写子控制器,封装业务功能

public class Controller {
		/**
		 * 子控制器方法
		 * @param request 用于在控制器和JSP直接传递数据
		 * @return 转发的目标JSP页面
		 * @throws Exception
		 */
		@RequestMapping("/list.do") 
		public String execute(
				HttpServletRequest request) 
				throws Exception {
			UserDAO dao = new UserDAO();
			List<User> users = dao.findAll();
			//将users数据传递到JSP
			request.setAttribute("users", users); 
			return "list";
		}
		
		@RequestMapping("/add.do") 
		public String add(HttpServletRequest request) 
				throws Exception {
			return "add";
		}
	}

### 1.3 编写视图组件 list.jsp

视图组件的JSP文件放在 /WEB-INF/JSP中:

<%@ page language="java" 
		contentType="text/html; charset=UTF-8"
	    pageEncoding="UTF-8"%>
	<%@ taglib prefix="c" 
		uri="http://java.sun.com/jsp/jstl/core"%> 
	<!DOCTYPE html>
	<html>
	<head>
	<meta charset="UTF-8">
	<title>用户列表</title>
	</head>
	<body>
		<h1>用户列表</h1>
		<table>
			<tr>
				<td>ID</td>
				<td>用户名</td>
				<td>密码</td>
				<td>邮箱</td>
				<td>操作</td>
			</tr>			
			<c:forEach items="${users}" var="user">
				<tr>
					<td>${user.id}</td> 
					<td>${user.uname}</td> 
					<td>${user.pwd}</td> 
					<td>${user.email}</td> 
					<td>删除</td> 
				</tr>
			</c:forEach>
		</table>
	</body>
	</html>

## 2. 利用注解映射用户请求到控制器方法

原理:

 

### 2.1 编写注解

    @Retention(RetentionPolicy.RUNTIME) //注解一值保留到运行期
    @Target(ElementType.METHOD) //此注解只能写在方法上
    public @interface RequestMapping {
        String value();
    }

### 2.2 编写Handler类用于封装控制器和控制器方法

/**
	 * 处理者,处理器 
	 */
	public class Handler {
		/**
		 * 子控制器对象
		 */
		private Object controller;
		/**
		 * 子控制的方法 
		 */
		private Method method;
		
		public Handler() {
		}
		public Handler(Object controller, Method method) {
			this.controller = controller;
			this.method = method;
		}
		@Override
		public String toString() {
			return "Handler [controller=" + controller + ", method=" + method + "]";
		}
		
		public String execute(HttpServletRequest req) 
			throws Exception {
			//利用反射执行方法
			return (String)method.invoke(controller, req);
		}
		
	}


### 2.3 创建HandlerMapping用于封装URL与控制器方法的映射关系

HandlerMapping的内部封装了一个Map,利用Map优化查询效率,提高性能。

/**
	 * 用于管理,URL与子控制器方法的映射关系
	 * 如:
	 * 	 /list.do -> Handler(controller, execute())
	 * 	 /add.do -> Handler(controller, add())
	 */
	public class HandlerMapping {
		private Map<String, Handler> mapping=
				new HashMap<String, Handler>();
		
		public HandlerMapping() {
		}
		
		/**
		 * 将 一个控制器类解析并且添加到map中
		 * 1. 动态加载类到内存
		 * 2. 找到全部的方法,并且查找方法上是否有
		 *    RequestMapping 注解
		 * 3. 如果有注解,就将注解URL,和控制器以及方法添加到
		 *    map中
		 * @param className 一个控制器类名
		 */
		public void parseController(String className) 
			throws Exception {
			//加载类到内存中
			Class cls = Class.forName(className);
			//创建控制器对象
			Object controller = cls.newInstance();
			//找到全部方法
			Method[] methods = cls.getDeclaredMethods();
			for (Method method : methods) {
				//在方法上查找 RequestMapping 注解
				RequestMapping ann=
				  method.getAnnotation(RequestMapping.class);
				//如果ann不为空,则方法上包含RequestMapping注解
				if(ann!=null) {
					String url=ann.value();
					//将找到的方法添加到map中
					mapping.put(url,  
						new Handler(controller, method));
				}
			}
		}
		
		
		@Override
		public String toString() {
			return "HandlerMapping [mapping=" + mapping + "]";
		}
		/**
		 * 根据请求的路径url,找到Handler对象
		 * @param url
		 * @return 
		 */
		public Handler get(String url) {
			return mapping.get(url);
		}
	
		public static void main(String[] args) 
			throws Exception {
			HandlerMapping handlerMapping=
					new HandlerMapping();
			handlerMapping.parseController("test.Demo");
			System.out.println(handlerMapping.mapping); 
		}
	}

> 注意:务必利用main方法对HandlerMapping进行单元测试!

测试素材:

public class Demo {
		
		@RequestMapping("/list.do")
		public String list() {
			return "list";
		}
	
		@RequestMapping("/add.do")
		private String add() {
			return "add";
		}
		
	}

### 2.4 重构 DispatchServlet 利用init方法读取mvc.xml 加载控制器类

mvc.xml: 配置控制器其类

<?xml version="1.0" encoding="UTF-8"?>
	<beans>
		<bean class="mvc.Controller"/>
	</beans>

DispatchServlet: 

private HandlerMapping handlerMapping;
	
	@Override
	public void init() throws ServletException {
		try {
			handlerMapping = new HandlerMapping();
			//handlerMapping.parseController(
			//	"mvc.Controller");
			SAXReader reader = new SAXReader();
			
			//读取web.xml中的Servlet配置参数
			String file=getInitParameter("config");
			System.out.println("file:"+file); 
			
			InputStream in = getClass()
				.getClassLoader()
				.getResourceAsStream(file);
			Document doc = reader.read(in);
			Element root=doc.getRootElement();
			//找到全部 bean 元素
			List<Element> list=root.elements("bean");
			for (Element element : list) {
				//获取bean元素上的class属性
				String className=
						element.attributeValue("class");
				System.out.println("Controller:"+className); 
				//将得到的类名交给 handlerMapping 
				handlerMapping.parseController(className);
			}
			in.close();
			System.out.println(handlerMapping); 
		}catch (Exception e) {
			e.printStackTrace();
			throw new ServletException(
					"控制器解析错误", e);
		}
	}

在web.xml 中添加 load-on-startup 在容器启动时候初始化 Servlet 测试init方法

<servlet>
	    <description></description>
	    <display-name>DispatchServlet</display-name>
	    <servlet-name>DispatchServlet</servlet-name>
	    <servlet-class>mvc.DispatchServlet</servlet-class>
	    <init-param>
	      <param-name>config</param-name>
	      <param-value>mvc.xml</param-value>
	    </init-param>
	    <load-on-startup>1</load-on-startup>
	  </servlet>
	  <servlet-mapping>
	    <servlet-name>DispatchServlet</servlet-name>
	    <url-pattern>*.do</url-pattern>
	  </servlet-mapping>


### 2.5 重构 DispatchServlet 实现根据URL执行控制器方法

1. 重构service()方法,根据用户请求的URL执行对应的控制器方法
2. 重构service()方法,支持重定向功能:
    1. 控制返回值以redirect:为开头是重定向功能
        - 如果返回http为开头的就是跳转到其他网站
        - 否则是本网站的地址,拼接 ContextPath以后再重定向。
    2. 其他情况认为是转发到JSP页面

protected void service(
			HttpServletRequest request, 
			HttpServletResponse response) throws ServletException, IOException {
		//创建子控制器
		//执行子控制器方法
		//根据子控制器的返回值,转发到JSP页面
		request.setCharacterEncoding("UTF-8");	
		try {
			
			//获取用户请求的路径 
			String path = request.getRequestURI();
			System.out.println(path);
			String contextPath=request.getContextPath();
			System.out.println(contextPath);
			path = path.substring(contextPath.length());
			System.out.println(path);
			//根据请求的URL找到Handler
			Handler handler=handlerMapping.get(path);
			//执行控制器方法(利用反射执行方法)
			path = handler.execute(request);
			//创建子控制器
			//Controller controller=new Controller();
			//调用子控制器的方法(封装业务逻辑)
			//String path = controller.execute(request);
				
			if(path.startsWith("redirect:")) {
				//支持重定向功能
				path = path.substring(
					"redirect:".length());
				if(path.startsWith("http")) {
					//如果是http开头的就直接重定向
					response.sendRedirect(path);
				} else {
					// 否则就拼接绝对路径
					//  /servler6-4/list.do
					path = contextPath+path;
					response.sendRedirect(path);
				}
			} else {
				//转发到JSP
				path = "/WEB-INF/jsp/"+path+".jsp";
				request.getRequestDispatcher(path)
				.forward(request, response);
			}
		} catch (Exception e) {
			e.printStackTrace(); 
			response.setContentType(
					"text/html; charset=UTF-8");
			PrintWriter out=response.getWriter();
			out.println("系统故障:"+e.getMessage()); 
		}
	}

## 3. 实现添加和删除功能

 ### 3.1 添加add.jsp页面

<%@ page language="java" 
		contentType="text/html; charset=UTF-8"
	    pageEncoding="UTF-8"%>
	<%@ taglib prefix="c" 
		uri="http://java.sun.com/jsp/jstl/core"%> 
	<!DOCTYPE html>
	<html>
	<head>
	<meta charset="UTF-8">
	<title>添加用户</title>
	</head>
	<body>
		<h1>添加用户</h1>
		<form action="save.do" method="post">
			<div>
				<label>用户名:</label>
				<input type="text" name="uname" 
				value="${uname}"> 
				<span>${unameError}</span>
			</div>
			<div>
				<label>密码:</label>
				<input type="password" name="pwd"> 
			</div>
			<div>
				<label>e-mail:</label>
				<input type="text" name="email"
					value="${email}">
			</div>
			<div>
				<input type="submit" value="保存">  
			</div>
		</form>
	</body>
	</html>


### 3.2 添加控制器Controller方法:

public class Controller {
		/**
		 * 子控制器方法
		 * @param request 用于在控制器和JSP直接传递数据
		 * @return 转发的目标JSP页面
		 * @throws Exception
		 */
		@RequestMapping("/list.do") 
		public String execute(
				HttpServletRequest request) 
				throws Exception {
			UserDAO dao = new UserDAO();
			List<User> users = dao.findAll();
			//将users数据传递到JSP
			request.setAttribute("users", users); 
			return "list";
		}
		
		@RequestMapping("/add.do") 
		public String add(HttpServletRequest request) 
				throws Exception {
			return "add"; //add.jsp
		}
		
		@RequestMapping("/save.do") 
		public String save(HttpServletRequest request) 
				throws Exception {
			String uname=request.getParameter("uname");
			String pwd=request.getParameter("pwd");
			String email=request.getParameter("email");
			
			UserDAO dao = new UserDAO();
			//检查用户名是否是同名的
			User u = dao.find(uname);
			if(u!=null) {
				//返回到添加页面,继续输入信息
				request.setAttribute("uname", uname);
				request.setAttribute("email", email);
				request.setAttribute("unameError", 
						"用户名重复");
				return "add";
			}
			
			User user = new User();
			user.setUname(uname);
			user.setPwd(pwd);
			user.setEmail(email);		
			dao.save(user);
			request.setAttribute("message", "成功"); 
			//return "success"; //success.jsp
			return "redirect:/list.do";
			//return "redirect:http://tmooc.cn";
		}
		
		@RequestMapping("/delete.do")
		public String delete(HttpServletRequest request)
			throws Exception{
			
			String str = request.getParameter("id");
			int id = Integer.parseInt(str);
			UserDAO dao = new UserDAO();
			dao.delete(id);
			
			return "redirect:/list.do";
		}
		
	}

> 测试 列表,添加,删除功能

## 4. 添加登录功能

 ### 4.1 添加登录页面

<%@ page language="java" 
		contentType="text/html; charset=UTF-8"
	    pageEncoding="UTF-8"%>
	<%@ taglib prefix="c" 
		uri="http://java.sun.com/jsp/jstl/core"%> 
	<!DOCTYPE html>
	<html>
	<head>
	<meta charset="UTF-8">
	<title>登录</title>
	</head>
	<body>
		<h1>登录</h1>
		<form action="login.do" method="post">
			<div>
				<label>用户名:</label>
				<input type="text" name="uname" 
				value="${uname}"> 
				<span>${unameError}</span>
			</div>
			<div>
				<label>密码:</label>
				<input type="password" name="pwd"> 
				<span>${pwdError}</span>
			</div>
			<div>
				<input type="submit" value="登录">  
			</div>
		</form>
	</body>
	</html>

### 4.2 添加登录控制器方法

public class LoginController {
		
		@RequestMapping("/login-form.do")
		public String form(HttpServletRequest request)
			throws Exception{
			return "login";
		}
		
		@RequestMapping("/login.do")
		public String login(HttpServletRequest request) 
			throws Exception{
			//检查用户名和密码是否正确
			String uname = request.getParameter("uname");
			String pwd = request.getParameter("pwd");
			UserDAO dao=new UserDAO();
			User user = dao.find(uname);
			//数据中没有用户信息,拒绝登录返回 "login" 
			if(user==null) {
				request.setAttribute("unameError",
						"用户名或密码错误");
				request.setAttribute("uname", uname);
				return "login";
			}
			if(user.getPwd().equals(pwd)) {
				//密码也一样则登录成功!
				request.getSession().setAttribute(
						"loginUser", user);
				return "redirect:/list.do";
			}//密码不同,返回登录页面继续登录
			request.setAttribute("pwdError",
					"用户名或密码错误");
			request.setAttribute("uname", uname);
			return "login";
		}
	}

> 登录成功以后需要将登录信息保存到session中

### 4.3 利用过滤器解决权限问题
 

 过滤器:

public class AccessFilter implements Filter {
	
		public void destroy() {
		}
	
		public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
			HttpServletRequest req = (HttpServletRequest)request;
			HttpServletResponse res = (HttpServletResponse)response;
	
			//如果当前请求是 /login 开始的,就放过
			String path = req.getRequestURI();
			String ctxPath = req.getContextPath();
			path = path.substring(ctxPath.length());
			if(path.startsWith("/login")) {
				chain.doFilter(req, res);
				return;
			}
			
			HttpSession session = req.getSession();
			//检查登录用户信息
			User user=(User)session.getAttribute("loginUser");
			if(user==null) {
				//没有登录过,重定向到登录页面
				res.sendRedirect(req.getContextPath()+ 
						"/login-form.do"); 
			}else {
				//如果登录了就放过
				chain.doFilter(req, res);
			}
			
		}
	
		public void init(FilterConfig fConfig) throws ServletException {
		}
	}


配置过滤器 web.xml

 <filter>
	    <display-name>AccessFilter</display-name>
	    <filter-name>AccessFilter</filter-name>
	    <filter-class>mvc.AccessFilter</filter-class>
	  </filter>
	  <filter-mapping>
	    <filter-name>AccessFilter</filter-name>
	    <url-pattern>*.do</url-pattern>
	  </filter-mapping>

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值