リソース注入
現在のEJB 3.0ドラフト仕様では、リソースを@Resource
アノテーションで宣言し、そのリソースをコンテナからEJBに注入することができます(@Resource
アノテーションは共通アノテーション仕様の中で規定されています)。依存性注入とは、オブジェクトの依存性をオブジェクト自身が明示的に作成する代わりに、オブジェクト外部のエンティティから提供(注入)する手法です。これを茶化して「ハリウッド方式」と呼ぶこともあります。本人からのアクセスは認めず、「用があるときはこちらからお電話します」というわけです。
サンプルのTravelAgencyServiceImpl
クラスを見てみましょう。このクラスでは、データの持続性を実現するために、IFlightDAO
インターフェイスの実装を見つける必要があります。これは伝統的に、Factory、Singleton、Service Locatorなどのパターンを用いて実現されます。ソリューションの一例は次のような形になります。
public class TravelAgencyServiceImpl implements ITravelAgencyService { public IFlightDAO flightDAO; public TravelAgencyServiceImpl() { flightDAO = FlightDAOFactory.getInstance().getFlightDAO(); } public void bookTrip(long outboundFlightID, long returnFlightID, int seats) throws InsufficientSeatsException { reserveSeats(outboundFlightID, seats); reserveSeats(returnFlightID, seats); } }
この実装では、特別なファクトリクラスが作成されます。容易に想像されるように、このクラスは、どこかに保存されている設定情報を読み取り、IFlightDAO
の実装をどんな形で生成すればよいかを知ることになります。サービスがその依存性を明示的に作成する代わりにコンテナから依存性を注入する場合は、設定を含むオブジェクト生成の詳細がコンテナに委譲されます。これにより、アプリケーションに必要なコンポーネントを設定に応じて簡単に「配線」し、決まり切ったシングルトンやファクトリコードの多くを排除することが可能となります。
このクラスが、JSR 250リソースアノテーションで宣言されたIFlightDAO
の実装に対して依存性を持つ場合、クラスの実装は次のようになります。
public class TravelAgencyServiceImpl implements ITravelAgencyService { @Resource(name = "flightDAO") public IFlightDAO flightDAO; public void bookTrip(long outboundFlightID, long returnFlightID, int seats) throws InsufficientSeatsException { reserveSeats(outboundFlightID, seats); reserveSeats(returnFlightID, seats); } }
この例では、「flightDAO」というリソースの適切な実装が、コンテナによってサービスクラスに提供されます。ところで、EJB 3.0がまだリリースされていない現在、リソース注入を利用するにはどうすればよいのでしょう。SpringやPico Containerといった軽量コンテナを使えば依存性注入を実現できますが、筆者が知る限り、JSR 250リソースアノテーションを用いて注入条件を指定する軽量コンテナはまだ存在しません(いずれ、この場で紹介されるとは思いますが)。
1つの方法として、アスペクトを用いて依存性注入を実装することが考えられます。@Resource
アノテーションをこの目的で使えば、その実装はEJB 3.0による方法と一貫性を持ち、EJB 3.0の実装と前方互換となります。実際、これはそれほど難しくありません。次のコードはAspectJで作成したアスペクトです。これは、@Resource
アノテーションで指定されたフィールドを注入します。
@Aspect public class InjectionAspect { private DependencyManager manager = new DependencyManager(); @Before("get(@Resource * *.*)") public void beforeFieldAccesses(JoinPoint thisJoinPoint) throws IllegalArgumentException, IllegalAccessException { FieldSignature signature = (FieldSignature) thisJoinPoint.getSignature(); Resource injectAnnotation = signature.getField().getAnnotation(Resource.class); Object dependency = manager.resolveDependency( signature.getFieldType(), injectAnnotation.name()); signature.getField().set(thisJoinPoint.getThis(), dependency); } }
このアスペクトは、プロパティファイル内から実装クラスを探し(このロジックはDependencyManager
オブジェクトにカプセル化されています)、見つけた実装クラスを@Resource
アノテーションで指定されたフィールドに注入したのちに、フィールドアクセスを行います。なお、この依存性注入はJSR 250とEJB 3.0のやり方をまねています。言うまでもなく、この実装は完全なものでありませんが、JSR 250互換の方法でリソース注入を実現するとき、必ずしもEJBを使わなくてよいことが分かります。
セキュリティ
JSR 250とEJB 3.0は、リソース注入に加え、アノテーションを用いてセキュリティメタデータを表現することも実現しています。javax.annotation.securityパッケージの中で規定されている5つのアノテーション(RunAs
、RolesAllowed
、PermitAll
、DenyAll
、RolesReferenced
)をメソッドに適用することで、セキュリティ要件を定義できます。例えば、前掲のbookTrip
メソッドについて、呼び出し元のロールが「user」である場合のみ実行を許可すると宣言するには、次のようなアノテーションを記述してセキュリティ制限を課します。
public class TravelAgencyServiceImpl implements ITravelAgencyService { @Resource(name = "flightDAO") public IFlightDAO flightDAO; @RolesAllowed("user") public void bookTrip(long outboundFlightID, long returnFlightID, int seats) throws InsufficientSeatsException { reserveSeats(outboundFlightID, seats); reserveSeats(returnFlightID, seats); } }
このアノテーションは、所定のロールを持つ呼び出し元にだけメソッドの実行を許可するようコンテナに指示します。次に、このセキュリティ制約をアプリケーションに課す簡単なアスペクトの例を紹介しましょう。
@Aspect public class SecurityAspect { @Around("execution(@javax.annotation.security.RolesAllowed * *.*(..))") public Object aroundSecuredMethods( ProceedingJoinPoint thisJoinPoint) throws Throwable { boolean callerAuthorized = false; RolesAllowed rolesAllowed = rolesAllowedForJoinPoint(thisJoinPoint); for (String role : rolesAllowed.value()) { if (callerInRole(role)) { callerAuthorized = true; } } if (callerAuthorized) { return thisJoinPoint.proceed(); } else { throw new RuntimeException( "Caller not authorized to perform specified function"); } } private RolesAllowed rolesAllowedForJoinPoint( ProceedingJoinPoint thisJoinPoint) { MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature(); Method targetMethod = methodSignature.getMethod(); return targetMethod.getAnnotation(RolesAllowed.class); } private boolean callerInRole(String role) { ... } }
このアスペクトは、@RolesAllowed
アノテーションで指定されたメソッドの実行要求に対して必ず適用され、呼び出し元のロールがアノテーションで指定されたロールのいずれかに一致するかどうかによって、呼び出し元にメソッドの実行許可を与えるかどうかを判断します。もちろん、ユーザーのロールを調べて実行を許可する部分については、JAASやカスタムソリューションなど、任意のアルゴリズムを使うことができます。本稿のサンプルコードでは、簡略化のためにサーブレットコンテナに委譲しました。