1.对AOP的理解 —— 分工来做各个部分,运行时候整合的思想
2.理解 面向过程,面向对象,面向切面 的思想
1)面向过程:房间装修时,准备装一个灯,就拉一根电线,连接灯。
2)面向对象:设计房间中哪些位置需要使用电线接口,然后在相应的位置设置电线接口,以备以后使用。
3)面向切面:装修房子,先设计需要在哪些地方装上电线接口,就将电线接口先设置好并且不打开接口,此处即为连接点,当此处电线切口确实需要使用时将接口打开插电器即为切入点。
方面:功能(登陆 日志)
目标:主要方面(登陆)
切面:切入点 植入 通知的综合体
连接点:可以插入副功能(日志)的地方
切入点:准备插入副功能的地方
通知:对副功能的封装对象
植入:将通知插入切入点
3.实现登陆和日志管理(使用Spring AOP)
1)LoginService LogService TestMain
2)用Spring 管理 LoginService 和 LogService 的对象
3)确定哪些连接点是切入点,在配置文件中
4)将LogService封装为通知
5)将通知植入到切入点
6)客户端调用目标
- <aop:config>
- <aop:pointcut expression="execution(* cn.com.spring.service.impl.*.*(..))" id="myPointcut"/>
- <!--将哪个-->
- <aop:aspect id="dd" ref="logService">
- <aop:before method="log" pointcut-ref="myPointcut"/>
- </aop:aspect>
- </aop:config>
execution(* * cn.com.spring.service.impl.*.*(..))
1)* 所有的修饰符
2)* 所有的返回类型
3)* 所有的类名
4)* 所有的方法名
5)* ..所有的参数名
1.ILoginService.java
- package cn.com.spring.service;
-
- public interface ILoginService {
- public boolean login(String userName, String password);
- }
- package cn.com.spring.service.impl;
-
- import cn.com.spring.service.ILoginService;
-
- public class LoginServiceImpl implements ILoginService {
-
- public boolean login(String userName, String password) {
- System.out.println("login:" + userName + "," + password);
- return true;
- }
-
- }
3.ILogService.java
- package cn.com.spring.service;
-
- import org.aspectj.lang.JoinPoint;
-
- public interface ILogService {
- //无参的日志方法
- public void log();
- //有参的日志方法
- public void logArg(JoinPoint point);
- //有参有返回值的方法
- public void logArgAndReturn(JoinPoint point,Object returnObj);
- }
4.LogServiceImpl.java
- package cn.com.spring.service.impl;
-
- import org.aspectj.lang.JoinPoint;
-
- import cn.com.spring.service.ILogService;
-
- public class LogServiceImpl implements ILogService {
-
- @Override
- public void log() {
- System.out.println("*************Log*******************");
- }
-
- //有参无返回值的方法
- public void logArg(JoinPoint point) {
- //此方法返回的是一个数组,数组中包括request以及ActionCofig等类对象
- Object[] args = point.getArgs();
- System.out.println("目标参数列表:");
- if (args != null) {
- for (Object obj : args) {
- System.out.println(obj + ",");
- }
- System.out.println();
- }
- }
-
- //有参并有返回值的方法
- public void logArgAndReturn(JoinPoint point, Object returnObj) {
- //此方法返回的是一个数组,数组中包括request以及ActionCofig等类对象
- Object[] args = point.getArgs();
- System.out.println("目标参数列表:");
- if (args != null) {
- for (Object obj : args) {
- System.out.println(obj + ",");
- }
- System.out.println();
- System.out.println("执行结果是:" + returnObj);
- }
- }
- }
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:p="http://www.springframework.org/schema/p"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
-
- <bean id="logService" class="cn.com.spring.service.impl.LogServiceImpl"></bean>
- <bean id="loginService" class="cn.com.spring.service.impl.LoginServiceImpl"></bean>
-
- <aop:config>
- <!-- 切入点 -->
- <aop:pointcut
- expression="execution(* cn.com.spring.service.impl.LoginServiceImpl.*(..))"
- id="myPointcut" />
- <!-- 切面: 将哪个对象中的哪个方法,织入到哪个切入点 -->
- <aop:aspect id="dd" ref="logService">
- <!-- 前置通知
- <aop:before method="log" pointcut-ref="myPointcut" />
- <aop:after method="logArg" pointcut-ref="myPointcut">
- -->
- <aop:after-returning method="logArgAndReturn" returning="returnObj" pointcut-ref="myPointcut"/>
- </aop:aspect>
- </aop:config>
- </beans>
- public class TestMain {
- public static void testSpringAOP(){
- ApplicationContext ctx = new ClassPathXmlApplicationContext("app*.xml");
-
- ILoginService loginService = (ILoginService)ctx.getBean("loginService");
- loginService.login("zhangsan", "12344");
- }
- public static void main(String[] args) {
- testSpringAOP();
- }
- }
- login:zhangsan,12344
- 目标参数列表:
- zhangsan,
- 12344,
-
- 执行结果是:true
2.再调用了logArgAndReturn()方法输出了日志,并且返回了login()方法是否成功
- System.out.println("目标参数列表:");
- if (args != null) {
- for (Object obj : args) {
- System.out.println(obj + ",");
- }
- System.out.println();
- System.out.println("执行结果是:" + returnObj);
- }
- <aop:config>
- <aop:pointcut expression="execution(* cn.com.spring.service.impl.*.*(..))" id="myPointcut"/>
- <!--将哪个-->
- <aop:aspect id="dd" ref="logService">
- <aop:before method="log" pointcut-ref="myPointcut"/>
- </aop:aspect>
- </aop:config>
execution(* * cn.com.spring.service.impl.*.*(..))
1)* 所有的修饰符
2)* 所有的返回类型
3)* 所有的类名
4)* 所有的方法名
5)* ..所有的参数名
1.ILoginService.java
- package cn.com.spring.service;
-
- public interface ILoginService {
- public boolean login(String userName, String password);
- }
- package cn.com.spring.service.impl;
-
- import cn.com.spring.service.ILoginService;
-
- public class LoginServiceImpl implements ILoginService {
-
- public boolean login(String userName, String password) {
- System.out.println("login:" + userName + "," + password);
- return true;
- }
-
- }
3.ILogService.java
- package cn.com.spring.service;
-
- import org.aspectj.lang.JoinPoint;
-
- public interface ILogService {
- //无参的日志方法
- public void log();
- //有参的日志方法
- public void logArg(JoinPoint point);
- //有参有返回值的方法
- public void logArgAndReturn(JoinPoint point,Object returnObj);
- }
4.LogServiceImpl.java
- package cn.com.spring.service.impl;
-
- import org.aspectj.lang.JoinPoint;
-
- import cn.com.spring.service.ILogService;
-
- public class LogServiceImpl implements ILogService {
-
- @Override
- public void log() {
- System.out.println("*************Log*******************");
- }
-
- //有参无返回值的方法
- public void logArg(JoinPoint point) {
- //此方法返回的是一个数组,数组中包括request以及ActionCofig等类对象
- Object[] args = point.getArgs();
- System.out.println("目标参数列表:");
- if (args != null) {
- for (Object obj : args) {
- System.out.println(obj + ",");
- }
- System.out.println();
- }
- }
-
- //有参并有返回值的方法
- public void logArgAndReturn(JoinPoint point, Object returnObj) {
- //此方法返回的是一个数组,数组中包括request以及ActionCofig等类对象
- Object[] args = point.getArgs();
- System.out.println("目标参数列表:");
- if (args != null) {
- for (Object obj : args) {
- System.out.println(obj + ",");
- }
- System.out.println();
- System.out.println("执行结果是:" + returnObj);
- }
- }
- }
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:p="http://www.springframework.org/schema/p"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
-
- <bean id="logService" class="cn.com.spring.service.impl.LogServiceImpl"></bean>
- <bean id="loginService" class="cn.com.spring.service.impl.LoginServiceImpl"></bean>
-
- <aop:config>
- <!-- 切入点 -->
- <aop:pointcut
- expression="execution(* cn.com.spring.service.impl.LoginServiceImpl.*(..))"
- id="myPointcut" />
- <!-- 切面: 将哪个对象中的哪个方法,织入到哪个切入点 -->
- <aop:aspect id="dd" ref="logService">
- <!-- 前置通知
- <aop:before method="log" pointcut-ref="myPointcut" />
- <aop:after method="logArg" pointcut-ref="myPointcut">
- -->
- <aop:after-returning method="logArgAndReturn" returning="returnObj" pointcut-ref="myPointcut"/>
- </aop:aspect>
- </aop:config>
- </beans>
- public class TestMain {
- public static void testSpringAOP(){
- ApplicationContext ctx = new ClassPathXmlApplicationContext("app*.xml");
-
- ILoginService loginService = (ILoginService)ctx.getBean("loginService");
- loginService.login("zhangsan", "12344");
- }
- public static void main(String[] args) {
- testSpringAOP();
- }
- }
- login:zhangsan,12344
- 目标参数列表:
- zhangsan,
- 12344,
-
- 执行结果是:true
2.再调用了logArgAndReturn()方法输出了日志,并且返回了login()方法是否成功
补充一篇AOP注解方式实现的
利用spring AOP实现每个请求的日志输出
前提条件:
除了spring相关jar包外,还需要引入aspectj包。
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.7.2</version> </dependency>
要实现此功能,必须完成以下几步:
1.在springmvc-servlet.xml中实现对AOP的支持
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <aop:aspectj-autoproxy proxy-target-class="true"/> <bean class="com.yusj.interceptor.LogAspect" /> . . . . </beans>
2.注解的方法实现Aspect
package com.yusj.core.interceptor;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.google.gson.Gson;
/**
*
* @ClassName: LogAspect
* @Description: 日志记录AOP实现
* @author shaojian.yu
* @date 2014年11月3日 下午1:51:59
*
*/
@Aspect
public class LogAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private String requestPath = null ; // 请求地址
private String userName = null ; // 用户名
private Map<?,?> inputParamMap = null ; // 传入参数
private Map<String, Object> outputParamMap = null; // 存放输出结果
private long startTimeMillis = 0; // 开始时间
private long endTimeMillis = 0; // 结束时间
/**
*
* @Title:doBeforeInServiceLayer
* @Description: 方法调用前触发
* 记录开始时间
* @author shaojian.yu
* @date 2014年11月2日 下午4:45:53
* @param joinPoint
*/
@Before("execution(* com.yusj.controller..*.*(..))")
public void doBeforeInServiceLayer(JoinPoint joinPoint) {
startTimeMillis = System.currentTimeMillis(); // 记录方法开始执行的时间
}
/**
*
* @Title:doAfterInServiceLayer
* @Description: 方法调用后触发
* 记录结束时间
* @author shaojian.yu
* @date 2014年11月2日 下午4:46:21
* @param joinPoint
*/
@After("execution(* com.yusj.controller..*.*(..))")
public void doAfterInServiceLayer(JoinPoint joinPoint) {
endTimeMillis = System.currentTimeMillis(); // 记录方法执行完成的时间
this.printOptLog();
}
/**
*
* @Title:doAround
* @Description: 环绕触发
* @author shaojian.yu
* @date 2014年11月3日 下午1:58:45
* @param pjp
* @return
* @throws Throwable
*/
@Around("execution(* com.yusj.controller..*.*(..))")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
/**
* 1.获取request信息
* 2.根据request获取session
* 3.从session中取出登录用户信息
*/
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes)ra;
HttpServletRequest request = sra.getRequest();
// 从session中获取用户信息
String loginInfo = (String) session.getAttribute("username");
if(loginInfo != null && !"".equals(loginInfo)){
userName = operLoginModel.getLogin_Name();
}else{
userName = "用户未登录" ;
}
// 获取输入参数
inputParamMap = request.getParameterMap();
// 获取请求地址
requestPath = request.getRequestURI();
// 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
outputParamMap = new HashMap<String, Object>();
Object result = pjp.proceed();// result的值就是被拦截方法的返回值
outputParamMap.put("result", result);
return result;
}
/**
*
* @Title:printOptLog
* @Description: 输出日志
* @author shaojian.yu
* @date 2014年11月2日 下午4:47:09
*/
private void printOptLog() {
Gson gson = new Gson(); // 需要用到google的gson解析包
String optTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(startTimeMillis);
logger.info("\n user:"+userName
+" url:"+requestPath+"; op_time:" + optTime + " pro_time:" + (endTimeMillis - startTimeMillis) + "ms ;"
+" param:"+gson.toJson(inputParamMap)+";"+"\n result:"+gson.toJson(outputParamMap));
}
}
这里
spring的ServletRequestAttributes创建request()得到空指针
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
查看RequestContextHolder源代码,发现该类中有提到RequestContextListener
再查看RequestContextListener的源代码,其中有个方法
public void requestInitialized(ServletRequestEvent requestEvent) {
if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
throw new IllegalArgumentException(
"Request is not an HttpServletRequest: " +
requestEvent.getServletRequest());
}
HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
LocaleContextHolder.setLocale(request.getLocale());
RequestContextHolder.setRequestAttributes(attributes); //把requestAttributes的属性设置好
if (logger.isDebugEnabled()) {
logger.debug("Bound request context to thread: " + request);
} }
所以,要在web.xml下面配置好监听,让服务器启动时就初始化改类,可以得到request
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
这样就可以了正常执行
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();