Spring AOP
在前面的文章中已经和大家分享过关于spring IOC的知识,已经通过他的实现机制。我们都知道spring的两大核心:AOP(面向切面)和IOC(控制反转),本篇我们就一起学习一下AOP的知识的。
这里分享一个问题?当我们软件开发完成后,需要给每一个方法添加操作日志,我们怎么操作呢?我想最简单的方法就是在每一个方法的开始前将我们的日志逻辑加入,当然这是最直接的一种方法,但是他的缺点也是很明细,如果我们的方法有很多,添加这个日志逻辑就需要很多的工作量,显然这是一种不可取的方式。如何更好的解决这个问题呢?spring很好的帮我们处理了这个难点,通过切面编程,我们可以在我们需要的切面添加相应的业务逻辑已达到我们需要的效果。
接下来开始我们的内容,AOP的实现借助于JAVA的动态代理知识,我先通过动态代理的方式为大家介绍一下AOP的实现原理,以便大家更好的理解。
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?
proxy: 指代我们所代理的那个真实对象
method: 指代的是我们所要调用真实对象的某个方法的Method对象
args: 指代的是调用真实对象某个方法时接受的参数
这里我以一个添加日志需求为因,实现一个这个过程:
public class LogInterception implements InvocationHandler { private Object target; public void setTarget(Object target) { this.target = target; } private void beforeMethod(){ System.out.println("切面添加日志开始"); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub beforeMethod(); Object result = method.invoke(target, args); afterMethod(); return result; } private void afterMethod(){ System.out.println("切面添加日志结束"); } }
我们写好了代理,下面我们看一下如何将其加入到方法中使用:
//添加日志管理切面 LogInterception log = new LogInterception(); log.setTarget(userService); IUserService userServiceProxy = (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), new Class[]{IUserService.class}, log); // IUserService userServiceProxy = (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), log); boolean flag = userServiceProxy.add();
这里简单为大家解释一下,首先我们声明动态代理的类,通过我们对外提供的setTarget方法将我们的代理类注入,然后通Proxy.newProxyInstance得到我们代理方法的代理对象,然后通过调用我们的代理对象实现我们的动态代理。
public class UserAction extends ActionSupport implements ServletRequestAware, ServletResponseAware{ private IUserService userService; private HttpServletRequest request; private HttpSession session; private HttpServletResponse response; /** * @Description 添加用户测试 * @throws IOException * * @author 高尚 * @version 1.0 * @date 创建时间:2017年12月13日 上午10:56:30 */ public void addJdkInterception(){ Map<String, Object> result = new HashMap<String, Object>(); result.put("status", 1); result.put("msg", "操作成功"); System.out.println("action操作开始"); //添加日志管理切面 LogInterception log = new LogInterception(); log.setTarget(userService); IUserService userServiceProxy = (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), new Class[]{IUserService.class}, log); // IUserService userServiceProxy = (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), log); boolean flag = userServiceProxy.add(); result.put("data", flag); System.out.println("action操作结束"); //输出到客户端 PrintWriter writer = null; try { if(null != response){ response.setContentType("text/html;charset=UTF-8");// 解决中文乱码 response.setCharacterEncoding("UTF-8"); writer = response.getWriter(); writer.write(JSONObject.toJSONString(result)); }else{ System.out.println(JSONObject.toJSONString(result)); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { if(null != writer){ writer.flush(); writer.close(); } } } public void setUserService(IUserService userService) { this.userService = userService; } @Override public void setServletResponse(HttpServletResponse response) { // TODO Auto-generated method stub this.response = response; } @Override public void setServletRequest(HttpServletRequest request) { // TODO Auto-generated method stub this.request = request; this.session = this.request.getSession(); } }
启动我们的项目试一把,通过控制台的日志信息,我想你一定能理解java动态代理的机制。
通过上面的内容,相信对于你理解spring aop的知识一定很有帮助。下面我们来看一下srping aop的知识:
首先我们搭建一个简单的spring开发环境:
1、dao接口和实现类
public interface IUserDao { boolean add(); void delete(); } public class UserDaoImpl implements IUserDao { @Override public boolean add() { System.out.println("dao添加操作成功"); return false; } @Override public void delete() { System.out.println("dao删除操作成功"); } }
2、service接口和实现类
public interface IUserService { boolean add(); void delete(); } public class UserServiceImpl implements IUserService { private IUserDao userDao; @Override public boolean add() { System.out.println("service添加操作开始"); userDao.add(); System.out.println("service添加操作结束"); return false; } @Override public void delete() { System.out.println("service删除操作开始"); userDao.delete(); System.out.println("service删除操作结束"); } public void setUserDao(IUserDao userDao) { this.userDao = userDao; } }
3、action实现类
public class UserAction extends ActionSupport implements ServletRequestAware, ServletResponseAware{ private IUserService userService; private HttpServletRequest request; private HttpSession session; private HttpServletResponse response; /** * @Description 添加用户测试 * @throws IOException * * @author 高尚 * @version 1.0 * @date 创建时间:2017年12月13日 上午10:56:30 */ public void add(){ Map<String, Object> result = new HashMap<String, Object>(); result.put("status", 1); result.put("msg", "操作成功"); System.out.println("action操作开始"); boolean flag = userService.add(); result.put("data", flag); System.out.println("action操作结束"); //输出到客户端 PrintWriter writer = null; try { if(null != response){ response.setContentType("text/html;charset=UTF-8");// 解决中文乱码 response.setCharacterEncoding("UTF-8"); writer = response.getWriter(); writer.write(JSONObject.toJSONString(result)); }else{ System.out.println(JSONObject.toJSONString(result)); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { if(null != writer){ writer.flush(); writer.close(); } } } /** * @Description 删除用户测试 * @throws IOException * * @author 高尚 * @version 1.0 * @date 创建时间:2017年12月13日 上午10:56:30 */ public void delete(){ Map<String, Object> result = new HashMap<String, Object>(); result.put("status", 1); result.put("msg", "操作成功"); System.out.println("action操作开始"); userService.delete(); result.put("data", true); System.out.println("action操作结束"); //输出到客户端 PrintWriter writer = null; try { if(null != response){ response.setContentType("text/html;charset=UTF-8");// 解决中文乱码 response.setCharacterEncoding("UTF-8"); writer = response.getWriter(); writer.write(JSONObject.toJSONString(result)); }else{ System.out.println(JSONObject.toJSONString(result)); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { if(null != writer){ writer.flush(); writer.close(); } } } public void setUserService(IUserService userService) { this.userService = userService; } @Override public void setServletResponse(HttpServletResponse response) { // TODO Auto-generated method stub this.response = response; } @Override public void setServletRequest(HttpServletRequest request) { // TODO Auto-generated method stub this.request = request; this.session = this.request.getSession(); } }
4、切面逻辑
public class TimeIntercepation { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); public void before(){ System.out.println("执行开始时间:" + sdf.format(new Date())); } public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("around 执行前"); Object result = pjp.proceed();//获得方法执行后的返回参数 System.out.println("around 执行后"); return result; } public void after(){ System.out.println("执行开始时间:" + sdf.format(new Date())); } public void afterReturning(){ System.out.println("方法正常执行完毕"); } public void throwing(){ System.out.println("方法执行出现异常"); } }
5、spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <bean name="userDao" class="com.hpugs.spring.one.dao.UserDaoImpl" lazy-init="true"></bean> <bean name="userService" class="com.hpugs.spring.one.service.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean> <bean name="userAction" class="com.hpugs.spring.one.action.UserAction" scope="prototype"> <property name="userService" ref="userService"></property> </bean> <bean name="timeIntercepation" class="com.hpugs.spring.one.interception.TimeIntercepation" lazy-init="true"></bean> <aop:config> <aop:pointcut expression="execution(* com.hpugs.spring.one.service.*.*(..))" id="serviceIntercepation"/> <aop:aspect id="timeAspect" ref="timeIntercepation"> <aop:before method="before" pointcut-ref="serviceIntercepation"/> <aop:around method="around" pointcut-ref="serviceIntercepation"/> <aop:after-returning method="afterReturning" pointcut-ref="serviceIntercepation"/> <aop:after method="after" pointcut-ref="serviceIntercepation"/> <aop:after-throwing method="throwing" pointcut-ref="serviceIntercepation"/> </aop:aspect> </aop:config> </beans>
这里简单解释一下,aop:config:设置spring切面,添加切面操作;aop:pointcut:声明切面;aop:aspect:添加切面操作
6、status配置
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <!-- 取消 struts2 动态方法调用 --> <constant name="struts.enable.DynamicMethodInvocation" value="false" /> <!-- 是否启用开发模式 --> <constant name="struts.devMode" value="false" /> <!-- 所有匹配 *.action 的请求都由 struts2 处理 --> <constant name="struts.action.extension" value="action" /> <!-- struts 配置文件改动后,是否重新加载 --> <constant name="struts.configuration.xml.reload" value="true" /> <!-- 设置浏览器是否缓存静态内容 --> <constant name="struts.serve.static.browserCache" value="false" /> <!-- 自动重新加载映射加载 --> <constant name="struts.convention.classes.reload" value="true"/> <!-- action 对象是由Spring负责创建 --> <constant name="struts.objectFactory" value="spring" /> <package name="user" namespace="/" extends="struts-default"> <action name="user_add" class="userAction" method="add"></action> <action name="user_delete" class="userAction" method="delete"></action> </package> </struts>
7、web.xml配置
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping> <web:welcome-file-list> <web:welcome-file>index.jsp</web:welcome-file> </web:welcome-file-list>
8、junit单元测试
@Test public void addTest(){ //注入Spring Bean ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //获取UserAction对象 UserAction userAction = (UserAction) applicationContext.getBean("userAction"); //都有添加操作 userAction.add(); }
9、单元测试结果:
action操作开始
执行开始时间:2018-01-18 15:34:14.412
around 执行前
service添加操作开始
dao添加操作成功
service添加操作结束
around 执行后
方法正常执行完毕
执行开始时间:2018-01-18 15:34:14.412
action操作结束
{"msg":"操作成功","data":false,"status":1}
到这里我们通过xml的方式就实现了方法操作时间切面方法的注入。
最后在为大家介绍一下关于spring annotation添加切面的实现:
首先使我们的spring配置文件,添加注解事务
<!-- <bean name="timeIntercepation" class="com.hpugs.spring.one.interception.TimeIntercepation" lazy-init="true"></bean> --> <context:annotation-config></context:annotation-config> <context:component-scan base-package="com.hpugs.spring.one.interception"></context:component-scan> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
这里解释一下,我们可以通过bean的方式将我们的代理方法注入,但是不推荐大家使用,这里推荐大家使用包扫描的方式进行代理方法注入。
通过注解的方式进行切面声明:
@Aspect @Component public class TimeIntercepation { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Pointcut("execution(* com.hpugs.spring.one.service.*.*(..))") public void myMethod(){} @Before("execution(* com.hpugs.spring.one.service.*.*(..))") public void before(){ System.out.println("执行开始时间:" + sdf.format(new Date())); } @Around("myMethod()") public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("around 执行前"); Object result = pjp.proceed();//获得方法执行后的返回参数 System.out.println("around 执行后"); return result; } @After("execution(* com.hpugs.spring.one.service.*.*(..))") public void after(){ System.out.println("执行开始时间:" + sdf.format(new Date())); } @AfterReturning("myMethod()") public void afterReturning(){ System.out.println("方法正常执行完毕"); } @AfterThrowing("myMethod()") public void throwing(){ System.out.println("方法执行出现异常"); } }
这里解释一下myMethod方法,如果我们的方法共用切面时,我们可以通过这种方式将面声明进行复用。
关于Spring AOP的内容就和大家探讨到这里,以上源代码下载地址:https://github.com/hpugs/Spring-aop