この記事では、AOP の実際の部分でもある Spring Boot の統合関数処理モジュールについて学習します。
-
ユーザーログイン権限検証実装インターフェース
HandlerInterceptor
+WebMvcConfigurer
-
例外処理はアノテーションを使用します
@RestControllerAdvice
+@ExceptionHandler
-
データ形式はアノテーションを使用して戻り
@ControllerAdvice
、インターフェイスを実装します。@ResponseBodyAdvice
1. 統一ユーザーのログイン権限確認
ユーザーログイン権限の開発・改善プロセス
-
初期ユーザーログイン検証:各メソッドでセッションとセッション内のユーザー情報を取得します。ユーザーが存在する場合、ログインは成功とみなされ、存在しない場合、ログインは失敗します。
-
ユーザー ログイン認証の 2 番目のバージョン:統一されたメソッドを提供し、判断するために検証する必要がある各メソッドで統一されたユーザー ログイン ID 検証メソッドを呼び出します。
-
ユーザー ログイン検証の 3 番目のバージョン: Spring AOP を使用して、統合されたユーザー ログイン検証を実行します。
-
ユーザー ログイン検証の 4 番目のバージョン: Spring インターセプターを使用して、ユーザーの統合ログイン検証を実現します。
1.1 初回ユーザーのログイン権限確認
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/a1")
public Boolean login (HttpServletRequest request) {
// 有 Session 就获取,没有就不创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,进行业务处理
return true;
} else {
// 未登录
return false;
}
}
@RequestMapping("/a2")
public Boolean login2 (HttpServletRequest request) {
// 有 Session 就获取,没有就不创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,进行业务处理
return true;
} else {
// 未登录
return false;
}
}
}
この方法で記述されたコードは、各メソッドで同じユーザー ログイン検証権限を持ちますが、欠点は次のとおりです。
-
各メソッドはユーザーのログイン検証のメソッドを個別に記述する必要があり、パブリック メソッドにカプセル化されている場合でも、パラメータを渡してメソッド内で呼び出して判定する必要があります。
-
コントローラーが追加されると、ユーザーのログイン検証のために呼び出されるメソッドが増えるため、後の変更やメンテナンスの成功率が高くなります。
-
これらのユーザーログイン認証メソッドは、これから実装する業務とはほとんど関係がありませんが、それぞれのメソッドを再度記述する必要があるため、統一されたユーザーログイン権限認証メソッドを公開する AOP メソッドを提供することは非常に良い解決策です。
1.2 Spring AOP 統合ユーザーログイン認証
統合ユーザーログイン認証の実装方法として最初に考えられるのは、Spring AOP の事前通知またはサラウンド通知を使用して実現することです。
@Aspect // 当前类是一个切面
@Component
public class UserAspect {
// 定义切点方法 Controller 包下、子孙包下所有类的所有方法
@Pointcut("execution(* com.example.springaop.controller..*.*(..))")
public void pointcut(){}
// 前置通知
@Before("pointcut()")
public void doBefore() {}
// 环绕通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) {
Object obj = null;
System.out.println("Around 方法开始执行");
try {
obj = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("Around 方法结束执行");
return obj;
}
}
ただし、ユーザーのログイン権限検証の機能が上記のコード Spring AOP の側面でのみ実装されている場合、次の 2 つの問題が発生します。
-
HttpSession
オブジェクトを取得してリクエストする方法はありません -
一部のメソッドはインターセプトする必要がありますが、他のメソッドは必要ありません。たとえば、登録メソッドとログイン メソッドはインターセプトされません。つまり、実際のインターセプト ルールは非常に複雑で、単純なアスペクト J 式ではインターセプト要件を満たすことができません。
1.3 スプリングインターセプター
上記のコードにおける Spring AOP の問題に対応して、Spring は特定の実装インターセプターを提供します。HandlerInterceptor
インターセプターの実装には 2 つのステップがあります。
1. カスタム インターセプターを作成し、 Spring のHandlerInterceptor
インターフェイスに preHandle メソッドを実装します。
2. カスタム インターセプターをフレームワークの構成に追加し、インターセプト ルールを設定します。
-
現在のクラス
@Configuration
に注釈を付ける -
WebMvcConfigurer
インターフェースを実装する -
オーバーライド
addInterceptors
メソッド
注: プロジェクト内で複数のインターセプターを同時に構成できます。
(1) カスタムインターセプターの作成
/**
* @Description: 自定义用户登录的拦截器
* @Date 2023/2/13 13:06
*/
@Component
public class LoginIntercept implements HandlerInterceptor {
// 返回 true 表示拦截判断通过,可以访问后面的接口
// 返回 false 表示拦截未通过,直接返回结果给前端
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// 1.得到 HttpSession 对象
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 表示已经登录
return true;
}
// 执行到此代码表示未登录,未登录就跳转到登录页面
response.sendRedirect("/login.html");
return false;
}
}
(2) カスタムインターセプタをシステム構成に追加し、インターセプトルールを設定します
-
addPathPatterns
: インターセプトする必要がある URL を示し、**
すべてのメソッドがインターセプトされることを示します -
excludePathPatterns
: 除外する必要がある URL を示します。
説明: インターセプト ルールは、静的ファイル (画像ファイル、JS および CSS ファイルなど) を含む、このプロジェクトで使用される URL をインターセプトできます。
/**
* @Description: 将自定义拦截器添加到系统配置中,并设置拦截的规则
* @Date 2023/2/13 13:13
*/
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Resource
private LoginIntercept loginIntercept;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(new LoginIntercept());//可以直接new 也可以属性注入
registry.addInterceptor(loginIntercept).
addPathPatterns("/**"). // 拦截所有 url
excludePathPatterns("/user/login"). //不拦截登录注册接口
excludePathPatterns("/user/reg").
excludePathPatterns("/login.html").
excludePathPatterns("/reg.html").
excludePathPatterns("/**/*.js").
excludePathPatterns("/**/*.css").
excludePathPatterns("/**/*.png").
excludePathPatterns("/**/*.jpg");
}
}
1.4 演習: ログイン インターセプタ
必須
-
ログインおよび登録ページはブロックされませんが、他のページはブロックされます
-
ログインがセッションに正常に書き込まれると、インターセプトされたページに通常どおりアクセスできるようになります。
1.3 では、カスタム インターセプターが作成されてシステム構成に追加され、インターセプトのルールが設定されました。
(2) controller
パッケージを作成し、パッケージ内に作成し UserController
、ログインページとホームページの業務コードを記述します
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public boolean login(HttpServletRequest request,String username, String password) {
boolean result = false;
if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
if(username.equals("admin") && password.equals("admin")) {
HttpSession session = request.getSession();
session.setAttribute("userinfo","userinfo");
return true;
}
}
return result;
}
@RequestMapping("/index")
public String index() {
return "Hello Index";
}
}
(3) プログラムを実行し、ページにアクセスし、ログイン前とログイン後の効果を比較します。
1.5 インターセプタの実装原理
インターセプターでは、コントローラーを呼び出す前に対応する業務処理が実行されます。実行プロセスは下図に示されています。
実装原理のソースコード解析
すべての 実行はController
スケジューラを通じて DispatcherServlet
行われます
すべてのメソッドはスケジューリング メソッドDispatcherServlet
で 実行され 、 ソース コード分析は次のようになります。doDispatch
doDispatch
ソースコード分析を通じて、Spingのインターセプタも動的プロキシとサラウンド通知のアイデアを通じて実現されていることがわかります
1.6 ユニファイドアクセスプレフィックスの追加
すべてのリクエスト アドレスに API プレフィックスを追加します。c はすべてを意味します
@Configuration
public class AppConfig implements WebMvcConfigurer {
// 所有的接口添加 api 前缀
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("api", c -> true);
}
}
2. 統一された例外処理
@ControllerAdvice
コントローラー通知クラスを現在のクラスに追加します 。
例外ハンドラーを示すメソッドに追加し @ExceptionHandler(xxx.class)
、例外によって返されるビジネス コードを追加します。
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/index")
public String index() {
int num = 10/0;
return "Hello Index";
}
}
config パッケージで MyExceptionAdvice
クラスを作成します
@RestControllerAdvice // 当前是针对 Controller 的通知类(增强类)
public class MyExceptionAdvice {
@ExceptionHandler(ArithmeticException.class)
public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) {
HashMap<String, Object> result = new HashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg" , "算出异常:"+ e.getMessage());
return result;
}
}
このように書いても効果は同じです
@ControllerAdvice
public class MyExceptionAdvice {
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) {
HashMap<String, Object> result = new HashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg" , "算数异常:"+ e.getMessage());
return result;
}
}
別の null ポインター例外がある場合、上記のコードは機能せず、null ポインター例外のハンドラーを作成する必要があります。
@ExceptionHandler(NullPointerException.class)
public HashMap<String,Object> nullPointerExceptionAdvice(NullPointerException e) {
HashMap<String, Object> result = new HashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg" , "空指针异常异常:"+ e.getMessage());
return result;
}
@RequestMapping("/index")
public String index(HttpServletRequest request,String username, String password) {
Object obj = null;
System.out.println(obj.hashCode());
return "Hello Index";
}
ただし、考慮すべき点は、すべての例外をこのように記述すると作業負荷が非常に大きくなり、カスタム例外もあるため、上記のような書き方は絶対に良くありません。例外なので、直接 Exception を記述してください。すべての例外の親クラスです。上に書かれていない 2 つの例外が発生した場合、それは Exception に直接一致します。
複数の例外通知がある場合、一致する順序は、現在のクラスとそのサブクラスが上位に一致することになります。
@ExceptionHandler(Exception.class)
public HashMap<String,Object> exceptionAdvice(Exception e) {
HashMap<String, Object> result = new HashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg" , "异常:"+ e.getMessage());
return result;
}
優先順位の一致は、以前に記述された null ポインタ例外のままであることがわかります。
3. 統一データフォーマットの返却
3.1 データフォーマットの統一返却の実現
(1) 現在のクラスに追加する @ControllerAdvice
(2) ResponseBodyAdvice
メソッドの書き換えを実装する
-
supports
メソッド。このメソッドはコンテンツを書き換える必要があるかどうかを示します (このメソッドを通じて、一部のコントローラーとメソッドを選択的に書き換えることができます)。書き換えられた場合は true を返します。 -
beforeBodyWrite
メソッドの場合、このメソッドはメソッドが返される前に呼び出されます。
@ControllerAdvice
public class MyResponseAdvice implements ResponseBodyAdvice {
// 返回一个 boolean 值,true 表示返回数据之前对数据进行重写,也就是会进入 beforeBodyWrite 方法
// 返回 false 表示对结果不进行任何处理,直接返回
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
// 方法返回之前调用此方法
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
HashMap<String,Object> result = new HashMap<>();
result.put("state",1);
result.put("data",body);
result.put("msg","");
return result;
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public boolean login(HttpServletRequest request,String username, String password) {
boolean result = false;
if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
if(username.equals("admin") && password.equals("admin")) {
HttpSession session = request.getSession();
session.setAttribute("userinfo","userinfo");
return true;
}
}
return result;
}
@RequestMapping("/reg")
public int reg() {
return 1;
}
}
3.2 @ControllerAdvice ソースコード分析
@ControllerAdvice
ソースコードの分析を通じて 、上記の統合例外と統合データリターンの実行プロセスを知ることができます。
(1) まずは @ControllerAdvice のソースコードを見てみましょう
@ControllerAdvice
これは @Component
コンポーネントから派生しており、すべてのコンポーネントの初期化が InitializingBean
インターフェイスを呼び出すことがわかります。
(2)initializeBeanがどの実装クラスを持っているか確認してみよう
クエリ処理中に、Spring MVC の実装サブクラスには、すべてのパラメーターの設定が完了した後に実行されるメソッドを示すメソッドがRequestMappingHandlerAdapter
存在すること がわかりました 。afterPropertiesSet()
(3) このメソッドには initControllerAdviceCache メソッドがあり、このメソッドをクエリします
このメソッドは実行時にすべてのクラスを検索して使用することがわかります @ControllerAdvice
。イベントが送信されると、データを返す前に統合されたデータのカプセル化を呼び出すなど、対応する Advice メソッドが呼び出されます。たとえば、例外が発生した場合に実装されます。異常な Advice メソッドを呼び出すことによって