本人理解,SpringMvc重要的3点:实例化、注入、URL映射,新手,有误莫怪。
简单理解就是扫描用户设定的包下面所有的类,然后进行遍历,对加了@Controller、@Service注解的对象进行实例化,然后对这些类里面的加了@Autowired注解的属性进行依赖注入,对Controller中加了@RequestMapping注解的方法做URL映射。
1、创建一个maven项目
Packaging选择war,因为我们是一个web工程。
然后JDK版本选择1.8,因为java1.8是支持从反射获取参数名的。
下图为项目结构
2、定义注解
只是为了理解,所以功能不是特别强大,本项目只定义了5个比较常用的注解Controller、Service、Autowire、RequestMapping、RequestParam,便于区分,名称前面加上Sunwj。废话不多说,直接上代码。
package com.sunwj.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SunwjController {
String value();
}
package com.sunwj.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SunwjService {
String value();
}
package com.sunwj.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SunwjAutowire {
String value();
}
package com.sunwj.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SunwjRequestMapping {
String value();
}
package com.sunwj.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SunwjRequestParam {
String value();
}
@Target、@Retention、@Documented为Java自定义注解,起辅助作用,不会影响代码的实际逻辑。
2.1、@Target:定义注解的作用目标
@Target(ElementType.TYPE) // 接口、类、枚举、注解
@Target(ElementType.FIELD) // 字段、枚举的常量
@Target(ElementType.METHOD) // 方法
@Target(ElementType.PARAMETER) // 方法参数
@Target(ElementType.CONSTRUCTOR) // 构造函数
@Target(ElementType.LOCAL_VARIABLE) // 局部变量
@Target(ElementType.ANNOTATION_TYPE) // 注解
@Target(ElementType.PACKAGE) / // 包
查看Target源码可以知道, ElementType 可以有多个,一个注解可以为类的,方法的,字段的等等。
2.2、@Retention: 定义注解的保留策略
@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
2.3、@Document:说明该注解将被包含在javadoc中
3、编写对应的Servlet类
SpringMvc的核心类DispatcherServlet。我们不需要那么多功能。本项目就只涉及到实例化、注入、URL映射。
创建一个Servlet类,SunwjServlet。
在web.xml声明SunwjServlet并映射。
<servlet>
<servlet-name>sunwjServlet</servlet-name>
<servlet-class>com.sunwj.servlet.SunwjServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>sunwjServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
在初始化方法里面,分为5个步骤:
3.1、加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation"));
通过web.xml配置的参数,获得配置文件,进行加载。
3.2、初始化所有相关联的类,扫描用户设定的包下面所有的类 doScanner(properties.getProperty("scanPackage"));
在Properties中,定义了一个scanPackage为扫描的路径。通过递归,获取该路径下的所有文件。
3.3、拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean) doInstance();
对扫描到的所有类,进行遍历,对加了@SunwjController、@SunwjService注解的对象进行实例化,并存入Map中。
3.4、初始化HandlerMapping(将url和method对应上) initHandlerMapping();
对实例化的对象进行遍历,获取加了@SunwjController注解的对象的method数值,然后进行遍历,对加了@SunwjRequestMapping注解的方法,进行映射,存入URL映射的Map中。
3.5、实现注入 ioc();
对实例化的对象进行遍历,获取加了@SunwjController注解的对象的field数值,然后进行遍历,对加了@SunwjAutowire注解的属性,进行依赖注入。
这样就完成了本项目最重要的功能了,接下去写一下doGet、doPost方法。
代码如下:
package com.sunwj.servlet;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.sunwj.annotation.SunwjAutowire;
import com.sunwj.annotation.SunwjController;
import com.sunwj.annotation.SunwjRequestMapping;
import com.sunwj.annotation.SunwjRequestParam;
import com.sunwj.annotation.SunwjService;
public class SunwjServlet extends HttpServlet {
private static final long serialVersionUID = 7265511195544348690L;
private Properties properties = new Properties();
private List<String> classNames = new ArrayList<String>();
private Map<String, Object> instanceMap = new HashMap<String, Object>();
private Map<String, Method> handlerMapping = new HashMap<String, Method>();
@Override
public void init(ServletConfig config) {
// 1.加载配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));
// 2.初始化所有相关联的类,扫描用户设定的包下面所有的类
doScanner(properties.getProperty("scanPackage"));
// 3.拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean)
doInstance();
// 4.初始化HandlerMapping(将url和method对应上)
initHandlerMapping();
// 5.实现注入
ioc();
}
private void doLoadConfig(String location) {
// 把web.xml中的contextConfigLocation对应value值的文件加载到流里面
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
try {
// 用Properties文件加载文件里的内容
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关流
if (null != resourceAsStream) {
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void doScanner(String packageName) {
// 把所有的.替换成/
URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
System.out.println(url);
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
// 递归读取包
doScanner(packageName + "." + file.getName());
} else {
String className = packageName + "." + file.getName().replace(".class", "");
classNames.add(className);
}
}
}
private void doInstance() {
if (classNames.isEmpty()) {
return;
}
for (String className : classNames) {
try {
// 把类搞出来,反射来实例化(只有加@SunwjController需要实例化)
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(SunwjController.class)) {
instanceMap.put(clazz.getAnnotation(SunwjController.class).value(), clazz.newInstance());
} else if (clazz.isAnnotationPresent(SunwjService.class)) {
instanceMap.put(clazz.getAnnotation(SunwjService.class).value(), clazz.newInstance());
}else {
continue;
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
private void initHandlerMapping() {
if (instanceMap.isEmpty()) {
return;
}
try {
for (Entry<String, Object> entry : instanceMap.entrySet()) {
Class<? extends Object> clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(SunwjController.class)) {
continue;
}
// 拼url时,是controller头的url拼上方法上的url
String baseUrl = clazz.getAnnotation(SunwjController.class).value();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(SunwjRequestMapping.class)) {
continue;
}
SunwjRequestMapping sunwjRequestMapping = method.getAnnotation(SunwjRequestMapping.class);
String url = sunwjRequestMapping.value();
url = (baseUrl + "/" + url).replaceAll("/+", "/");
handlerMapping.put(url, method);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void ioc() {
if (instanceMap.isEmpty())
return;
for (Map.Entry<String, Object> entry : instanceMap.entrySet()) {
// 拿到里面的所有属性
Field fields[] = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);// 可访问私有属性
if (field.isAnnotationPresent(SunwjAutowire.class)) {
SunwjAutowire sunwjAutowire = field.getAnnotation(SunwjAutowire.class);
String value = sunwjAutowire.value();
field.set(entry.getValue(), instanceMap.get(value));
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
try {
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
public void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
if (handlerMapping.isEmpty()) {
return;
}
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replace(contextPath, "").replaceAll("/+", "/");
if (!this.handlerMapping.containsKey(url)) {
resp.getWriter().write("404 NOT FOUND!");
return;
}
Method method = this.handlerMapping.get(url);
// 获取方法里的参数
Parameter[] parameters = method.getParameters();
// 保存参数值
List<Object> paramValue = new ArrayList<Object>();
for (Parameter parameter : parameters) {
// 当前参数有别名注解并且别名不为空
if(parameter.isAnnotationPresent(SunwjRequestParam.class) && !parameter.getAnnotation(SunwjRequestParam.class).value().isEmpty()){
String value = req.getParameter(parameter.getAnnotation(SunwjRequestParam.class).value());
paramValue.add(value);
}else if (parameter.getParameterizedType().getTypeName().contains("HttpServletRequest")) {
paramValue.add(req);
}else if (parameter.getParameterizedType().getTypeName().contains("HttpServletResponse")) {
paramValue.add(resp);
}else{
paramValue.add(null);
}
}
// 利用反射机制来调用
try {
method.invoke(this.instanceMap.get('/' + url.split("/")[1]), paramValue.toArray());// 第一个参数是method所对应的实例 // 在ioc容器中
} catch (Exception e) {
e.printStackTrace();
}
}
}
4、例子
创建controller,service及实现类。
package com.sunwj.controller;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import com.sunwj.annotation.SunwjAutowire;
import com.sunwj.annotation.SunwjController;
import com.sunwj.annotation.SunwjRequestMapping;
import com.sunwj.annotation.SunwjRequestParam;
import com.sunwj.service.HelloService;
@SunwjController("/hello")
public class HelloContr {
@SunwjAutowire("helloService")
private HelloService helloService;
@SunwjRequestMapping("/hello")
public void hello(String name, HttpServletResponse resp) {
try {
String result = helloService.sayHello(name);
PrintWriter out = resp.getWriter();
out.write(result);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@SunwjRequestMapping("/sayHello")
public void sayHello(@SunwjRequestParam("name") String n,@SunwjRequestParam("age") Integer a, HttpServletResponse resp) {
try {
String result = helloService.sayHello(n);
PrintWriter out = resp.getWriter();
out.write(result + a + "岁了!");
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.sunwj.service;
public interface HelloService {
public String sayHello(String name);
}
package com.sunwj.service.impl;
import com.sunwj.annotation.SunwjService;
import com.sunwj.service.HelloService;
@SunwjService("helloService")
public class HelloServiceImpl implements HelloService{
public String sayHello(String name) {
return "hello," + name + "。";
}
}