雨,像银灰色黏湿的蛛丝,织成一片轻柔的网,网住了整个秋的世界。 天也是暗沉沉的,像古老的住宅里缠满着蛛丝网的屋顶。那堆在天上的灰白色的云片,就像屋顶上剥落的白粉。在这古旧的屋顶的笼罩下,一切都是异常的沉闷。园子里绿翳翳的石榴、桑树、葡萄藤,都不过代表着过去盛夏的繁荣,现在已成了古罗马建筑的遗迹一样,在萧萧的雨声中瑟缩不宁,回忆着光荣的过去。草色已经转入忧郁的苍黄,地下找不出一点新鲜的花朵;宿舍墙外一带种的娇嫩的洋水仙,垂了头,含着满眼的泪珠,在那里叹息它们的薄命,才过了两天的晴美的好日子又遇到这样霉气薰薰的雨天。只有墙角的桂花,枝头已经缀着几个黄金一样宝贵的嫩蕊,小心地隐藏在绿油油椭圆形的叶瓣下,透露出一点新生命萌芽的希望。 雨静悄悄地下着,只有一点细细的淅沥沥的声音。桔红色的房屋,像披着鲜艳的袈裟的老僧,垂头合目,受着雨底洗礼。那潮湿的红砖,发出有刺激性的猪血的颜色和墙下绿油油的桂叶成为强烈的对照。灰色的癞蛤蟆,在湿烂发霉的泥地里跳跃着;在秋雨的沉闷的网底,只有它是唯一的充满愉快的生气的东西。它背上灰黄斑驳的花纹,跟沉闷的天空遥遥相应,造成和谐的色调。它噗通噗通地跳着,从草窠里,跳到泥里,溅出深绿的水花。 织梦 内容管理系统 雨,像银灰色黏濡的蛛丝,织成一片轻柔的网,网住了 整个秋的世界。
不知道是偶然还是缘分,点进了张爱玲的文集,看到了秋雨这篇小时候一直看不懂的文章,突然有一种冲击在心底的失落。知道自己一直和文艺搭不上边。却在此时如此深刻的理解了,文中那深深的乡愁之情 每一句都是深深的包含对家的思念。一场秋雨一场寒 哈哈 扯远了。好久没有更新了,一是感觉时间不够用,突然生出一种感悟 时间都去哪了。二是对未来的路有点失落和迷茫了,不知道自己的路在何方。每天虽然都在努力给自己充电,但是充的电好像引不起我的共鸣。我想这很多人可能和我有相同的感觉,时代在变,我们就需要付出更大的努力才能勉强跟上它的脚步,总有一种感觉好像突然就会被错过,以前没有的压力,突然全都压上心头。哈哈 好像又一次扯远了,说一说今天的动机吧!很久没有更新是因为感觉以前写的文章,都有点应付自己。所以想要沉淀沉淀自己。昨天在网上看了一遍博文,感觉很对我的口味。全文我不记的了,最后那句话 如果你在感慨或者迷茫的时候不如埋下头来认真的去做一件事,把其他的事情都抛开,这是你就再也不会把时间浪费在感慨和迷茫上了。好了感慨到此结束进入今天的主题。
spring
spring我相信在java程序员中是每天必须接触的。使用也是必须掌握的。但是我们是不是真的理解或者是掌握了呢!我想大部分人肯定和我一样,处于迷糊的状态。似乎知道怎么用,但是如果需要你掌握其中的精髓原理,可能大部分人都感到有点丈二和尚摸不着头脑。如果大部分的人都是这种情况那我们何不把它精确的掌握呢,那我们是不是比别人高了一个档次了。今天我就是朝这个目标前进。先说一说spring的原理。
spring主要分三个阶段配置,初始化,运行。每一个阶段对应的再上图都可以看出有哪些操作这里就不细说了。
看了Tom哥的springMVC从无到有,虽然有时候坑点很多,但是最终还是感觉受益匪浅。所以今天自己也想手写实现一下。不是因为闲的蛋疼。而是因为太慌了,撸撸代码来平复一下这糟糕的情绪。O(∩_∩)O
好了废话就不说了,开始上代码。
这是准备的代码结构。文中pom.xml只是引用了servlet.api这一个jar包。
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0-alpha-1</version>
</dependency>
</dependencies>
一下是web.xml配置文件
<?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/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<display-name>mySpring</display-name>
<servlet>
<servlet-name>lcmvc</servlet-name>
<servlet-class>com.lc.mvcframework.servlet.LCDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>lcmvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
application.properties配置 这里是扫描的路径
scanPackage=com.lc.demo
@Target({ElementType.FIELD}) // 注释在字段上
@Retention(RetentionPolicy.RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented//说明该注解将被包含在javadoc中
public @interface LCAutowrited {
String value() default "";
}
@Target({ElementType.TYPE}) // 注释在类上
@Retention(RetentionPolicy.RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented//说明该注解将被包含在javadoc中
public @interface LCController {
String value() default "";
}
@Target({ElementType.TYPE,ElementType.METHOD}) // 注释在类上也可以在方法上
@Retention(RetentionPolicy.RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented//说明该注解将被包含在javadoc中
public @interface LCRequestMapping {
String value() default "";
}
@Target({ElementType.PARAMETER}) // 注释在参数上
@Retention(RetentionPolicy.RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented//说明该注解将被包含在javadoc中
public @interface LCRequestParam {
String value() default "";
}
@Target({ElementType.TYPE}) // 注释在类上
@Retention(RetentionPolicy.RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented//说明该注解将被包含在javadoc中
public @interface LCService {
String value() default "";
}
DemoAction
@LCController
@LCRequestMapping("/demo")
public class DemoAction {
@LCAutowrited private IDemoService demoService;
@LCRequestMapping("/edit.do")
public void edit(HttpServletRequest req,HttpServletResponse rep,
@LCRequestParam("name") String name) {
String res = demoService.get(name);
try {
rep.getWriter().write(res);
} catch (Exception e) {
e.printStackTrace();
}
}
}
TestAction
@LCController
@LCRequestMapping("/test")
public class TestAction {
@LCAutowrited private IDemoService demoService;
@LCRequestMapping("/query.do")
public void query(HttpServletRequest req,HttpServletResponse rep,
@LCRequestParam("name") String name) {
String res = demoService.get(name);
try {
rep.getWriter().write(res);
} catch (Exception e) {
e.printStackTrace();
}
}
}
service接口
public interface IDemoService {
String get(String name);
}
service实现类
@LCService
public class DemoService implements IDemoService{
public String get(String name) {
return "My name is"+name;
}
}
dispatcherServlet
package com.lc.mvcframework.servlet;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.lc.mvcframework.annotation.LCAutowrited;
import com.lc.mvcframework.annotation.LCController;
import com.lc.mvcframework.annotation.LCRequestMapping;
import com.lc.mvcframework.annotation.LCRequestParam;
import com.lc.mvcframework.annotation.LCService;
public class LCDispatcherServlet extends HttpServlet{
private Properties p = new Properties();//根据Properties 加载InputStream流properties配置文件
private List<String> classNames = new ArrayList<String>();
private Map<String,Object> iocs = new HashMap<String,Object>();
//申明一个handlerMapping
//private Map<String,Method> handlerMapping = new HashMap<String,Method>();
private List<Handler> handlerMapping = new ArrayList<Handler>();
public void init(ServletConfig config) throws ServletException {
// 1 加载配置文件
// 获取配置文件所在路径 并通过配置文件的路径将文件读取进来
String path = config.getInitParameter("contextConfigLocation");
doLoadConfig(path);
// 2 读取配置文件扫描相关的类
doScanner(p.getProperty("scanPackage"));//通过获取到的配置文件 取出其中需要扫描的路径
// 3将已经扫描的类进行初始化并放入ioc容器中
doInstance();
// 4依赖注入
doAutowrited();
// 5初始化handlerMapping
initHandlerMapping();
System.out.println("this is My Spring servlet");
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
// 6等待调用
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try{
doDispatcher(req, resp);
}catch(Exception e){
resp.getWriter().write("500 Exception,Details:\r\n"+Arrays.toString(e.getStackTrace()));
}
}
private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {
/*
//判断handlerMapping是否存在
if(handlerMapping.isEmpty()) return;
// 获取用户请求的url
String url = req.getRequestURI();
//获取绝对路径
String contextPath = req.getContextPath();
//将url中的绝对路径去掉获得相对路径
url = url.replace(contextPath, "").replaceAll("/+", "/");
// 根据url从handlerMapping中获取对应的值
if(!handlerMapping.containsKey(url)){
try {
resp.getWriter().write("Not Found 404!!!");
return;
} catch (Exception e) {
e.printStackTrace();
}
}
// 通过反射调用
Method method = handlerMapping.get(url);
// 第一个参数传方法method所对应的对象,第二个参数传对应的实参
// 这里只能从ioc容器中取值 做到这里发现走不通 因为参数没有 所以只能换一个数据结构
method.invoke(obj, args);
System.out.println(handlerMapping.get(url));
*/
try{
Handler handler = getHandler(req);
if(handler == null){
// 没有匹配上
resp.getWriter().write("404 Not Found");
return;
}
// 获取参数列表
Class<?>[] paramTypes = handler.method.getParameterTypes();
// 保存需要自动赋值的参数值
Object[] paramValues = new Object[paramTypes.length];
Map<String,String[]> params = req.getParameterMap();
for (Entry<String,String[]> param : params.entrySet()) {
String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
// 如果找不到匹配的对象,则开始填充参数值
if(!handler.paramIndexMapping.containsKey(param.getKey())){
continue;
}
int index = handler.paramIndexMapping.get(param.getKey());
paramValues[index] = convert(paramTypes[index],value);
}
// 设置方法中的request和response对象
int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
paramValues[reqIndex]= req;
int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
paramValues[respIndex]= resp;
handler.method.invoke(handler.controller,paramValues );
}catch(Exception e){
System.err.println(e.getStackTrace());
}
}
private Handler getHandler(HttpServletRequest req) throws Exception{
if(handlerMapping.isEmpty()) return null;
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replace(contextPath, "").replaceAll("/+", "/");
for (Handler handler : handlerMapping) {
try{
Matcher matcher = handler.pattern.matcher(url);
// 如果没有匹配上继续下一个匹配
if(!matcher.matches()){
continue;
}
return handler;
}catch(Exception e){
throw e;
}
}
return null;
}
private Object convert(Class<?> type,String value){
if(Integer.class == type){
return Integer.valueOf(value);
}
return value;
}
private void doLoadConfig(String path) {
// 根据路径获取一个InputStream的流
InputStream is = this.getClass().getClassLoader().getResourceAsStream(path);
try {
p.load(is);
} catch (Exception e) {
e.printStackTrace();
}finally {
// 判断是否为空 如果不为空 则close流
if(null != is)
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void doScanner(String packageName) {
// 因为packageName是类似于com.lc.demo这样的格式 需要将所有点替换成/的路径 并将转换成String的路径
String url = this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/")).getFile();
// 根据路径可以得到一个file
File files = new File(url);
// 通过循环遍历找出里面所有的类文件
for (File f : files.listFiles()) {
// 判断是不是一个目录 如果是一个目录继续调用这个方法 并将路径拼接上去
if (f.isDirectory()) {
doScanner(packageName+"."+f.getName());
}else {
//如果是一个文件就需要将这个类名存入 并将他的后缀去掉
String className = packageName+"."+f.getName().replace(".class", "");
// 将className 存入容器之中
classNames.add(className);
}
}
}
private void doInstance() {
// 初始化容器
// 判断classNames中是否为空
if(classNames.isEmpty()) return;
// 不为空就循环取出
try {
for (String className : classNames) {
Class<?> clazz = Class.forName(className);
// 初始化这里需要注意几个概念
// 对于controller来说 默认情况 一般是 首字母小写 同时我们也需要注意一点,在并不是所有的controller都会被注入。
// 注释类型的注释存在于此元素上,则返回true,否则返回false
if(clazz.isAnnotationPresent(LCController.class)) {
// 获取controller的类名
String beanName = firstLower(clazz.getSimpleName());//同时需要注意这里我们获取到的类名首字母是大写的 我们需要将其变成小写
// 判断是不是已经存在形同的key 如果存在相同的 不处理
if(!iocs.containsKey(beanName)) {
iocs.put(beanName, clazz.newInstance());
}
}
// 针对于service来说
else if(clazz.isAnnotationPresent(LCService.class)) {
// 1 IOC容器的结构 Map<String,Object>
// String 也可以叫做IOC容器的beanName
// beanName有几个规则 默认情况下是类名的首字母小写
// 第二种情况 就是当我们自定义了一个名称时应该以我们自定义的优先级高 或者是说以我们自定义的为准
// 第三种情况 当我们注入service时我们一般是注入的接口,在java中接口是不能被实例化的。这时我们需要主要接口的实现类
// 这时我们又会遇到另外一个问题 就是接口是可以实现多个的,所以这时候我们应该根据接口的类型来注入。
// 首先我们取出自定义的
LCService service = clazz.getAnnotation(LCService.class);
String beanName = service.value();//获取自定义的name
// 判断是否为空
if("".equals(beanName)) {
// 默认首字母小写
beanName = firstLower(clazz.getSimpleName());
}
// 这里是将第一种和第二种情况处理
Object instance = clazz.newInstance();
iocs.put(beanName, instance);
// 处理第三种情况 获取接口类型
Class<?> [] classes = clazz.getInterfaces();
for (Class<?> c : classes) {
// 这里为什么这样可以 因为instance在这里是单例模式 只被初始化一次
iocs.put(c.getName(), instance);
}
}else {
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 依赖注入
private void doAutowrited() {
// 判断是否为空 如果为空就直接退出
if(iocs.isEmpty()) return;
// 不为空就循环取出
for (Entry<String, Object> entry : iocs.entrySet()) {
// 通过反射取出entry中所有的字段
Field[] fields = entry.getValue().getClass().getDeclaredFields();
// 循环赋值
for (Field field : fields) {
// 这里需要注意 只有加了LCAutowirted才进行赋值
if(!field.isAnnotationPresent(LCAutowrited.class)) continue;
// 如果有获取它的值
LCAutowrited autowrited = field.getAnnotation(LCAutowrited.class);
String beanName = autowrited.value();
// 判断beanName是否设置了
if("".equals(beanName)) {
// 从field中取
beanName = field.getType().getName();
}
// 这里需要注意 因为我们的字段可能加了访问权限 如受保护的 私有的 ...
// 这里我们需要设置不管什么访问权限全部获取 授权
field.setAccessible(true);
// 赋值
try {
field.set(entry.getValue(), iocs.get(beanName));//第一个参数表示给哪个字段赋值,第二个参数要赋的值
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
}
// 初始化handlerMapping
private void initHandlerMapping() {
if(iocs.isEmpty()) return;
for (Entry<String, Object> entry : iocs.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
// 这里因为handlerMapping对应的是controller所以这里需要判断是不是controller
if(!clazz.isAnnotationPresent(LCController.class)) continue;
// 保存url
String url = "";
// 取出LCRequestMapping中的值
if(clazz.isAnnotationPresent(LCRequestMapping.class)) {
LCRequestMapping requestMapping = clazz.getAnnotation(LCRequestMapping.class);
url = requestMapping.value();
}
// 扫描所有方法 这里需要注意一点为什么不用clazz.getDeclaredMethods() 因为这个只是扫描所有方法 而这里我们只是需要公有方法即可,减少循环次数
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if(!method.isAnnotationPresent(LCRequestMapping.class)) continue;
// LCRequestMapping requestMapping = method.getAnnotation(LCRequestMapping.class);
// String r = requestMapping.value();
// url = ("/"+url +"/"+ r).replaceAll("/+", "/");//将多余的斜杠去掉
// handlerMapping.put(url, method);
// System.out.println("app Mapping "+url+","+method);
// 映射URL
LCRequestMapping requestMapping = method.getAnnotation(LCRequestMapping.class);
String regex = ("/"+url+ requestMapping.value()).replaceAll("/+", "/");
Pattern pattern = Pattern.compile(regex);
handlerMapping.add(new Handler(pattern, entry.getValue(), method));
System.out.println("app Mapping "+regex+","+method);
}
}
}
//首字母变成小写
private String firstLower(String name) {
char[] chars = name.toCharArray();
chars[0]+=32;
return String.valueOf(chars);
}
//handler的内部类
private class Handler{
protected Object controller;//保存方法对应的实例
protected Method method;//保存映射的方法
protected Pattern pattern;
protected Map<String,Integer> paramIndexMapping;//参数对应顺序
protected Handler(Pattern pattern,Object controller,Method method){
this.controller = controller;
this.pattern = pattern;
this.method = method;
paramIndexMapping = new HashMap<String, Integer>();
putParamIndexMapping(method);
}
private void putParamIndexMapping(Method method){
// 提取方法中加了注解的参数
Annotation[][] pa = method.getParameterAnnotations();
for(int i=0;i<pa.length;i++){
for (Annotation a : pa[i]) {
if(a instanceof LCRequestParam){
String paramName = ((LCRequestParam)a).value();
if(!"".equals(paramName.trim())){
paramIndexMapping.put(paramName, i);
}
}
}
}
// 提取方法中的request 和response参数
Class<?>[] paramsTypes = method.getParameterTypes();
for(int i=0;i<paramsTypes.length;i++){
Class<?> type = paramsTypes[i];
if(type == HttpServletRequest.class ||
type == HttpServletResponse.class){
paramIndexMapping.put(type.getName(), i);
}
}
}
}
}
/*
//判断handlerMapping是否存在
if(handlerMapping.isEmpty()) return;
// 获取用户请求的url
String url = req.getRequestURI();
//获取绝对路径
String contextPath = req.getContextPath();
//将url中的绝对路径去掉获得相对路径
url = url.replace(contextPath, "").replaceAll("/+", "/");
// 根据url从handlerMapping中获取对应的值
if(!handlerMapping.containsKey(url)){
try {
resp.getWriter().write("Not Found 404!!!");
return;
} catch (Exception e) {
e.printStackTrace();
}
}
// 通过反射调用
Method method = handlerMapping.get(url);
// 第一个参数传方法method所对应的对象,第二个参数传对应的实参
// 这里只能从ioc容器中取值 做到这里发现走不通 因为参数没有 所以只能换一个数据结构
method.invoke(obj, args);
System.out.println(handlerMapping.get(url));
*/
调用成功!!
上文中被注释的这一段本来是按照这个思路写的 但是到后面method.invoke(obj, args);这一步的时候坑来了。obj实例对象和实力参数好像没有,实验了好几种办法都不能实现彻底绝望了,后面又重新找资料 用的后面那种 加了一个handler的数据结构,将每次获取到的实例对象和实例参数保存在一个List<handler>结构中才解决这个问题。没有看spring源码是如何实现的,估计spring实现应该是更好的办法,只能下次再去细看了。个人觉得文中的注释还是写的比较详细的,以及思路也还算详细。这里就不赘述了。谢谢那些没有跳过开头的同学。好久没有更新了所以难免会有点感慨。O(∩_∩)O 如果你觉得有帮助记得点赞嚄!