一般我们的异常都会抛出到控制层,如果使用struts2也就是action。然后try{//正确代码实现}catch{//在里面记录错误日志},这样咋一看是不错,代码很完美。但是如果项目中有成千上万个项目怎么办?难道在每个action的catch里面都要加入异常记录代码?很显然工作量是很大的。7 `, X, R" [ \1 [2 D
鉴于项目中配置了数据库事务,其实也是使用了AOP 详见applicationContext-service.xml配置文件。由于配置文件的局限性这里采用注释的方式实现。之所有使用注释是可以自定义参数并实现日志的详细记录。既然使用注释,现成的没有,只有自己定义来实现需求。其实spring 注释实现的注入、hibernate的model类注释实现与数据库关联以及我们最常见Override。
资料:java如何实现自定义注释http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html
一、 记录日志并发送邮件通知
(1)、定义注解:
1.ServiceLog.java(各种参数类型详见上面的资料)
- import java.lang.annotation.*;
- /**
- * 自定义注解 拦截service
- * 创建者 张志朋
- * 创建时间 2015年6月3日
- */
- @Target({ElementType.PARAMETER, ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface ServiceLog {
- String description() default "";
- }
- import java.lang.annotation.*; 1 O6 b$ f+ A5 N G5 R
- /**
- * 自定义注解 拦截Controller: g' a) s9 S- b- [% Z+ v" s
- * 创建者 张志朋
- * 创建时间 2015年6月3日
- *
- */
- @Target({ElementType.PARAMETER, ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME) * D. u, W' W3 d. P9 J! h
- @Documented
- public @interface ControllerLog {
- String description() default "";
- }
1.LogAspect.java
- /**
- * 日志记录AOP+ ~# G$ `. M' W+ b
- * 创建者 张志朋* U3 Q. a' V* d' c+ {1 |' Q i/ H" A
- * 创建时间 2015年6月3日
- *
- */* a& I5 G, i9 [
- @Component
- @Scope$ w& p0 A1 g7 h5 |: V# p! ?2 T7 P
- @Aspect
- public class LogAspect {. n [5 j- F( q( ^8 Q& i3 X7 [
- //Service层切点 用于记录错误日志
- @Pointcut("@annotation(com.web.aop.ServiceLog)")
- public void serviceAspect() {$ g: a. u5 m. Y" w
- 6 C9 [5 x" }3 m2 z
- }; ]7 {, \" L1 }) [ l1 t' i
- /**
- * 异常通知 用于拦截service层记录异常日志 ! J( E: g* X' T ~ c5 Y
- * @Author 张志朋
- * @param joinPoint- L3 l7 g* T1 F$ H z- m
- * @param e void B- K3 D" I. H3 D) `
- * @Date 2015年6月3日
- * 更新日志9 N- S: z6 {2 B* q5 K: C! ^, H$ w
- * 2015年6月3日 张志朋 首次创建/ f( k# a5 t/ X7 p( r, f
- *
- */! ^' 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();
- String ip = AddressUtils.getIpAddr(request);
- 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) {
- for (int i = 0; i < joinPoint.getArgs().length; i++) {
- params += JSONUtil.toJSONstring(joinPoint.getArgs()[i]) + ";";- m4 ^8 \* @9 c% `8 a
- }
- }; {7 t9 m$ K$ T! R1 P0 h2 C
- String description = getServiceMthodDescription(joinPoint);//用户操作
- String exceptionCode =e.getClass().getName();//异常类型代码* D" q% b# H1 P1 N
- String exceptionDetail = e.getMessage();//异常详细信息
- String method = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()";//异常方法
- /*==========记录数据库异常日志==========*/ 4 P* G$ z, O& F
- Log log = new Log();
- log.setDescription(description);
- log.setExceptionCode(exceptionCode);
- log.setExceptionDetail(exceptionDetail);
- log.setMethod(method);2 ~/ g: N' F4 j# ?
- log.setType(Constants.LOG_ERROE);// 日志类型
- log.setRequestIp(ip);// 请求IP2 E4 ~- N, E. s2 Z
- log.setParams(params);//请求参数
- if(null!=user){' N# l6 h/ r3 I2 D/ }' q) ~
- log.setCreateUid(user.getUid());//用户ID
- log.setCreateName(user.getNickname());//用户昵称
- }# 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);
- /*==========发送异常日志到邮箱==========*/
- StringBuffer errorMsg = new StringBuffer();8 i! O' d, {. F) C0 d$ w
- errorMsg.append("异常方法:");* F; l& W8 J- V
- errorMsg.append(method);
- errorMsg.append("</br>");8 {7 i1 F$ b3 c, V. o
- errorMsg.append("异常类型代码:");
- errorMsg.append(exceptionCode);% [# B0 Y/ S- P& i) d; m6 V3 B
- errorMsg.append("</br>");
- errorMsg.append("异常详细信息:");. p; r6 _5 q7 D* |8 T% q
- errorMsg.append(exceptionDetail);
- 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
- }
- /**$ 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
- * 更新日志
- * 2015年6月3日 张志朋 首次创建
- *1 ]" @9 k' t% s, \
- */1 B! U- |) f( \
- @SuppressWarnings("rawtypes")
- 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();
- 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 = "";
- 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) {
- description = method.getAnnotation(ServiceLog. class).description();
- break;
- }
- }
- } 3 e& ~% G' z- I/ ]
- return description; , ~ v4 J7 A4 T: }2 S4 F8 p. Q
- }
- }
这里说明一下 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
2.QuesPerServiceImpl.java(注意此类必须实现接口 默认JDK的动态代理实现是基于接口实现的 否则会报错)
- @ServiceLog(description="获取待审试题数量")
- public long getAuditQuesNum(TeacherEntity currentUser) throws Exception {5 \9 a& ~6 {4 `- |9 ?' E& L& c
- return quesPerDao.getAuditQuesNum(currentUser);
- }
之所以定义description 描述 是为了更好的记录错误日志 文字总是比方法名 更容易识别。1 C0 Z3 n7 v+ X# ^
二、AOP实现权限控制
上面说过使用动态代理的类 必须实现接口但是我们的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)-->
- <aop:aspectj-autoproxy proxy-target-class="true"/>
(1)、定义注释:
1.Permission.java
- /**/ N4 f: v' A" C1 X8 g6 d3 r( G& J
- * 自定义权限管理
- * 创建者 张志朋
- * 创建时间 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})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface Permission {
- String name() default ""; //操作行为
- int id() default -1;//权限值
- }
1.PsemissionAspect.java 0 U+ g8 C+ _7 g6 K( T: V% }+ A
- /**% V/ {# B- w7 j8 z
- * 权限管理% f! C% F7 {; w) B( c
- * 创建者 张志朋
- * 创建时间 2015年7月3日
- *% l2 Y! |8 y8 c/ Y. R. O
- */, h; k+ k7 X- I- A5 P z i
- @Component
- @Scope7 J/ [* j# j/ I" ]1 T% x6 N0 Q6 ^
- @Aspect" p X9 T- |+ ?+ t- C2 b6 M
- public class PsemissionAspect {
-
- //Controller层切点 用于权限控制
- @Pointcut("@annotation(com.acts.web.aop.Permission)") ( l* c8 d* V4 E8 P
- public void permissionAspect() {
-
- }
- /**
- * 用于拦截Controller层用户操作权限(环绕通知)$ [% e5 v$ N. m* `) Y
- * @Author 张志朋' f+ |; s' \1 m x, o
- * @param joinPoint void
- * @Date 2015年6月3日
- * 更新日志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
- */
- @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();
- }8 K+ T" f$ Y9 ~+ w$ s
- return retVal;
- }& h2 k' S. q/ T! @
- /**
- * 没有权限 实现跳转/ o3 g, c6 N: _- B
- * @Author 张志朋
- * @throws IOException void
- * @Date 2015年7月3日
- * 更新日志
- * 2015年7月3日 张志朋 首次创建
- *
- */# 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();
- response.sendRedirect(path+"/pages/noAuthorization.jsp");
- }/ V+ k% Q) m9 b+ W) f! |- p
- /**4 o5 M E. ?7 F( Q! a7 @" |
- * 获取注解中对方法的权限值 用于Controller层注解
- * @Author 张志朋
- * @param joinPoint
- * @return
- * @throws Exception int' r8 C3 [& v, z7 X, i
- * @Date 2015年7月3日
- * 更新日志
- * 2015年7月3日 张志朋 首次创建7 U, T9 U( y2 Y! z
- *2 Z4 ?2 c: Y8 o, T
- */
- @SuppressWarnings("rawtypes")
- public static int getControllerMethodRole(JoinPoint joinPoint) throws Exception {
- 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();
- 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();
- if (clazzs.length == arguments.length) { - i* n; p5 M9 }: u) @
- role = method.getAnnotation(Permission. class).id(); 5 u( i9 ~1 B2 ~
- break;
- }
- } ( g* S9 u, x' O. v( D& B1 J& V4 I
- }
- return role;
- }
2.action层代码实现: 3 j. @6 G! b. H* z
- /**
- * 试题审核不通过
- * @Author 张志朋 void
- * @Date 2015年5月4日
- * 更新日志. R5 [/ y4 i1 E) L/ P
- * 2015年5月4日 张志朋 首次创建9 q' `2 s) k! k# r1 v! M$ R( O
- *: }. L" Z! S Q
- */
- @Permission(name="审核试题权限(审核不通过)",id=Constants.ROLE_QUES_AUDIT)2 U, ]( e; H$ z
- public void auditQuestions(){
- try {
- //代码实现
- } catch (Exception e) {' b4 H' W+ @! i0 M
- e.printStackTrace();
- }- G& Y$ {* t" ~
- # {8 s$ ~+ |2 ~7 c; U2 l
- }