TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
首先说一下:+ [ r7 h6 C( k# W; p
*JDK 的动态代理只能对实现了接口的目标类进行代理,而不实现接口的类就不能使用 JDK 的动态代理( i& K' a1 K: }. \
*CGLIB 是针对类来实现代理,当没有实现接口的类需要代理时就需要通过 CGLIB 来实现代理了,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但是因为采用的是继承,所以不能对 finall 类进行继承。% o5 A0 k! H( o% {$ w
- A* i0 C1 e1 d w( V4 g$ l6 V
+ J5 K* U) n% D& Ejava的动态代理 8 ?, D2 g# Q$ M' G" C/ r# G
代理模式
- |( I! z& b$ \2 W/ ?! w b代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。 0 N6 ~1 R; q3 j s" V( s
按照代理的创建时期,代理类可以分为两种。
3 z# k0 e* }* T5 x" P% b0 {静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 3 i8 t, H- }* g9 S: V9 _ J0 u( [5 [
动态代理:在程序运行时,运用反射机制动态创建而成。
2 Z- u1 o* R% V& `9 d
& K2 Z1 ?8 R. e$ b7 I1 C# ?6 a/ v首先看一下静态代理:
8 I1 W$ {5 p$ N3 d- `7 V; p1、Count.java- package net.battier.dao;
3 \0 b2 e1 w0 y p# V1 W/ F - 4 ^. V: x3 F/ B7 _+ h1 s
- /**
# Y7 k5 K- |+ z& R! @, c3 p( {) q: P - * 定义一个账户接口
; v! o0 y0 V7 l. A' j4 p - * 2 S) {8 {" d( _2 e
- * @author Administrator ' P) V/ n3 o2 ^2 `: ^
- *
* U+ {" e# ~- p) l- S! y - */ + A6 \! o2 E: q( n6 q
- public interface Count { * s1 j! Y5 l3 R
- // 查看账户方法
! a. z" }! g6 R - public void queryCount();
# W* B: ?* H7 O1 M -
1 G/ y3 a A+ \% v8 `4 i6 l2 f2 S - // 修改账户方法 1 J: O: {: C1 `1 y
- public void updateCount(); , O9 Q5 |1 ?5 K: B k
- / h7 O4 w9 b8 X# a! @
- }
复制代码 2、CountImpl.java- package net.battier.dao.impl;
: j2 M# Z& ?% ?: f2 ` - 4 t' u6 Q# X: k) ~$ ?' B
- import net.battier.dao.Count;
' G3 h- J/ s+ H' x( x" W -
( O2 O3 e- A |1 v$ h* r* @ - /**
( S5 b: D1 U9 o7 r0 h7 E - * 委托类(包含业务逻辑) $ i6 A8 j8 g* h1 p
- *
2 K4 A' t. }& s1 [9 @6 Q7 Z8 [ - * @author Administrator ' h' f. ~: e; C
- * - Q6 }- J. }* A: E8 j
- */
" X% r4 D- l6 [* D7 N) b2 U6 ` - public class CountImpl implements Count { 5 G( j0 l F X3 Q$ j
-
! Q) `- a1 |- J' U1 z - @Override 5 U- e8 ^- E$ o* F' X6 P
- public void queryCount() {
- p6 N: [4 ~9 S% T - System.out.println("查看账户方法...");
, m* E/ ~# x; d5 E; y" R - " h) q! E0 }- m! d3 k* A+ H0 Y8 n6 l
- }
. h7 o9 |* f9 ?5 e2 Z u - 1 ?7 Z3 i! t* Y& Z. B8 I' h
- @Override
; ~" u# f2 Y2 T& l- z+ U - public void updateCount() {
3 G, L( J0 V5 C' F9 ~" _& P - System.out.println("修改账户方法..."); 8 H/ u% u5 t7 B8 L8 ^5 T
- + o& f& K0 ~+ ` J d0 L8 W
- }
+ X. W6 j+ f. q5 Y -
3 [+ ~6 i2 R# i0 h2 p! H2 q6 U - }
2 L' _6 x# I+ v/ j% n6 ~ -
4 Q0 S- l& o# v; I5 w8 k - 、CountProxy.java
/ C" @0 H1 c) R1 E, K - package net.battier.dao.impl;
1 c% h6 ^7 Y# p, i -
( R. ~. h1 n, T - import net.battier.dao.Count;
( W4 ~% f5 W4 t, M2 [ -
* O* u3 W. r9 M3 X' f - /** 8 h( x- D% r2 S! @1 E" f. ^
- * 这是一个代理类(增强CountImpl实现类) 9 Y) v/ C0 l' y! t
- * " H6 \0 N1 K; N \7 Z: i
- * @author Administrator
0 G7 p Y7 s' F; X3 y+ V) l - * ) V* @, t* w! I
- */
% o/ o/ m8 W8 w - public class CountProxy implements Count { " J/ y) Q& M9 C* B2 h1 E4 w: ?: c
- private CountImpl countImpl; 2 r, Z+ }# L1 |6 R. `; H4 h& D0 z0 m
- 5 t( X3 e, O. g; h! B+ R3 f# s( a
- /**
! P5 k/ R8 ^" _% ]$ A$ j - * 覆盖默认构造器
3 `2 X; q: r4 p - * $ Q5 n4 K. u0 Y( c5 `, y
- * @param countImpl $ Y* }9 F+ J3 ^% _+ `, U6 |
- */
) `, l7 v6 m0 T6 }2 ~/ D. M - public CountProxy(CountImpl countImpl) {
; Y# ~0 A$ A H- j) y - this.countImpl = countImpl;
' Y& I* o' [+ r6 c* _ - }
& _5 D: Y5 d; y* H& A - 5 E7 S; {$ ?% a& q
- @Override - b: _+ u6 g2 c
- public void queryCount() { 1 c7 _ ^8 n5 l6 {
- System.out.println("事务处理之前"); U+ v' _1 S% K" M# N$ @, v
- // 调用委托类的方法; # r* m1 p! ^# S5 P+ W
- countImpl.queryCount();
0 S9 A9 G( z, }" d) s* P - System.out.println("事务处理之后"); ! N" a4 a" P; Y$ S- e# P
- }
3 W+ s5 S& F; [7 N - 9 {% }; ]) ^/ ]. D
- @Override
5 _3 C! z/ C) l2 g9 g. } - public void updateCount() {
4 V0 o1 z/ Z1 n/ \7 _% ]0 ^ - System.out.println("事务处理之前");
" l* |2 k. D) l$ r/ P1 ]& f7 ~2 y - // 调用委托类的方法;
, e# _4 `6 l0 S7 N6 }! ?; s - countImpl.updateCount(); , Z4 y$ s/ o- r* R; S: h: D3 G
- System.out.println("事务处理之后"); 4 S: Q: D3 |, J" a k$ M7 N+ l# L
- " @4 k, `( i; g' b! f0 t
- }
% Q+ d9 k2 O3 D" o) M - 6 T) w* }9 X# \( N2 z/ g( I& }) u% R4 r
- }
复制代码
% F9 I5 _' @, H; Z' Y3 W' A" d" v! C6 d1 L) ?0 w
6 N8 S- \, `" b# D# i( l. V- |
3、TestCount.java- package net.battier.test;
: a J9 a/ m/ A5 n6 O& V @ - + p$ ]* ]) H7 X3 l* m; [9 L- o! _0 D) _$ G
- import net.battier.dao.impl.CountImpl;
, G" N' \; f4 I& i - import net.battier.dao.impl.CountProxy; ' r+ s" H) T8 n- z: x) P9 ?4 r
-
8 S* y/ o7 b1 v5 Z' Y - /**
% Q# P0 x$ y; k. I4 f - *测试Count类 4 B$ K: h4 E4 N
- * 0 f! I9 k( v1 ^+ }1 i, v
- * @author Administrator
8 C, w( i& Y n7 A- X, H. D W - *
" x1 l3 G: s1 A+ F' T - */
" V# G& r' S3 e% G- k8 D - public class TestCount {
. \) v6 u& N* {8 o: g - public static void main(String[] args) { 3 v6 L; [. o, f) Z! i
- CountImpl countImpl = new CountImpl(); / U+ ?( H" H9 F6 t) S4 l4 x. f
- CountProxy countProxy = new CountProxy(countImpl);
2 x1 V$ w Q* |* G9 ]! u - countProxy.updateCount(); . B; P: e" z( N( E( y U4 b3 }
- countProxy.queryCount();
0 e# `: ?; ]1 o" p6 R - / L2 b0 W% u3 ?+ Y
- } 1 ?+ m/ O* w' [/ S# a2 w/ j
- }
复制代码 观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。 6 R# l h3 t( p# o, Y$ ? Q
再来看一下动态代理:
% h8 ?; U. R. N' e6 \/ o LJDK动态代理中包含一个类和一个接口:
) a" k+ x7 x, ^% PInvocationHandler接口:
U4 E5 c( l2 W6 q1 s# M0 Z [2 f* ?- Opublic interface InvocationHandler {
! H8 d9 Q3 _9 J" A' n0 Epublic Object invoke(Object proxy,Method method,Object[] args) throws Throwable; 3 ^& s, l& P/ P% o
}
& ^' B8 Z$ e5 J8 D: H- x7 e( J参数说明:
" D1 {- _( `, p ?. l- Y i8 aObject proxy:指被代理的对象。
6 v3 x% p/ b; J' WMethod method:要调用的方法 # J0 E4 r6 j+ u" Y
Object[] args:方法调用时所需要的参数 0 x/ e" _: L/ E) T8 |! Z0 Z
- k2 _- r9 o. A* F3 E F" Y可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。 1 ?. S8 G# U3 J8 N; w
+ W `( r4 ~/ Y9 G1 N& IProxy类: * K8 t" \/ @, I6 _
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法: 4 L0 s. B: S* W" }1 c) K
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
& C3 d4 ?; C: IInvocationHandler h)
9 I: f$ f* _ \0 s6 \ throws IllegalArgumentException ( S+ ?. `$ I2 o7 _
参数说明:
- p7 M q5 f5 XClassLoader loader:类加载器 9 c. S- f+ |9 S1 I" n
Class<?>[] interfaces:得到全部的接口
& L- P, P# _, Z7 K8 ~; B4 ~" F7 xInvocationHandler h:得到InvocationHandler接口的子类实例
) K `7 H. A# }4 p6 P4 ~0 l$ C: r( W2 X
Ps:类加载器
3 c7 ^9 m0 G( t' e3 W在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器,在Java中主要有一下三种类加载器; 1 A3 ?9 m$ U1 n3 g
Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的; 4 b2 t8 i9 }6 f, m9 f; j% i
Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类; 7 t1 s" q- D; m4 B$ p
AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。
- k6 X, k! o6 T1 `+ { w* {" l6 ?: Q. A" ~! K- q: M; D/ y0 }) V8 Q
动态代理
. L. O& V% o# F与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。
- i/ k+ C; U4 Y- P& D+ _
8 }: H1 ?9 ?+ `动态代理示例: 1 H0 ]. P; o# H* i- I8 Y
1、BookFacade.java- package net.battier.dao;
/ ~# M' }* @6 h2 h# M - \' w6 h! ^; ?& y* ~
- public interface BookFacade { 6 Z# W# f, O8 H; x6 f3 p) f
- public void addBook(); % Z- E6 l1 F' V8 P* |
- }
复制代码 2、BookFacadeImpl.java- package net.battier.dao.impl; . R5 R# A" T& Q' A' n
- % f/ y: U: U$ v
- import net.battier.dao.BookFacade;
& t; D k6 p$ t* i -
* q# A) l- B$ F4 N; q* G, M1 x; L - public class BookFacadeImpl implements BookFacade { , ~! D% E' U2 v7 T7 d
- : t4 L4 u! j$ \' E1 Z3 f$ H
- @Override
' q% l- Z6 ^4 ^/ z* H4 X& { - public void addBook() {
' }8 u. [* O% x - System.out.println("增加图书方法。。。"); # f8 ^; r0 z; R2 b! j$ q5 Q: m
- }
& C, b6 h' W% [ - , t: k$ o) s) F% h1 J; N* A
- } & J5 \, _* b% Q# [9 x
- 9 r# X5 r4 w" ~; F: v
- 、BookFacadeProxy.java
G. y# p4 a3 K7 s# u/ @. G -
( W9 z) }0 H. B! t, A - package net.battier.proxy; , |/ L9 ]; A! E! a4 z
-
- P6 n/ A5 p+ @+ I _) e - import java.lang.reflect.InvocationHandler;
7 |# c0 T5 u. `1 `' X. a D4 D - import java.lang.reflect.Method;
2 y9 K4 l3 V( p6 T - import java.lang.reflect.Proxy; * o, F# S. x( j% q( @0 J% R
- 4 {$ o# \! \! g1 Z$ G7 k* B
- /** ! t( Y% P' V( v/ O2 R$ J% w; a& o5 P' \
- * JDK动态代理代理类 ( W, \4 z/ K$ B+ g) l/ ?5 f
- *
4 r5 X$ n5 o$ k* C5 l( E - * @author student ( F0 E5 y, o- E: D
- *
- f& F7 V* S6 [3 a, w+ | - */
2 r; p/ u' j% c1 M - public class BookFacadeProxy implements InvocationHandler {
. w5 B2 D. f N - private Object target;
: L1 s7 I) R2 i9 j7 Z - /**
/ q) x" v6 B4 {. S - * 绑定委托对象并返回一个代理类 ! S8 @5 h0 P! u( ]: d( U& S
- * @param target
' @2 V) }, M5 m- u. L - * @return 9 c5 ~. {4 s- d6 X) D0 V) p
- */
4 V9 s5 u( f; \, T( B4 `4 w - public Object bind(Object target) { - Z2 \9 B3 f7 m+ h' D$ P% z9 b! W
- this.target = target;
- \8 k% W- C: P* j9 O# z - //取得代理对象
( P* Z Y4 y# Z - return Proxy.newProxyInstance(target.getClass().getClassLoader(),
1 f" C' [1 p- u2 `5 s - target.getClass().getInterfaces(), this); //要绑定接口(这是一个缺陷,cglib弥补了这一缺陷)
% m$ b2 X& K6 H& u - } 4 f; e/ b; Q, f7 \) N5 T" a$ Q7 ~
- , _4 f$ @2 K8 ]
- @Override
$ N" ?& E5 k1 Q f6 |9 r - /**
! z+ \6 X, n0 H# j+ m) b) t - * 调用方法
* U" C. Y6 d1 c) K. Z1 E - */ 2 i# d, Y! z$ T
- public Object invoke(Object proxy, Method method, Object[] args) 6 I" |% J1 m$ E8 v- _( }
- throws Throwable {
, u2 ~4 Z5 r% f7 S$ S2 B6 _ - Object result=null;
0 C3 A6 a* L' W: \/ |! H - System.out.println("事物开始"); ; p* h+ m$ W; n, S
- //执行方法 ' D& F# ]+ p6 G/ {1 j7 D& L8 N, G
- result=method.invoke(target, args);
2 Z. v( b% i5 |% S - System.out.println("事物结束"); 4 r& k; O) _" f2 Y- k. z; \4 O) \
- return result; 6 Q7 F, E9 I' s: Q Y/ E
- } : {* o2 u# A$ P9 B8 R1 I
- - G3 K# K$ Q0 k, u- D V
- }
复制代码 3、TestProxy.java- package net.battier.test; ! j) Z6 r8 X. W/ {0 Z
- # H$ w+ q0 y9 L) j2 R" F, H
- import net.battier.dao.BookFacade;
! Z' R. P* Y* Y! G1 A - import net.battier.dao.impl.BookFacadeImpl; & ^6 [3 {& @7 z! e
- import net.battier.proxy.BookFacadeProxy; . o2 I( `" K6 I1 d! d0 `
- 5 I5 [" ^' f! S& U0 p, M
- public class TestProxy { & U( n) { ^; {) P6 r, q2 ^
- * B' P8 |% @0 J o* Q- t# }
- public static void main(String[] args) {
/ K- L$ }5 ?* S2 B8 d - BookFacadeProxy proxy = new BookFacadeProxy(); ) Y1 J# o& l* I6 M7 f n1 d
- BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl()); & u) @: S" {6 x r1 L0 A
- bookProxy.addBook();
! o9 K7 I3 H# @2 N7 M, O5 D4 _ - } / x3 k2 w* o$ N7 R
- 8 B/ _( ?4 @% i4 H7 w! i
- }
复制代码 但是,JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了。 # d$ g' p/ L/ W. N8 r
$ q- m) }& u- D# j' V# `- `6 GCglib动态代理
% h3 O9 Q' r* p$ V, F) k; \; K$ dJDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。 . l$ y( z2 V* u- {1 a$ F4 a! S6 b
示例
- `' `( M9 E$ _# T5 i% F1、BookFacadeCglib.java- package net.battier.dao;
l! i, ]. ^+ L' p# B" l; W -
5 z$ n2 R' @2 O- ]7 K0 ]" S; j4 i - public interface BookFacade {
# Z% }2 O7 n- @6 N" h0 p - public void addBook(); 0 G; }+ \& q% j& @
- }
复制代码 2、BookCadeImpl1.java- package net.battier.dao.impl;
' B, K1 m8 y! l e9 D } -
, W# b8 z5 I) g1 M. i - /**
7 C/ y: m; `3 `8 S: I" O& ]6 _ - * 这个是没有实现接口的实现类
! j* Y% q# l( Z% w0 q% O" y7 X( M - * 9 p8 P Z& Q) i# r* S/ X S* ~
- * @author student
9 O, X5 x# M2 u* g0 q9 e6 T - *
3 p, o1 k8 [3 P* h' K2 m* J3 X - */
. E6 x' X" D3 I' I Y; | - public class BookFacadeImpl1 { $ ~) t; ]! j, s% x, ^* l
- public void addBook() { ; {, u( r- H- n1 }
- System.out.println("增加图书的普通方法..."); ) P+ k: \! m$ K
- } , d4 z$ \! Q( n+ v
- }
复制代码 3、BookFacadeProxy.java- package net.battier.proxy;
2 Y$ R. n$ e2 ?* b _7 I" m& H -
& U* H8 ~/ J( J' s7 }& _/ D* q - import java.lang.reflect.Method; 7 Y$ y" p, }! Q& M9 r
- * g- h: ?- S; `# @& i! H
- import net.sf.cglib.proxy.Enhancer;
- ~5 \- Z$ v1 q, q" @* D - import net.sf.cglib.proxy.MethodInterceptor;
2 M" Y) \" v1 ~- }! k" t$ d: w; \+ d - import net.sf.cglib.proxy.MethodProxy;
7 z8 E! n1 c3 [6 R; w, m! E - + j9 k0 R' M% r- i* |
- /** * B; T" B5 W2 d0 [
- * 使用cglib动态代理 + l! \' Y+ I. C: v
- *
: ?" l- C$ F" R! f - * @author student " A7 j# E! ~3 c# T/ E/ V
- *
$ H% h8 w3 Z8 q* P - */
2 N. v" K2 {9 p' J7 m5 z - public class BookFacadeCglib implements MethodInterceptor {
7 P$ L/ y" T7 m( [0 b - private Object target; ' d/ }' Y K: N( k
- ( o6 C: b+ D7 U
- /** ! @+ ~9 R' Q c/ |2 C+ W4 L
- * 创建代理对象
* z3 u$ S. X* G8 E - *
& F3 F ^! l. V6 G - * @param target ! n( B" q0 D% I G' L
- * @return * ?# M8 ]; O, d* O: H/ j ?
- */
% u: i6 z$ l# ?! {/ } - public Object getInstance(Object target) { 9 z! {) v5 A O- r, g
- this.target = target;
+ |. g4 E: o$ c! b - Enhancer enhancer = new Enhancer();
# L7 Y/ L* {1 C/ w - enhancer.setSuperclass(this.target.getClass());
' P0 k* g' V( T( {3 a' o* E! R5 Y$ Q - // 回调方法
' @ Z: Y2 r& n3 |5 c M$ { - enhancer.setCallback(this); 0 V8 G, x$ Z% t
- // 创建代理对象
' p. J: D; i0 {8 K8 x+ M' z8 u) T1 L - return enhancer.create();
, I1 i% X) b" |4 \+ Z) a q - }
, q! u2 @* t+ z8 M) E+ }. a -
, H& M" k+ P7 b - @Override
9 J8 B( ?8 H4 \ z# y, E - // 回调方法 9 H$ F- M% t: i% j* ~
- public Object intercept(Object obj, Method method, Object[] args, % V; `0 u/ i" r
- MethodProxy proxy) throws Throwable {
& e( `" T$ _1 }8 X [5 A - System.out.println("事物开始");
$ W8 w! n. P! H; Y9 O1 j - proxy.invokeSuper(obj, args);
8 K& \' `8 ]7 p; M3 \ - System.out.println("事物结束"); , C: _) x0 `3 l" r \: v5 e
- return null; . w3 V9 n* ~8 r7 S
- ' l4 N' G( U, M- H- ~ Z- B% ~
- ; g; H; z8 r( g. L3 r. o Y U4 v
- } / y% H$ @! n' a3 o' p- r8 V
- & F' z) U* u0 l
- }
复制代码 4、TestCglib.java- package net.battier.test;
. ]3 |5 t6 j k - + K& }) T4 D r" }0 l# ^1 w( J- M
- import net.battier.dao.impl.BookFacadeImpl1;
$ N# ]) V; t# K0 C8 n9 [ - import net.battier.proxy.BookFacadeCglib;
7 t& n' v8 q% V% I3 j+ q2 s -
9 S9 m+ x8 H7 k, K' b* U8 R+ W - public class TestCglib {
$ H8 A6 c- h1 Y5 E& ^: f -
0 L! `& C' B; [( n& v$ L) b, O - public static void main(String[] args) {
- N- D% l1 O( Z - BookFacadeCglib cglib=new BookFacadeCglib(); 1 S. K& {% l1 U' D7 ]
- BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1()); 9 n6 g! Y: E; P
- bookCglib.addBook();
: s, j% r8 k7 n+ x - } 5 V+ S* X3 j& N6 V
- }
复制代码 参考:http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html
: T8 m- t4 S; Z0 y( P$ y; t0 q4 \4 ~" l9 A' U
% ^1 r8 {* _- N2 E) a: E
|
|