前言
spring和之前将的tomcat差不多,都有一个servlet,但是有了分发的功能。最大的不同是需要把类实例化成对象放到ioc容器。
6大步骤
- 1.加载配置文件application.properties
public class HLPDidspatcherServlet extends HttpServlet{
private Properties p = new Properties();
private List<String> classNames = new ArrayList<String>();
private Map<String,Object> ioc = new HashMap<String,Object>();
private Map<String,Method> handlerMapping = new HashMap<String,Method>();
@Override
public void init(ServletConfig config) throws ServletException {
String application = config.getInitParameter("contextConfigLocation");
System.out.println("application = " + application);
//1.加载配置文件application.properties
doLoadConfig(application);
//2.扫描配置文件中描述的相关的所有类
doScanner(p.getProperty("scanPackage"));
//3.实例化所有扫描到的类,并放到ioc容器中(自己实现ioc容器)
doInstance();
//4.依赖注入,di从ioc容器中找到加@autowired这个注解的属性,并找到其在ioc容器中的实例,动态赋值
doAutowired();
//5.把在@controller中加了@requestMapping这个注解的方法和url构造成一个对应关系,一个map结构,key是URL;value是method
initHandlerMapping();
//6.等待用户请求,根据用户请求的url去map中找对应的method
// 调用doPost/doGet方法,通过反射机制动态调用该方法并执行
}}
//1.加载配置文件application.properties
private void doLoadConfig(String location){
InputStream is = this.getClass().getClassLoader().getResourceAsStream(location);
try {
p.load(is);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null != is){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 2.扫描配置文件中描述的类
//2.扫描配置文件中描述的相关的所有类
//递归扫描
private void doScanner(String packageName){
URL url = this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.","/"));
File dir = new File(url.getFile());
for(File file : dir.listFiles()){
if(file.isDirectory()){
doScanner(packageName+"."+file.getName());
}else{
classNames.add(packageName + "." + file.getName().replaceAll(".class","").trim());
}
}
}
- 3.把这些类实例化放到ioc容器中
//3.实例化所有扫描到的类,并放到ioc容器中(自己实现ioc容器)
private void doInstance(){
try {
for (String className : classNames) {
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(HLPController.class)){
String beanName = lowerFirst(clazz.getSimpleName());
ioc.put(beanName,clazz.newInstance());
}else if(clazz.isAnnotationPresent(HLPService.class)){
//第一种形式,默认首字母小写
//第二种形式,如果自己起了名字,优先用自己定义的名字去匹配
//第三种形式,利用接口本身作为key,把其对应类的实例作为值
HLPService service= clazz.getAnnotation(HLPService.class);
String beanName = service.value();
if(!"".equals(beanName.trim())){
ioc.put(beanName,clazz.newInstance());
continue;
}
Object instance = clazz.newInstance();
beanName=lowerFirst(clazz.getSimpleName());
ioc.put(beanName,instance);
Class<?>[] interfaces = clazz.getInterfaces();
for(Class<?> i : interfaces)
{
ioc.put(i.getName(),instance);
}
}else{
continue;
}
}
}catch (Exception e ){
e.printStackTrace();
}
}
对所有加了@Controller的类都放到ioc容器了,其实就是一个map,key,value对;key是类名的首字母小写,value是类的对象。
但是@service有一些不同,key可能是serviceImpl的类名首字母小写,也可能是@service里自定义的名字,还可能是service接口的类名
这一步就是IOC,对象的生命周期交给容器管理
- 4.依赖注入,找到@autowired并把ioc容器中的实例动态注入
//4.依赖注入,di从ioc容器中找到加@autowired这个注解的属性,并找到其在ioc容器中的实例,动态赋值
private void doAutowired(){
if(ioc.isEmpty()){return;}
for(Map.Entry<String,Object> entry: ioc.entrySet() ){
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for(Field field: fields){
if(!field.isAnnotationPresent(HLPAutowired.class)){continue;}
HLPAutowired autowired = field.getAnnotation(HLPAutowired.class);
String beanName = autowired.value().trim();
if("".equals(beanName)){
beanName=field.getType().getName();
}
//即使是private,也要强吻
field.setAccessible(true);
//开始赋值
try {
field.set(entry.getValue(),ioc.get(beanName));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这一步是DI,在加了@autowired @requestMapping等注解的地方像为属性赋值一样 ,赋上类的实例。
这是一个和我之前想的不一样的地方,注入的时候不是遍历代码中的每个类的每个 方法,相反是遍历ioc容器里每一个key,value对的value对应的类上的注解。
之前ioc中已经有了key,value对,然后再建立一个key,value对,key是ioc容器中value也就是对象,value是注解比如@autowired中自定义的名字或者是类的类型。
- 5.把加了@requestMapping的方法与url对应起来,key是url,value是method
//5.把在@controller中加了@requestMapping这个注解的方法和url构造成一个对应关系,一个map结构,key是URL;value是method
private void initHandlerMapping(){
if(ioc.isEmpty()){return;}
for(Map.Entry<String,Object> entry: ioc.entrySet()){
Class<?> clazz = entry.getValue().getClass();
if(!clazz.isAnnotationPresent(HLPController.class)){continue;}
String url = "";
if(clazz.isAnnotationPresent(HLPRequestMapping.class)){
HLPRequestMapping requestMapping = clazz.getAnnotation(HLPRequestMapping.class);
url = requestMapping.value();
}
Method[] methods = clazz.getMethods();
for(Method method:methods){
if(!method.isAnnotationPresent(HLPRequestMapping.class)){continue;}
HLPRequestMapping hlpRequestMapping = method.getAnnotation(HLPRequestMapping.class);
String mapping =("/"+url+"/"+hlpRequestMapping.value()).replaceAll("/+","/") ;
handlerMapping.put(mapping,method);
}
}
}
- 6.等待用户请求,根据用户的url请求map中的method
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doDispatcher(req,resp);
}
private void doDispatcher(HttpServletRequest req, HttpServletResponse resp){
if(handlerMapping.isEmpty()){return;}
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replace(contextPath,"").replaceAll("/+","/");
Method method = handlerMapping.get(url);
}
一直HttpServletRequest如何获得url?
分三步:
1.获得 requestURL
String url = req.getRequestURI(); 这个包括http://应用名:端口号/程序名/类名/方法名;而只有类名/方法名是我们需要的,因为requestMapping组成的在ioc中的key,value对中key是这个。
2.获得http://应用名:端口号/程序名
request.getContextPath();
3.把url中contextPath这部分替换成空,剩下的/+替换成一个的/
url.replace(contextPath,”“).replaceAll(“/+”,”/”);