序文
春の使い方を学び、SpringMVCは非常に高速ですが、必然的原則の枠組みの背後にあるものを探求したくなるか、将来的に使用されるプロセスでは、この記事では、ビューのコードポイントから直接フレーム、springMVCフレームの簡易版でどのように手書きを説明します配信要求、および依存関係反転制御注入が実現する方法です。
次のアドレスのサンプルソースコードの読み取り、githubのと推奨事項:
https://github.com/liuyj24/mini-spring
構築するプロジェクト
右のjarパッケージ管理ツールを選択するために、参照することができ、プロジェクトを構築するためのGitHubプロジェクトは、MavenとのGradleはGradleのを使用してこのプロジェクトを行います。
(音符がフレームワークテストモジュールのモジュールに依存する)他の試験であり、アプリケーション・フレームワークをテストし、2つのモジュールを構築する項目であり、それがフレームを書き込むために使用されるフレームワークです。
続いて、フレームへの書き込みアクセスが完了した後、スプリングモジュールフレームワークに対応するパケットに応じて豆、コア、コンテキスト、Webモジュールを作成しました。
リクエスト分布
ウェブ全体をとかすために最初の要求分布モデルの前に話すこと:
- まず、ユーザクライアントは、Webサーバリスニングポートに渡されるオペレーティング・システムのTCP / IPスタックによって解析されたサーバに要求を送信します。
- 要求は、対応する処理手順に分配された後、ウェブサーバは、要求をリッスンします。TomcatのJavaプログラムは、分散処理に対応する(サーブレット)を要求するような、Webサーバ自体は、要求を処理していません。
そのような春のブートフレームワークとして、プロジェクトの選択Tomcat Webサーバー、および、プロジェクトがアップで直接実行させるプロジェクトに組み込まれたTomcatを選択するためには、テストを容易にするため、時間のキーテストとしてやって起動することができるようになります。
サーブレット
選択は、サーバー側のプログラムを書くためにJavaを使用するので、Servletインタフェースを言及する必要があります。サーバとJavaプログラムの間の通信を標準化するために、公式のJavaサーブレット仕様を開発、サーバー・サイドのJavaアプリケーションは、このインターフェースを実装する必要があり、サーバ・プロセスとしてのJava言語は、受信およびサーブレット仕様に従ってしなければなりません。
まだ春の前に、それが開発しているように、Webアプリケーションは、次のとおりです。ビジネスロジックは、web.xmlファイルと呼ばれる構成ファイルにサーブレットの構成が多数となり、複数のサーブレットがあるでしょう大きなプロジェクトで、サーブレットに対応し、サーバが実行されている場合、Tomcatのweb.xmlファイルは、要求URIに応じて扱うサービス要求に対応するサーブレットがあります。
しかし、あなたは、サーブレットを作成するための要求、およびサーブレットの実装クラスに、それぞれが、我々は通常、唯一の4つの方法に加えて、serviceメソッドを書き換えたいあまりにもリソースの無駄をこれを達成空にしています。そして、組紐プログラムは、管理が困難サーブレットの多くを作成します。我々はそれを改善することができますか?
春のDispatcherServlet的
この方法は、持っています:
さまざまな方法で配布リクエストURIに基づいて、サーブレットで、その後Webサーバがサーブレットにリクエストを送信するように、私たちは、思考に変えることができ、そして、私たちは別のサーブレットへの要求を分散するために、Webサーバー経由であることが判明し、チャートから見ることができます処理のため。
その結果、関係なく要求を受信したものを、Webサーバが複数のサーブレットによって引き起こされる問題を回避するために、同じサーブレット(DispatcherServletの)に配布されません、次のような利点があります。
- このステップは、制御が容易であり、内側フレームにWebサーバから配信要求を移動させるだけでなく、拡張を容易にします。
- 同じアプローチは、同じサービスクラスに焦点を当てたこのタイプのコントローラと命名することができ、コントローラは、クラッタの分散ように構成された処理方法を複数有しています。
- ウリは、設定ファイルを使用できない場合、パスをマップするように構成され、コンフィギュレーションは、集中設定、大規模で複雑な問題を解決するためのアプローチにコメントを直接使用することができます。
実用的な操作
記事の冒頭でソース勧告はリファレンスを与えられます
- 我々は、動的に構成情報をフレームの開始を得ることができますコメントで、コントローラー、RequestMapping、RequestParam:まずweb.mvcパッケージ内の3つのノートを作成します。
- 処理方法は注釈されているので、注釈付き構文解析クラスの順序は、全ての第一は、ソースコードClassScannerコアパッケージに対応するカテゴリの下に、関連するプロジェクトを得ます
public class ClassScanner {
public static List<Class<?>> scanClass(String packageName) throws IOException, ClassNotFoundException {
//用于保存结果的容器
List<Class<?>> classList = new ArrayList<>();
//把文件名改为文件路径
String path = packageName.replace(".", "/");
//获取默认的类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//通过文件路径获取该文件夹下所有资源的URL
Enumeration<URL> resources = classLoader.getResources(path);
int index = 0;//测试
while(resources.hasMoreElements()){
//拿到下一个资源
URL resource = resources.nextElement();
//先判断是否是jar包,因为默认.class文件会被打包为jar包
if(resource.getProtocol().contains("jar")){
//把URL强转为jar包链接
JarURLConnection jarURLConnection = (JarURLConnection)resource.openConnection();
//根据jar包获取jar包的路径名
String jarFilePath = jarURLConnection.getJarFile().getName();
//把jar包下所有的类添加的保存结果的容器中
classList.addAll(getClassFromJar(jarFilePath, path));
}else{//也有可能不是jar文件,先放下
//todo
}
}
return classList;
}
/**
* 获取jar包中所有路径符合的类文件
* @param jarFilePath
* @param path
* @return
*/
private static List<Class<?>> getClassFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
List<Class<?>> classes = new ArrayList<>();//保存结果的集合
JarFile jarFile = new JarFile(jarFilePath);//创建对应jar包的句柄
Enumeration<JarEntry> jarEntries = jarFile.entries();//拿到jar包中所有的文件
while(jarEntries.hasMoreElements()){
JarEntry jarEntry = jarEntries.nextElement();//拿到一个文件
String entryName = jarEntry.getName();//拿到文件名,大概是这样:com/shenghao/test/Test.class
if (entryName.startsWith(path) && entryName.endsWith(".class")){//判断是否是类文件
String classFullName = entryName.replace("/", ".")
.substring(0, entryName.length() - 6);
classes.add(Class.forName(classFullName));
}
}
return classes;
}
}
- MappingHandlerパッケージハンドラは、クラスを作成し、将来のフレームの動作中に、MappingHandlerは、ユーザーを高めるために、例えば、サービスロジックに対応しています。だから、MappingHandlerに「uriパラメータ要求、処理方法、方法、でカテゴリメソッド」リクエストURIのURIをマッチングするための要求は、この処理は3回の実行のための反射パラメータによってコールバックされた4つのフィールドが、持っています方法
public class MappingHandler {
private String uri;
private Method method;
private Class<?> controller;
private String[] args;
MappingHandler(String uri, Method method, Class<?> cls, String[] args){
this.uri = uri;
this.method = method;
this.controller = cls;
this.args = args;
}
public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
//拿到请求的uri
String requestUri = ((HttpServletRequest)req).getRequestURI();
if(!uri.equals(requestUri)){//如果和自身uri不同就跳过
return false;
}
Object[] parameters = new Object[args.length];
for(int i = 0; i < args.length; i++){
parameters[i] = req.getParameter(args[i]);
}
Object ctl = BeanFactory.getBean(controller);
Object response = method.invoke(ctl, parameters);
res.getWriter().println(response.toString());
return true;
}
}
- 静的MappingHandlerコレクションを持っているハンドラパッケージHandlerManagerクラスを作成する次に、このクラスの役割は、すべてのクラスから取得された各@ReqeustMappingを注釈付きの注釈付き@Controllerクラスとコントローラのクラスを見つけますその後、MappingHandlerへのパッケージングの方法、および静的セットMappingHandlerに入れます
public class HandlerManager {
public static List<MappingHandler> mappingHandlerList = new ArrayList<>();
/**
* 处理类文件集合,挑出MappingHandler
* @param classList
*/
public static void resolveMappingHandler(List<Class<?>> classList){
for(Class<?> cls : classList){
if(cls.isAnnotationPresent(Controller.class)){//MappingHandler会在controller里面
parseHandlerFromController(cls);//继续从controller中分离出一个个MappingHandler
}
}
}
private static void parseHandlerFromController(Class<?> cls) {
//先获取该controller中所有的方法
Method[] methods = cls.getDeclaredMethods();
//从中挑选出被RequestMapping注解的方法进行封装
for(Method method : methods){
if(!method.isAnnotationPresent(RequestMapping.class)){
continue;
}
String uri = method.getDeclaredAnnotation(RequestMapping.class).value();//拿到RequestMapping定义的uri
List<String> paramNameList = new ArrayList<>();//保存方法参数的集合
for(Parameter parameter : method.getParameters()){
if(parameter.isAnnotationPresent(RequestParam.class)){//把有被RequestParam注解的参数添加入集合
paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value());
}
}
String[] params = paramNameList.toArray(new String[paramNameList.size()]);//把参数集合转为数组,用于反射
MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params);//反射生成MappingHandler
mappingHandlerList.add(mappingHandler);//把mappingHandler装入集合中
}
}
}
- 上記の4つのステップが完了した後、我々はあなたに、反射によって治療の対応するメソッドを呼び出すことができ、要求が来たとき、我々は唯一の要求に応じて設定され、対応するURI MappingHandlerを見つけ、フレームはMappingHandlerセットを取得した時点で開始しますこれは、分布関数を要求するためのフレームワークを完了します。
制御の反転と依存性注入
リクエスト分布関数が終了した後、さらに、このような質問を考えます:
要求は、プロセスA、B、Cの3つのオブジェクトを作成する必要があることを今仮定し、
Aは、フィールドDを有する
フィールドD Bを有し
CのBフィールドはました
注文にABCを作成する場合、その後、
最初に、次にAを作成し、Dを作成する必要があり、
次いで、Dを作成し、次いで、Bを作成し、
次いで、Dを作成し、次にCを作成するために、Bを作成する
Aの合計を作成、2つのB、C、3 D.
上記の目的は、重複したオブジェクトの数が多いの創出につながっの参照を繰り返すことはできませんので、それを見ることができる、我々はプログラムを書くのオブジェクトを作成するための一つの方法です。
この問題を解決するために、スプリングBeanは、あなたは、オブジェクトとして理解Beanを置くことができ、そのような概念を提案したが、彼は普通のオブジェクトは、次のような特徴を持っている対照的。
- はかない、長いライフサイクルなどの通常のオブジェクトとは異なり、
- 可視、通常のオブジェクトとは異なり、コードブロック内の全仮想マシン内でのみ表示され
- 単一の実施形態の形で存在し、高いメンテナンスコスト、
上記のBeanを作るために、私たちは豆の工場を持っている必要があり、ビーンファクトリ原理は単純です:必要性から直接Beanを使用する際に、(使用時にも作成することができます)初期化時間枠に関連するBeanを作成します植物を取ります。つまり、私たちはある、フレームワークにオブジェクトを作成するための電源を入れて制御の反転
あなたは豆の植物ABC系列を作成したら、次のように:
まず、植物に、AをAを作成し、その後植物にD、Dを作成し、かつ、
Bを作成するために、プラントDから来た後、Bもあります;植物へ
工場一つから来る、Bは、Cを作成し、Cはまた、工場内に配置され、その後、
合計がA、1、B、C、D作成
到達目標オブジェクトの再利用を
Dを作成するように、その後、Dが呼び出され、そのようなプロセスのフィールドAに設定されている依存性注入
だから、コントロールと依存性注入の概念の反転は非常によく理解し、コントロール、依存性注入と制御の反転のイデオロギー的反転は、実際の具体的な実現されています。
実用的な操作
- まず@Bean @Autowiredと、フレームパーサクラス豆パッケージに2つのアノテーションを作成します。
- 次に、豆のマップのセットを静的getBean()メソッドを持っているために彼を必要とする機能、のインスタンスを取得するクラスによる方法を提供し、保存できるようにしたBeanFactory、たBeanFactoryでビーンバッグを作成します。
- ビーンを初期化するには、解析されたBeanクラスファイルに従って一連のメソッドを持っています。この方法では、注釈付きのカテゴリ豆抽出物に属し、そのクラスのオブジェクトを作成し、静的なコレクションを入れて、すべてのクラスのセットを横断します。
- Beanを作成するにはどのような順序で - ここで興味深い点はありますか?Beanを作成するために、現在のサイクルをスキップするために使用されていない場合は、この論文では、ソースは、Beanが直接作成し、他の豆に依存していない場合は、他のBeanのBeanが作成されていない見て他人に依存して存在する場合、豆、あなたは現在のBeanを作成した場合。
- Beanを作成するための処理ループにおける豆現象の一種の間の可能な相互依存性は、ソースコードは、この現象に対してではなく、治療のためにスローされます。
public class BeanFactory {
//保存Bean实例的映射集合
private static Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();
/**
* 根据class类型获取bean
* @param cls
* @return
*/
public static Object getBean(Class<?> cls){
return classToBean.get(cls);
}
/**
* 初始化bean工厂
* @param classList 需要一个.class文件集合
* @throws Exception
*/
public static void initBean(List<Class<?>> classList) throws Exception {
//先创建一个.class文件集合的副本
List<Class<?>> toCreate = new ArrayList<>(classList);
//循环创建bean实例
while(toCreate.size() != 0){
int remainSize = toCreate.size();//记录开始时集合大小,如果一轮结束后大小没有变证明有相互依赖
for(int i = 0; i < toCreate.size(); i++){//遍历创建bean,如果失败就先跳过,等下一轮再创建
if(finishCreate(toCreate.get(i))){
toCreate.remove(i);
}
}
if(toCreate.size() == remainSize){//有相互依赖的情况先抛出异常
throw new Exception("cycle dependency!");
}
}
}
private static boolean finishCreate(Class<?> cls) throws IllegalAccessException, InstantiationException {
//创建的bean实例仅包括Bean和Controller注释的类
if(!cls.isAnnotationPresent(Bean.class) && !cls.isAnnotationPresent(Controller.class)){
return true;
}
//先创建实例对象
Object bean = cls.newInstance();
//看看实例对象是否需要执行依赖注入,注入其他bean
for(Field field : cls.getDeclaredFields()){
if(field.isAnnotationPresent(AutoWired.class)){
Class<?> fieldType = field.getType();
Object reliantBean = BeanFactory.getBean(fieldType);
if(reliantBean == null){//如果要注入的bean还未被创建就先跳过
return false;
}
field.setAccessible(true);
field.set(bean, reliantBean);
}
}
classToBean.put(cls, bean);
return true;
}
}
- あなたは豆の工場を持っていたら、豆が使用する任意の場所には直接豆の工場を取ったことができます
- 最後に、我々は彼らのフレームワークが正しく応答を完了するために要求を処理するかどうかの小さなデモテストを書くことができます。私は、全体のフレームワークミニラインとダウン、Springのコア機能だけでなく、制御の反転、制御条件に依存しなくなったあなたの心の中だけの概念はなく、線で明確なコード行を信じています。