قبلا در رابطه با Exception Handling و انواع خطا ها خیلی مفصل صحبت کردیم و آشنا شدیم
اگر آموزش Java Exception Handling رو نخوندید پیشنهاد میکنم اول از اون شروع کنید.
خوب الان میخوام از یه نمونه کد که توی Spring به همراه دوست خفنم پیاده سازی کردیم براتون پرده برداری کنم.
دوست خفن و یاور همیشگی من : سید امین سید محمدی
آدرس لینکدینش : https://www.linkedin.com/in/seyedamin-seyedmohammadi
تا 4 صبح داشتیم برای بهتر شدن کار ایده میدادیم و حی پاک میکردیم و حی مینوشتیم سر سری رد نشید.
یه فایل Postman هم جهت تست پروژه براتون گذاشتم توی مسیر گیت.
نمونه خروجی سرویس:
{ "http_status_code": "INTERNAL_SERVER_ERROR", "http_custom_error_code": 0, "timestamp": "2024-09-04T17:46:42.344+00:00", "message": "not handler exception", "description": "Internal Server Error" }
این آدرس git پروژه :
آدرس وب پروژه : https://github.com/sadeghkhanzadi/exception-handling
آدرس گیت پروژه جهت clone گرفتن : https://github.com/sadeghkhanzadi/exception-handling.git
اول از همه بگم این خطا به عنوان یک commons توی پروژه اصلیمون بود که من جداش کردم و انواع خطاهای microservice های مختلف پروژه رو توی پکیج InternalExceptions قرار دادیم.
که مثلا
کلاس OauthException برای خطاهای مربوط به IdentityManagment و Oauth و از این قبیل موارد
و کلاس AsyncException برای خطاهای مربوط به Sync ، Async و MessageBroker و از این قبیل موارد
و ...
و اما یک کلاس خیلی مهم برای دیگر خطا هایی که ممکنه هر لحظه اتفاق بیفته و از عهده کنترلشون بر نیاید
با نام UnCheckedExceptions ایجاد کردیم
که مثلا خطاهای Db Constraints و خطاهای چک نشده مثل IllegalArgumentException در آن دریافت میگردد و میتواند در این کلاس هندلشون کنید.
اگر میخواید Db Constraints هارو هندل کنید میتونید Db Constraints هارو در دیتابس خودتون تغییر نام بدید و بعد بیاید اینجا دریافتش کنید که اون خودش یه مبحث مربوط به دیتابیس
هر کدوم از این کلاس ها از یک interface به اسم BaseException ایمپلیمنت کردن
BaseException: public interface BaseException { ErrorMessage getErrorMessage(Throwable t); }
همانطور که مشاهده میکنید این اینترفیس یک متد دارد که به عنوان پارامتر ورودی یک Throwable دریافت میکند که این به این دلیل است که بتواند انواع خطاهای کنترل شده و نشده و error ها را پشتیبانی کند و هیچی از دستمون در نره.
شکل پیاده سازی این متد در هر کلاس به صورت زیر میباشد :
@Override public ErrorMessage getErrorMessage(Throwable ex) { OauthException exc = (OauthException) ex; return new ErrorMessage.Builder() .message(exc.getMessage()) .timestamp(new Date()) .description(exc.getError_description()) .httpStatusCode(exc.http_status_code) .http_custom_error_code(exc.http_custom_error_code) .builder(); }
دلیل نوشتن این متد اصلا چی بود :
بریم بفهمیم
اگر به کلاس ExceptionConfiguration نگاه کنید به این متد میرسید : oauthException
@ResponseBody @ExceptionHandler({Throwable.class}) // @ResponseStatus(HttpStatus.BAD_REQUEST) public ResponseEntity<ErrorMessage> oauthException( Throwable ex, WebRequest request) { Class<? extends Throwable> aClass = ex.getClass(); BaseException baseException = baseExceptionMap.get(aClass.getSimpleName()); ErrorMessage message; if (baseException != null) { message = baseException.getErrorMessage(ex); } else { message = UnCheckedExceptions.getErrorMessage(ex); } //todo logs return new ResponseEntity<ErrorMessage>(message, new HttpHeaders(), message.getHttp_status_code()); }
همان طور که متوجه شدید هر زمان در یک سرویس خطا اتفاق میفتد یه صورت اتوماتیک خطا توسط متد فوق دریافت میگردد
Class<? extends Throwable> aClass = ex.getClass();
در این خط نام کلاس خطای مربوطه دریافت میشود مثلا فرض کنید AsyncException برگدونیم پس در اینجا نام کلاس میشه AsyncException
سپس:
BaseException baseException = baseExceptionMap.get(aClass.getSimpleName());
کلاسی که از baseException ایمپلیمنت کرده باشد ایجاد و برگشت داده میشود. و متن پیام موجود در آن جهت ایجاد خطای خروجی ایجاد بر میگردد.
شاید این سوال پیش بیاید اگر خطایی را ما هندل نکردیم چی میشه :
if (baseException != null) { message = baseException.getErrorMessage(ex); در این خط خطاهایی که ما پیاده سازی کردیم در صورت پرتاب شدن دریافت میگردد } else { message = UnCheckedExceptions.getErrorMessage(ex); در این خط هم خطاهایی که ما پیاده سازی نکرده باشیم و به اصطلاح هندل نشده دریافت میگردد }
خوب بریم یکم بیشتر وارد کلاس ExceptionConfiguration بشیم :
یکم قبل تر به این تکه کد در متد oauthException اشاره کردیم
BaseException baseException = baseExceptionMap.get(aClass.getSimpleName());
خوب همانطور که مشاهده میکنید baseExceptionMap یک HashMap میباشد که در زمان اجرا شدن اپلیکیشن از اسامی Exception Class ها پر میشود. (OauthException , AsyncException , ...)
مسیر Exception Class ها در ابتدای کلاس ExceptionConfiguration در متغیر exceptionPath قرار میگیرد
private static final String exceptionPath =" مسیر سیستم خودتون رو بدید به پکیج InternalExceptions "
و سپس در زمان اجرا شده اپلیکیشن در بلوک static موجود در کلاس ExceptionConfiguration کلاس ها توسط متد initBaseException شناسایی شده و در HashMap نام برده شده قرار میگیرد.
static { try { initBaseException(); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }
متد initBaseException:
در این متد ادرس پکیج Exception Class ها به صورت یک set از متد findAllClassesUsingClassLoader برگشت داده میشود و حالا زمان آن است که هر یک از این کلاس ها ایجاد شوند و در baseExceptionMap قرار بگیرند.
در واقع ما داریم یک HashMap از Exception Class ها ایجاد میکنیم تا در صورت بروز خطا در متد oauthException قابل دسترس باشد که قبل تر توضیح داده شد .
private static void initBaseException() throws ClassNotFoundException, InstantiationException, IllegalAccessException { Set<Class> classSet = findAllClassesUsingClassLoader(exceptionPath); for (Class i : classSet) { BaseException b = (BaseException) Class.forName(i.getName()).newInstance(); System.out.println("Exception Class Name: " + i.getSimpleName()); baseExceptionMap.put( i.getSimpleName(),b ); } }
متد fillAllClassesUsingClassLoader :
این متد آدرس Exception Class ها را به عنوان پارامتر ورودی دریافت میکند. و آدرس پکیج هر کلاس را در set قرار میدهد و برمیگرداند.
public static Set<Class> findAllClassesUsingClassLoader(String packageName) { InputStream stream = ClassLoader.getSystemClassLoader() .getResourceAsStream(packageName.replaceAll("[.]", "/")); BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); return reader.lines() .filter(line -> line.endsWith(".class")) .map(line -> getClass(line, packageName)) .collect(Collectors.toSet()); }
متد getClass:
در این متد نام کلاس و آدرس پکیج کلاس به عنوان پارامتر ورودی دریافت میگردد و سپس یک کلاس برگشت داده میشود.
private static Class getClass(String className, String packageName) { try { return Class.forName(packageName + "." + className.substring(0, className.lastIndexOf('.'))); } catch (ClassNotFoundException e) { // handle the exception } return null; }
امیدوارم توی پروژه هاتون استفاده کنید و لذت ببرید .
منتظر نگاه های قشنگتون هستم.