صادق خانزادی
صادق خانزادی
خواندن ۵ دقیقه·۴ ماه پیش

Exception Handling - Sample Code (Spring Boot)

قبلا در رابطه با Exception Handling و انواع خطا ها خیلی مفصل صحبت کردیم و آشنا شدیم

اگر آموزش Java Exception Handling رو نخوندید پیشنهاد میکنم اول از اون شروع کنید.

خوب الان میخوام از یه نمونه کد که توی Spring به همراه دوست خفنم پیاده سازی کردیم براتون پرده برداری کنم.

دوست خفن و یاور همیشگی من : سید امین سید محمدی

آدرس لینکدینش : https://www.linkedin.com/in/seyedamin-seyedmohammadi

تا 4 صبح داشتیم برای بهتر شدن کار ایده میدادیم و حی پاک میکردیم و حی مینوشتیم سر سری رد نشید.

یه فایل Postman هم جهت تست پروژه براتون گذاشتم توی مسیر گیت.

نمونه خروجی سرویس:

{ &quothttp_status_code&quot: &quotINTERNAL_SERVER_ERROR&quot, &quothttp_custom_error_code&quot: 0, &quottimestamp&quot: &quot2024-09-04T17:46:42.344+00:00&quot, &quotmessage&quot: &quotnot handler exception&quot, &quotdescription&quot: &quotInternal Server Error&quot }

این آدرس git پروژه :

آدرس وب پروژه : https://github.com/sadeghkhanzadi/exception-handling
آدرس گیت پروژه جهت clone گرفتن : https://github.com/sadeghkhanzadi/exception-handling.git

توضیح درباره کد های پروژه : (تا 4 صبح داشتیم ایده پردازی میکردیما سر سری رد نشید ازش )

اول از همه بگم این خطا به عنوان یک commons توی پروژه اصلیمون بود که من جداش کردم و انواع خطاهای microservice های مختلف پروژه رو توی پکیج InternalExceptions قرار دادیم.

که مثلا

کلاس OauthException برای خطاهای مربوط به IdentityManagment و Oauth و از این قبیل موارد

و کلاس AsyncException برای خطاهای مربوط به Sync ، Async و MessageBroker و از این قبیل موارد

و ...

و اما یک کلاس خیلی مهم برای دیگر خطا هایی که ممکنه هر لحظه اتفاق بیفته و از عهده کنترلشون بر نیاید

با نام UnCheckedExceptions ایجاد کردیم

که مثلا خطاهای Db Constraints و خطاهای چک نشده مثل IllegalArgumentException در آن دریافت میگردد و میتواند در این کلاس هندلشون کنید.

اگر میخواید Db Constraints هارو هندل کنید میتونید Db Constraints هارو در دیتابس خودتون تغییر نام بدید و بعد بیاید اینجا دریافتش کنید که اون خودش یه مبحث مربوط به دیتابیس

BaseException Interface:

هر کدوم از این کلاس ها از یک 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:

اگر به کلاس 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(&quotException Class Name: &quot + 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(&quot[.]&quot, &quot/&quot)); BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); return reader.lines() .filter(line -> line.endsWith(&quot.class&quot)) .map(line -> getClass(line, packageName)) .collect(Collectors.toSet()); }

متد getClass:

در این متد نام کلاس و آدرس پکیج کلاس به عنوان پارامتر ورودی دریافت میگردد و سپس یک کلاس برگشت داده میشود.

private static Class getClass(String className, String packageName) { try { return Class.forName(packageName + &quot.&quot + className.substring(0, className.lastIndexOf('.'))); } catch (ClassNotFoundException e) { // handle the exception } return null; }


امیدوارم توی پروژه هاتون استفاده کنید و لذت ببرید .

منتظر نگاه های قشنگتون هستم.




javaspring boot
Java Developer - Technical Team Lead At Dotin
شاید از این پست‌ها خوشتان بیاید