一般我们的异常都会抛出到控制层,如果使用struts2也就是action。然后try{//正确代码实现}catch{//在里面记录错误日志},这样咋一看是不错,代码很完美。但是如果项目中有成千上万个项目怎么办?难道在每个action的catch里面都要加入异常记录代码?很显然工作量是很大的。7 `, X, R" [ \1 [2 D
0 S+ I4 X, f& F) \, L C/ Y$ t
; P6 ~# Q4 g- Q4 v) H" M 鉴于项目中配置了数据库事务,其实也是使用了AOP 详见applicationContext-service.xml配置文件。由于配置文件的局限性这里采用注释的方式实现。之所有使用注释是可以自定义参数并实现日志的详细记录。既然使用注释,现成的没有,只有自己定义来实现需求。其实spring 注释实现的注入、hibernate的model类注释实现与数据库关联以及我们最常见Override。
. t1 t; z# z( Q7 x0 p 资料:java如何实现自定义注释http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html
1 ^. G4 }4 R% D7 R
% r5 E' J7 N7 ~; z6 ^6 z一、 记录日志并发送邮件通知
(1)、定义注解:
9 k9 S. Y4 n# U) S9 j0 A7 m9 ^1.ServiceLog.java(各种参数类型详见上面的资料)
0 D" K3 ~3 f2 ?( h
- import java.lang.annotation.*;
- S. w$ {) Z3 E# t: J' }& @ - /**
! }, O0 m4 n) A# Y9 I# A3 ~ - * 自定义注解 拦截service
5 [ G& I1 R& B E/ x - * 创建者 张志朋
. v9 C8 s$ }, K8 x - * 创建时间 2015年6月3日
- */
- o. R; b, I2 Z - @Target({ElementType.PARAMETER, ElementType.METHOD})
0 r0 w" j: w" ?& C- r$ _* }! U - @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface ServiceLog {
% V) K# P. z; |& y- j1 i - String description() default "";
3 |9 v# K6 p" V, L, ]" T) c5 S% c - }
- import java.lang.annotation.*; 1 O6 b$ f+ A5 N G5 R
- /**
; ~: V2 S4 G$ T+ C% R' | - * 自定义注解 拦截Controller: g' a) s9 S- b- [% Z+ v" s
- * 创建者 张志朋
- d3 R* O8 u" t - * 创建时间 2015年6月3日
/ E5 N% e$ c6 z# q. A+ X+ b$ { - *
: ~* v& ?. ]! T - */
6 M/ `8 H( \) J+ K. u0 K) n - @Target({ElementType.PARAMETER, ElementType.METHOD})
# V" N/ Z6 _, o, v4 i: M- J - @Retention(RetentionPolicy.RUNTIME) * D. u, W' W3 d. P9 J! h
- @Documented
f3 w* U. f r- Z4 w4 L3 r - public @interface ControllerLog {
S3 X. f9 i# w) E5 R9 Q - String description() default "";
?: A, M" a3 c! H% J- g - }
1.LogAspect.java
( `# e- e( r6 U, |( I1 |
- /**
$ c/ W4 ]. r" ~$ L2 ]) H6 ] - * 日志记录AOP+ ~# G$ `. M' W+ b
- * 创建者 张志朋* U3 Q. a' V* d' c+ {1 |' Q i/ H" A
- * 创建时间 2015年6月3日
- V+ o' ]) K0 x - *
4 ?4 h( H% d- e! [, }3 u - */* a& I5 G, i9 [
- @Component
/ C% s$ F* b l- r, I6 o9 m - @Scope$ w& p0 A1 g7 h5 |: V# p! ?2 T7 P
- @Aspect
) L/ Y. @8 X7 t' R' [0 s - public class LogAspect {. n [5 j- F( q( ^8 Q& i3 X7 [
- //Service层切点 用于记录错误日志
' `' \6 [8 d# Y, ~ - @Pointcut("@annotation(com.web.aop.ServiceLog)")
( J% V, a, ~* z9 y' z9 i - public void serviceAspect() {$ g: a. u5 m. Y" w
- 6 C9 [5 x" }3 m2 z
- }; ]7 {, \" L1 }) [ l1 t' i
- /**
* a) S) D4 p/ v% A: R$ V& T0 X* ?4 U - * 异常通知 用于拦截service层记录异常日志 ! J( E: g* X' T ~ c5 Y
- * @Author 张志朋
( }: D! e. F- X O3 T3 K - * @param joinPoint- L3 l7 g* T1 F$ H z- m
- * @param e void B- K3 D" I. H3 D) `
- * @Date 2015年6月3日
8 ]8 g" ?" O$ M3 K* _6 e - * 更新日志9 N- S: z6 {2 B* q5 K: C! ^, H$ w
- * 2015年6月3日 张志朋 首次创建/ f( k# a5 t/ X7 p( r, f
- *
1 @' ~* t1 g( @ - */! ^' U; k, B- G2 u" y; U4 v- R) x
- @AfterThrowing(pointcut = "serviceAspect()", throwing = "e") * X0 K5 m! P' l( }# \- w
- public void doAfterThrowing(JoinPoint joinPoint, Throwable e) { 2 X; e8 [$ n8 `; l
- HttpServletRequest request = ServletActionContext.getRequest();1 w4 k0 ?4 h( O6 ~1 h8 p) w7 D/ n
- TeacherEntity user = CommonUtil.getUser();
! B0 S4 H7 k+ }" e% R4 c - String ip = AddressUtils.getIpAddr(request);
6 \& v' p: u8 J - try {( L* R+ }4 E: g- p2 |5 p1 I
- String params = "";4 F2 u- c b7 v5 x: q) w
- if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
. X3 M$ m% [/ s( i1 M k. C: {3 Y! m: l" J - for (int i = 0; i < joinPoint.getArgs().length; i++) {
, |& ~/ _. c0 w& i; ~/ F - params += JSONUtil.toJSONstring(joinPoint.getArgs()[i]) + ";";- m4 ^8 \* @9 c% `8 a
- }
, ~/ y. l) l {& f - }; {7 t9 m$ K$ T! R1 P0 h2 C
- String description = getServiceMthodDescription(joinPoint);//用户操作
3 b! m: A8 I: G: x! k! w; f3 ? - String exceptionCode =e.getClass().getName();//异常类型代码* D" q% b# H1 P1 N
- String exceptionDetail = e.getMessage();//异常详细信息
, _+ }& J4 I5 J - String method = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()";//异常方法
1 ?/ {0 _" @( j4 q" p( O - /*==========记录数据库异常日志==========*/ 4 P* G$ z, O& F
- Log log = new Log();
5 ~1 x( J G6 u - log.setDescription(description);
* ?+ ]: w2 M7 p, C& F3 J1 z - log.setExceptionCode(exceptionCode);
( k5 Z* j3 _' ^ - log.setExceptionDetail(exceptionDetail);
4 E0 K7 l* @, B6 ^ - log.setMethod(method);2 ~/ g: N' F4 j# ?
- log.setType(Constants.LOG_ERROE);// 日志类型
$ v* q4 l( v( M+ K: O - log.setRequestIp(ip);// 请求IP2 E4 ~- N, E. s2 Z
- log.setParams(params);//请求参数
" i, w4 R& o- E8 h - if(null!=user){' N# l6 h/ r3 I2 D/ }' q) ~
- log.setCreateUid(user.getUid());//用户ID
# O; [+ e7 x- d9 I - log.setCreateName(user.getNickname());//用户昵称
( E" a) d: s) J% o4 U - }# C& ? ?7 f2 {) I
- log.setPlatFrom(Constants.SUBJECT_CODE);. ]8 `3 x3 F& k' L, ]: o: }7 Z) w
- /*==========记录数本地异常日志==========*/ , O' ~) z+ t- z4 ~* b
- //LogUtil.error(description, e);
: |& q" l. I* g+ V - /*==========发送异常日志到邮箱==========*/
1 ]3 U% s2 f; v6 A3 m6 [8 p - StringBuffer errorMsg = new StringBuffer();8 i! O' d, {. F) C0 d$ w
- errorMsg.append("异常方法:");* F; l& W8 J- V
- errorMsg.append(method);
; G$ |: v# L! L! s - errorMsg.append("</br>");8 {7 i1 F$ b3 c, V. o
- errorMsg.append("异常类型代码:");
9 C; j* \6 f6 s& x3 z9 s" j - errorMsg.append(exceptionCode);% [# B0 Y/ S- P& i) d; m6 V3 B
- errorMsg.append("</br>");
, j6 `$ W: _: _; w - errorMsg.append("异常详细信息:");. p; r6 _5 q7 D* |8 T% q
- errorMsg.append(exceptionDetail);
; @3 M# D' d) T$ c1 L - errorMsg.append("</br>");4 x" A' q3 l9 _0 I/ x8 n
- log.setErrorMsg(errorMsg.toString());; h& Y* b% O9 W0 W$ c
- WebServiceMathClient Client = new WebServiceMathClient();; x7 h7 J2 c& F9 w2 t
- Client.sendError(log);. l+ b+ S5 J# J0 e% l+ ^, r
- } catch (Exception ex) {3 q% B/ R0 Z0 H0 S1 l8 U
- e.printStackTrace();; k' e3 l( |+ ~0 |3 c7 |+ ]* e
- }) J# A4 j# B4 x; q8 b4 N& v D
- }
$ d0 Q2 r) c, Y- i6 _( I7 q - /**$ C$ j2 z7 z @% g7 [, ^
- * 获取注解中对方法的描述信息 用于service层注解 (基于反射)& \ r6 Y' c1 ~
- * @Author 张志朋+ c% C7 k7 D4 x
- * @param joinPoint5 }" U0 W, g' f$ S8 q
- * @return/ y7 K5 y* w" m
- * @throws Exception String6 D1 `5 p" J }+ B/ G
- * @Date 2015年6月3日# h1 g7 Y; v! d
- * 更新日志
: P& s( `2 E! f( h {% Z& Z - * 2015年6月3日 张志朋 首次创建
- a+ j r5 l# g0 S9 z' B" g0 j - *1 ]" @9 k' t% s, \
- */1 B! U- |) f( \
- @SuppressWarnings("rawtypes")
' }0 j* ?! T6 Y, _* `% v - public static String getServiceMthodDescription(JoinPoint joinPoint) ' g0 G6 P2 Q+ G5 ^+ S
- throws Exception { ( m* w& @% A0 k, v' ?, U
- String targetName = joinPoint.getTarget().getClass().getName(); I: V# i5 i; j( l3 u
- String methodName = joinPoint.getSignature().getName(); $ a9 t; `2 q( g3 K5 N
- Object[] arguments = joinPoint.getArgs();
: u) \& M4 C& m" I7 E6 z - Class targetClass = Class.forName(targetName); , I* G; G0 ?0 o
- Method[] methods = targetClass.getMethods(); 2 x1 P8 ~9 ^7 ~( r2 j. z8 f
- String description = "";
m4 u# Z4 Y& L! s - for (Method method : methods) { 6 q0 X) I, F# P$ v
- if (method.getName().equals(methodName)) { ' d" n& `1 ]; U0 z, N
- Class[] clazzs = method.getParameterTypes(); 8 z4 D* r+ T. n( e
- if (clazzs.length == arguments.length) {
( ` k8 [: R: e - description = method.getAnnotation(ServiceLog. class).description();
K6 E1 [& N9 |7 A9 u1 I2 h5 M. J% r - break;
% y$ V4 X- h- M3 m( z/ Q$ ]% | - }
+ P* q R- v, ^, _ - }
% c% a( T9 C2 p - } 3 e& ~% G' z- I/ ]
- return description; , ~ v4 J7 A4 T: }2 S4 F8 p. Q
- }
: ?+ E2 P" ~$ D - }
这里说明一下 serviceAspect()方法 上面注释了 @Pointcut("@annotation(com.acts.web.aop.ServiceLog)") 、也就说明这是一个切入点,springAOP对其进行了封装。# u$ g! \5 u3 w' }7 W0 H4 t& j( _7 y0 f
doAfterThrowing()方法上面加入了@AfterThrowing(pointcut = "serviceAspect()", throwing = "e") 对切入点的所有异常信息进行处理(记录日志到数据库或者发送错误信息到指定邮箱等等等,可以做任何你想做的事情)。, Z7 h; x$ U* D. U, E
5 C3 `* u! D m( c0 e2.QuesPerServiceImpl.java(注意此类必须实现接口 默认JDK的动态代理实现是基于接口实现的 否则会报错)
7 x% _8 P1 [- n- }3 J1 l4 o, `
- @ServiceLog(description="获取待审试题数量")
' C# V1 a3 Y9 t. E5 V B - public long getAuditQuesNum(TeacherEntity currentUser) throws Exception {5 \9 a& ~6 {4 `- |9 ?' E& L& c
- return quesPerDao.getAuditQuesNum(currentUser);
. C( [! t( |/ V7 w5 w( k$ u - }
之所以定义description 描述 是为了更好的记录错误日志 文字总是比方法名 更容易识别。1 C0 Z3 n7 v+ X# ^
二、AOP实现权限控制
0 _( Y' f! I- H+ R4 P3 k& J 上面说过使用动态代理的类 必须实现接口但是我们的action并没有实现接口。 JDK 的动态代理只能对实现了接口的目标类进行代理,而不实现接口的类就不能使用 JDK 的动态代理。$ F% v5 V( H" s. a' `0 }
还好有第三方的包为我们解决了问题。项目中引入cglib.jar,CGLIB 是针对类来实现代理,当没有实现接口的类需要代理时就需要通过 CGLIB 来实现代理了,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但是因为采用的是继承,所以不能对 finall 类进行继承。( {, s ]2 z E! N7 U! ]) l* B
首先配置文件要引入这样一段配置(看注释说明): & `2 m6 Q- q. p3 [7 e* ] s
- <!--通知spring使用cglib而不是jdk的来生成代理方法 AOP可以拦截到Controller(Action)-->
* N% z4 |% b0 h: U" J$ s$ `% U - <aop:aspectj-autoproxy proxy-target-class="true"/>
, F' X o% r8 W* d3 P/ i (1)、定义注释:
0 F( W" ^- s9 a; k% w1.Permission.java
# A, I1 S0 h; T) m C* R2 v
- /**/ N4 f: v' A" C1 X8 g6 d3 r( G& J
- * 自定义权限管理
; ~, [% }9 Y4 E$ ~. y; z - * 创建者 张志朋
3 w) N! C9 J+ `) r2 w( w - * 创建时间 2015年6月30日6 h! R: f* O* W8 R) f* X8 V+ _
- *1 N* y; g7 u- ~5 J% m- R
- */$ t$ a- X- e( T) _
- @Target({ElementType.PARAMETER, ElementType.METHOD})
8 o/ U! L2 f/ }5 ~ - @Retention(RetentionPolicy.RUNTIME)
# s4 w5 l2 ]" h; b - @Documented
8 T* Z7 n8 I8 M+ {0 j - public @interface Permission {
* q: v# L- H% N: B1 {& Y/ ?( \1 o - String name() default ""; //操作行为
7 v0 M K6 V: O4 ~2 ]* F - int id() default -1;//权限值
5 o& M+ Q+ [5 _% S. O - }
1.PsemissionAspect.java 0 U+ g8 C+ _7 g6 K( T: V% }+ A
- /**% V/ {# B- w7 j8 z
- * 权限管理% f! C% F7 {; w) B( c
- * 创建者 张志朋
" l" Q; A$ v5 m" }( W% n - * 创建时间 2015年7月3日
3 l- }$ F1 \: a/ s$ [( U2 n! x* }- O - *% l2 Y! |8 y8 c/ Y. R. O
- */, h; k+ k7 X- I- A5 P z i
- @Component
* \& L( a4 ]# n - @Scope7 J/ [* j# j/ I" ]1 T% x6 N0 Q6 ^
- @Aspect" p X9 T- |+ ?+ t- C2 b6 M
- public class PsemissionAspect {
) _% i0 V1 b/ l: S -
; z1 D1 B. h( E) j - //Controller层切点 用于权限控制
4 v9 C; c4 Q0 B4 O0 {$ K- ~ - @Pointcut("@annotation(com.acts.web.aop.Permission)") ( l* c8 d* V4 E8 P
- public void permissionAspect() {
5 w z/ Q {% Z M u, I8 s -
% Q( @. \" i4 `2 P# f+ s# n" U - }
8 M, [5 B2 L" d6 z! B3 \ - /**
5 w3 k3 f5 T, I$ @" S - * 用于拦截Controller层用户操作权限(环绕通知)$ [% e5 v$ N. m* `) Y
- * @Author 张志朋' f+ |; s' \1 m x, o
- * @param joinPoint void
& Z( j% N/ O. w7 P) g% X' K9 g- [ - * @Date 2015年6月3日
2 b |- Z! T0 M( k - * 更新日志5 Q% }* O/ A- f
- * 2015年6月3日 张志朋 首次创建/ u; i# F0 C5 g# S% [, b- q9 X
- *' N/ T8 _: u( p) {" o, y0 X: A
- */
4 Y; {1 _8 t1 p, p. r$ ~* s - @Around("permissionAspect()") ) x( w# {1 ?. w' l
- public Object permission(ProceedingJoinPoint joinPoint)throws Throwable { 8 o) |) B0 V+ Z3 t
- Object retVal = null;) ^8 U# G% D+ z7 s
- int role = getControllerMethodRole(joinPoint);! K1 [# g6 T5 W# F
- TeacherEntity user = CommonUtil.getUser();! f, y0 S+ q0 l/ W. m! f* S9 J
- if((user.getSpecRole()&role)==role){//没有权限6 T$ c1 \0 E( \+ J$ L h
- retVal = joinPoint.proceed();) z. _3 ?( l5 g% P+ B" q
- }else{/ O* W# X( ?: }" j4 |) ?
- noAuthorization();
5 _' ~; |; L$ P$ n$ R - }8 K+ T" f$ Y9 ~+ w$ s
- return retVal;
! }7 F- o! h2 M9 y6 z0 T, d1 s% I - }& h2 k' S. q/ T! @
- /**
" r7 H z7 r& U b- a - * 没有权限 实现跳转/ o3 g, c6 N: _- B
- * @Author 张志朋
4 \* o( `2 q/ P. C- m - * @throws IOException void
" b* V7 {) k/ E% y( s& ?$ |; w - * @Date 2015年7月3日
% F* W, a3 p. } - * 更新日志
. l2 v8 d9 C- Y6 n$ h - * 2015年7月3日 张志朋 首次创建
) l+ G; i$ a+ |# a$ [ - *
, r- ?) H. ? C - */# M! f l3 `* e6 `
- public void noAuthorization() throws IOException{& h) n% K0 r4 {$ F/ B( F
- HttpServletRequest request = ServletActionContext.getRequest();/ | k8 X0 [# M4 S4 p( y/ I
- String path = request.getContextPath();+ r7 x" D4 v- p- R% E9 r" T
- HttpServletResponse response = ServletActionContext.getResponse();
9 o s$ c8 a" m. S8 G6 n3 ]6 q - response.sendRedirect(path+"/pages/noAuthorization.jsp");
7 j, Y, C, q9 C- g - }/ V+ k% Q) m9 b+ W) f! |- p
- /**4 o5 M E. ?7 F( Q! a7 @" |
- * 获取注解中对方法的权限值 用于Controller层注解
8 k( J2 ~; t ^* K4 A - * @Author 张志朋
2 [6 N. F9 V: M - * @param joinPoint
+ ^- R# }' s1 r h x - * @return
. v* d9 G0 W, v5 z1 t) q5 c - * @throws Exception int' r8 C3 [& v, z7 X, i
- * @Date 2015年7月3日
5 @' S% [" y: I; [! ]* s. x9 b! n5 I4 f - * 更新日志
1 @( \) T. m) S, y' K; Z, c+ E* G - * 2015年7月3日 张志朋 首次创建7 U, T9 U( y2 Y! z
- *2 Z4 ?2 c: Y8 o, T
- */
9 @8 Q9 C$ x8 R& o K7 V7 ^( L - @SuppressWarnings("rawtypes")
8 c' k& S" Z* |7 _9 l - public static int getControllerMethodRole(JoinPoint joinPoint) throws Exception {
& o6 `9 D! I8 w. f - String targetName = joinPoint.getTarget().getClass().getName(); ! `' K; B2 l5 w
- String methodName = joinPoint.getSignature().getName(); ( D* y9 w, o" V' E; W
- Object[] arguments = joinPoint.getArgs(); , F* I# _8 K& H3 ^( ^; s
- Class targetClass = Class.forName(targetName); , r# D, w9 N, R8 S2 @2 A0 c
- Method[] methods = targetClass.getMethods();
3 s7 H7 F4 H9 p% Z3 J# j - int role = -1; * }' t8 i( O# ?% W# w
- for (Method method : methods) { + a# n, }0 f1 X5 E# t4 M( P, C
- if (method.getName().equals(methodName)) { * S9 C8 v/ G8 W5 @3 s8 G y
- Class[] clazzs = method.getParameterTypes();
! E% o" N; b3 U; P - if (clazzs.length == arguments.length) { - i* n; p5 M9 }: u) @
- role = method.getAnnotation(Permission. class).id(); 5 u( i9 ~1 B2 ~
- break;
* B, g1 A4 i. A2 H# b - }
3 G: E: Y( ?; f* }9 X - } ( g* S9 u, x' O. v( D& B1 J& V4 I
- }
" ~' p7 a& d8 _4 P! a - return role;
5 i% {4 P3 O- ? - }
: X N' a& G: o+ n 2.action层代码实现: 3 j. @6 G! b. H* z
- /**
$ e4 K; e( u9 |: w. v" W0 J3 y - * 试题审核不通过
3 Q- S' a8 g2 N* @. p# m8 ~ - * @Author 张志朋 void
, h6 F/ v% s( } - * @Date 2015年5月4日
& [- w* s k O% c9 \" F0 \- A. r - * 更新日志. R5 [/ y4 i1 E) L/ P
- * 2015年5月4日 张志朋 首次创建9 q' `2 s) k! k# r1 v! M$ R( O
- *: }. L" Z! S Q
- */
; ]$ a I- Q5 i1 l0 J& t - @Permission(name="审核试题权限(审核不通过)",id=Constants.ROLE_QUES_AUDIT)2 U, ]( e; H$ z
- public void auditQuestions(){
6 _! V+ i! M7 k - try {
$ F0 h; h' u+ ~) }0 c0 N5 h" P7 E - //代码实现
. \3 F. o8 r2 t& a- U2 q" u/ f$ u - } catch (Exception e) {' b4 H' W+ @! i0 M
- e.printStackTrace();
) ]+ \5 j5 v# _# Z - }- G& Y$ {* t" ~
- # {8 s$ ~+ |2 ~7 c; U2 l
- }