自己实现SpringMVC 底层机制[二]
实现任务阶段3- 从web.xml 动态获取myspringmvc.xml
前面我们加载myspringmvc.xml 是硬编码, 现在做活, 从web.xml 动态获取,
分图析示意
MyDispatcherServlet 在创建并初始化MyWebApplicationContext,动态的从web.xml 中获取到配置文件.
代码实现
修改my-springmvc\src\main\java\com\myspringmvc\context\MyWebApplicationContext.java
public class MyWebApplicationContext { //定义属性classFullPathList, 保存扫描包/子包的类的全路径 private List<String> classFullPathList = new ArrayList<>(); //定义属性ioc, 存放反射生成的Bean对象 /Controller/Service public ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>(); //无参构造器 public MyWebApplicationContext() { } private String configLocation;//属性,表示spring容器配置文件 public MyWebApplicationContext(String configLocation) { this.configLocation = configLocation; } //编写方法,完成自己的spring容器的初始化 public void init() { //这里是写的固定的spring容器配置文件=>做活 //String basePackage = XMLParser.getBasePackage("myspringmvc.xml"); String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]); //这时basePackage => com.controller,com.service String[] basePackages = basePackage.split(","); //判断包是否为空 if (basePackages.length > 0) { //遍历basePackages, 进行扫描 for (String pack : basePackages) { scanPackage(pack);//扫描包保存路径放在集合,便于后面进行反射到容器 } } System.out.println("扫描后的= classFullPathList=" + classFullPathList);//测试得到的路径集合是否正确 //将扫描到的类, 反射到ico容器 executeInstance(); System.out.println("扫描后的 ioc容器= " + ioc); } }
修改my-springmvc\src\main\java\com\myspringmvc\servlet\MyDispatcherServlet.java
@Override public void init(ServletConfig servletConfig) throws ServletException { //获取到web.xml中的 contextConfigLocation /* <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:myspringmvc.xml</param-value> </init-param> */ String configLocation = servletConfig.getInitParameter("contextConfigLocation"); //创建自己的spring容器 myWebApplicationContext = new MyWebApplicationContext(configLocation); myWebApplicationContext.init();//完成容器初始化 //调用 initHandlerMapping , 完成url和控制器方法的映射 initHandlerMapping(); //输出handlerList System.out.println("handlerList初始化的结果= " + handlerList); }
完成测试(启动tomcat 方式, 修改后,redeploye 即可生效)。
实现任务阶段4- 完成自定义@Service 注解功能。
功能说明: 如果给某个类加上@Service, 则可以将其注入到我们的Spring 容器
分析示意图
给Service 类标注@Service, 可以将对象注入到Spring 容器中。
可以通过接口名支持多级类名来获取到Service Bean。
如下
//扫描后的ioc ioc={monsterService=com.service.impl.MonsterServiceImpl@6323a1c1, orderController=com.controller.OrderController@4c96ec4f, monsterController=com.controller.MonsterController@81664b3}
代码实现
创建my-springmvc\src\main\java\com\entity\Monster.java
public class Monster { private Integer id; private String name; private String skill; private Integer age; public Monster(Integer id, String name, String skill, Integer age) { this.id = id; this.name = name; this.skill = skill; this.age = age; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSkill() { return skill; } public void setSkill(String skill) { this.skill = skill; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Monster{" + "id=" + id + ", name='" + name + '\'' + ", skill='" + skill + '\'' + ", age=" + age + '}'; } }
创建my-springmvc\src\main\java\com\myspringmvc\annotation\Service.java
// Service 注解,用于标识一个Service对象,并注入到spring容器 @Target(ElementType.TYPE)//该注解只能声明在一个类前 @Retention(RetentionPolicy.RUNTIME)//不仅被保存到class文件中,jvm加载class文件之后,仍然存在; @Documented// java 在生成文档,显示注解 public @interface Service { String value() default ""; }
创建my-springmvc\src\main\java\com\service\MonsterService.java
public interface MonsterService{ //增加方法-返回monster列表 public List<Monster> listMonster(); }
创建my-springmvc\src\main\java\com\service\impl\MonsterServiceImpl.java
@Service public class MonsterServiceImpl implements MonsterService { @Override public List<Monster> listMonster() { //这里模拟数据->DB List<Monster> monsters = new ArrayList<>(); monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400)); monsters.add(new Monster(200, "老猫妖怪", "抓老鼠", 200)); return monsters; } }
修改my-springmvc\src\main\resources\myspringmvc.xml
<?xml version="1.0" encoding="utf-8" ?> <beans> <!--指定要扫描的基本包以及子包的java类--> <component-scan base-package="com.controller,com.service"></component-scan> </beans>
修改my-springmvc\src\main\java\com\myspringmvc\context\MyWebApplicationContext.java
//编写方法,将扫描到的类, 在满足条件的情况下,反射到ioc容器 //实例化扫描到的类->创建对象->放入到IOC 容器[ConcurrentHashMap] public void executeInstance() { //判断是否扫描到类 if (classFullPathList.size() == 0) {//说明没有扫描到类 return; } try { //遍历classFullPathList,进行反射 for (String classFullPath : classFullPathList) { Class<?> clazz = Class.forName(classFullPath); //说明当前这个类有@Controller if (clazz.isAnnotationPresent(Controller.class)) { //得到类名首字母小写 String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1); ioc.put(beanName, clazz.newInstance()); } //如果有其它的注解,可以扩展 , 来处理@Service else if (clazz.isAnnotationPresent(Service.class)) {//如果类有@Serivce注解 //先获取到Service的value值=> 就是注入时的beanName Service serviceAnnotation = clazz.getAnnotation(Service.class); String beanName = serviceAnnotation.value(); if ("".equals(beanName)) {//说明没有指定value, 我们就使用默认的机制注入Service //可以通过接口名/类名[首字母小写]来注入ioc容器 //1.得到所有接口的名称=>反射 Class<?>[] interfaces = clazz.getInterfaces(); Object instance = clazz.newInstance(); //2. 遍历接口,然后通过多个接口名来注入 for (Class<?> anInterface : interfaces) { //接口名->首字母小写 String beanName2 = anInterface.getSimpleName().substring(0, 1).toLowerCase() + anInterface.getSimpleName().substring(1); ioc.put(beanName2, instance); } } else {//如果有指定名称,就使用该名称注入即可 ioc.put(beanName, clazz.newInstance()); } } } } catch (Exception e) { e.printStackTrace(); } }
完成测试(启动Tomcat, 自动加载MyDispatcherServlet, 完成IOC 容器的注入)。
扫描后的ioc= {monsterService=com.service.impl.MonsterServiceImpl@6323a1c1, orderController=com.controller.OrderController@4c96ec4f, monsterController=com.controller.MonsterController@81664b3}