思路:
-
在项目部署的时候,就马上启动扫描的工作了。
a. 把扫描的工作提前放到servlet的init方法去做: init --- service - destroy
b. 让这个init方法调用的时机再提前一些,提前到项目发布的时候就执行。
c. 设置 <load-on-startup> 1</load-on-startup>
-
DispatcherServlet注册的时候,不要使用注解来注册了,而是使用xml来注册, 在xml里面注册的时候,就可以配置servlet的初始化参数,用户指定扫描具体那个包。 <init-param>
-
扫描得到包下的所有类了之后,不是每一个类我们都要查看它的所有方法有没有requestMapping这个注解
-
只看类上有没有一个注解@Controller , 这个注解是自定义的注解。
-
谁身上有这个注解,我们就解析这个类里面的所有方法。
-
-
在init方法里面完成扫描的工作之后,需要使用一个Map集合来进行映射关系,也就是完成扫描工作之后,使用一个map集合来包装 映射的地址和controller的对象。 map集合里面就包装了请求地址和调用方法的一个关系。 KEY : 请求地址 , value : 包装的javaBean。
class MethodBean{
private Method method; //具体的方法对象
private Object obj; //调用这个方法要用的实例对象
}
Map<String , MethodBean> map ;
MethodBean mb = new MethodBean(方法对象 , 类的实例对象);
map.put("/user/register" , mb);
-
在请求来的时候,在service里面获取请求的地址
-
截获请求的地址了之后,就可以直接问map要方法来调用。
MethodBean mb = map.get("/user/register");
Method m = mb.getMethod();
m.invoke(mb.getObj() , req , resp);
-
在用户请求到来之前执行扫描包的操作
//工具类
import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* @Description:扫描包下的类
* @Author: yp
*/
public class ClassScannerUtils {
/**
* 获得包下面的所有的class
* @param
* @return List包含所有class的实例
*/
public static List<Class<?>> getClasssFromPackage(String packageName) {
List clazzs = new ArrayList<>();
// 是否循环搜索子包
boolean recursive = true;
// 包名对应的路径名称
String packageDirName = packageName.replace('.', '/');
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
findClassInPackageByFile(packageName, filePath, recursive, clazzs);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return clazzs;
}
/**
* 在package对应的路径下找到所有的class
*/
public static void findClassInPackageByFile(String packageName, String filePath, final boolean recursive,
List<Class<?>> clazzs) {
File dir = new File(filePath);
if (!dir.exists() || !dir.isDirectory()) {
return;
}
// 在给定的目录下找到所有的文件,并且进行条件过滤
File[] dirFiles = dir.listFiles(new FileFilter() {
public boolean accept(File file) {
boolean acceptDir = recursive && file.isDirectory();// 接受dir目录
boolean acceptClass = file.getName().endsWith("class");// 接受class文件
return acceptDir || acceptClass;
}
});
for (File file : dirFiles) {
if (file.isDirectory()) {
findClassInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, clazzs);
} else {
String className = file.getName().substring(0, file.getName().length() - 6);
try {
clazzs.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + "." + className));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
//servlet包
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DispatcherServlet extends HttpServlet {
Map<String, MethodBean> map = new HashMap<>();
@Override
public void init(ServletConfig config) throws ServletException {
try {
//在用户请求到来之前便执行初始化扫描包的操作
//获取需要初始化的包名
String packageName = config.getInitParameter("packageName");
//扫描得到的包下面的所以字节码
//得到有个list集合,也就是包下所有类的集合
List<Class<?>> classList = ClassScannerUtils.getClasssFromPackage(packageName);
//遍历得到的集合
for (Class<?> clazz : classList) {
//判断这个集合里面的类是否有注解Controller
if (clazz.isAnnotationPresent(Controller.class)) {
//有这个注解,那就获取这个注解下面的类中的所有方法得到一个方法数组
Method[] methods = clazz.getMethods();
//变量得到的方法数组,每遍历一次,获取方法上面的注解ResqustMapping
for (Method method : methods) {
ResqustMapping annotation = method.getAnnotation(ResqustMapping.class);
//判断这些方法上面的注解ResqustMapping是否为空
//不为空的话就获取注解中的值
if (annotation != null) {
String value = annotation.value();
//得到值后存储封装在map集合中
MethodBean methodBean = new MethodBean(method ,clazz.newInstance() );
map.put(value, methodBean);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
//获取地址
String uir = req.getRequestURI();
//截取地址
String path = uir.substring(0, uir.lastIndexOf("."));
//用截取到的地址去map集合中找对象
//那就得创建map集合对象
MethodBean methodBean = map.get(path);
//判断有没有执行到方法
if (methodBean != null) {
//不为空,执行到了,便获取参数
Method method = methodBean.getMethod();
Object object = methodBean.getObject();
//调用方法
method.invoke(object , req , resp);
}else {
System.out.println("没有找到方法......"+path);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//controller 需要扫描的包
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
public class UserController {
@ResqustMapping("/user/register")
public void register(HttpServletRequest req, HttpServletResponse resp){
System.out.println("调用了UserControllor的用户注册......register");
}
@ResqustMapping("/user/login")
public void login(HttpServletRequest req, HttpServletResponse resp){
System.out.println("调用了UserControllor的用户登录......login");
}
}
//bean 包
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.lang.reflect.Method;
@Data
@AllArgsConstructor//有参
@NoArgsConstructor//无参
@ToString
public class MethodBean {
private Method method;
private Object object;
}
//定义注解的包annotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)//表示在方法上面打注解
@Retention(RetentionPolicy.RUNTIME)//表示这个注解的生命周期
//这个注解是给类下面的方法做标记
public @interface ResqustMapping {
String value();
}
//定义注解的包annotation
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)//这个注解的生命周期直到运行阶段都还存在
//定义一个注解作为类的标记,这个注解为空的
public @interface Controller {
}
<!--相关依赖 pom.xml-->
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- 指定端口 -->
<port>82</port>
<!-- 请求路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
<!--web.xml文件中定义需要扫描包的操作,初始化-->
<!--注册DispatcherServlet-->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>servlet/DispatcherServlet</servlet-class>
<!--告诉DispatcherServlet要扫描的包是哪个-->
<init-param>
<param-name>packageName</param-name>
<param-value>controller</param-value>
</init-param>
<!--让DispatcherServlet在项目发布的时候,就进行初始化,执行init方法-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>