Spring--AOP 例子
先用代码讲一下什么是传统的AOP(面向切面编程)编程
需求:实现一个简单的计算器,在每一步的运算前添加日志。最传统的方式如下:
Calculator.Java
- package cn.limbo.spring.aop.calculator;
- /**
- * Created by Limbo on 16/7/14.
- */
- public interface Calculator {
- int add(int i , int j);
- int sub(int i , int j);
- int mul(int i , int j);
- int div(int i , int j);
- }
CalculatorImpl.java
- package cn.limbo.spring.aop.calculator;
- /**
- * Created by Limbo on 16/7/14.
- */
- public class CalculatorImpl implements Calculator {
- @Override
- public int add(int i, int j) {
- System.out.println("The method add begin with [ "+ i +"," + j+" ]");
- System.out.println("The method add end with [ "+ i +"," + j+"]");
- return i + j;
- }
- @Override
- public int sub(int i, int j) {
- System.out.println("The method sub begin with [ "+ i +"," + j+" ]");
- System.out.println("The method sub end with [ "+ i +"," + j+" ]");
- return i - j;
- }
- @Override
- public int mul(int i, int j) {
- System.out.println("The method mul begin with [ "+ i +"," + j+" ]");
- System.out.println("The method mul end with [ "+ i +"," + j+" ]");
- return i * j;
- }
- @Override
- public int div(int i, int j) {
- System.out.println("The method div begin with [ "+ i +"," + j+" ]");
- System.out.println("The method div end with [ "+ i +"," + j+" ]");
- return i / j;
- }
- }
这样就完成了需求,但是我们发现,倘若是要修改日志的信息,那么就需要在具体方法里面改,这样做很麻烦,而且把原本清爽的方法改的十分混乱,方法应该表现的是核心功能,而不是这些无关紧要的关注点,下面用原生的java的方式实现在执行方法时,动态添加输出日志方法
CalculatorImpl.java
- package cn.limbo.spring.aop.calculator;
- /**
- * Created by Limbo on 16/7/14.
- */
- public class CalculatorImpl implements Calculator {
- @Override
- public int add(int i, int j) {
- return i + j;
- }
- @Override
- public int sub(int i, int j) {
- return i - j;
- }
- @Override
- public int mul(int i, int j) {
- return i * j;
- }
- @Override
- public int div(int i, int j) {
- return i / j;
- }
- }
CalculatorLoggingProxy.java
- package cn.limbo.spring.aop.calculator;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.util.Arrays;
- import java.util.Objects;
- /**
- * Created by Limbo on 16/7/14.
- */
- public class CalculatorLoggingProxy {
- //要代理的对象
- private Calculator target;
- public CalculatorLoggingProxy(Calculator target) {
- this.target = target;
- }
- public Calculator getLoggingProxy(){
- //代理对象由哪一个类加载器负责加载
- ClassLoader loader = target.getClass().getClassLoader();
- //代理对象的类型,即其中有哪些方法
- Class[] interfaces = new Class[]{Calculator.class};
- // 当调用代理对象其中的方法时,执行改代码
- InvocationHandler handler = new InvocationHandler() {
- @Override
- /**
- * proxy:正在返回的那个对象的代理,一般情况下,在invoke方法中不使用该对象
- * method:正在被调用的方法
- * args:调用方法时,传入的参数
- */
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- String methodName = method.getName();
- System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
- //日志
- Object result = method.invoke(target,args);
- System.out.println("The method " + methodName + " ends with " + result);
- return result;
- }
- };
- Calculator proxy = (Calculator) Proxy.newProxyInstance(loader,interfaces,handler);
- return proxy;
- }
- }
Main.java
- package cn.limbo.spring.aop.calculator;
- /**
- * Created by Limbo on 16/7/14.
- */
- public class Main {
- public static void main(String[] args) {
- Calculator calculator = new CalculatorImpl();
- Calculator proxy = new CalculatorLoggingProxy(calculator).getLoggingProxy();
- int result = proxy.add(1,2);
- System.out.println("--->" + result);
- result = proxy.sub(1,2);
- System.out.println("--->" + result);
- result = proxy.mul(3,2);
- System.out.println("--->" + result);
- result = proxy.div(14,2);
- System.out.println("--->" + result);
- }
- }
这样写虽然已经简化了代码,而且可以任意修改日志信息代码,但是写起来还是很麻烦!!!
下面我们使用spring自带的aop包实现但是要加入
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.8.5.RELEASE.jar
这两个额外的包
下面看代码,要点全部卸载代码里面了
Calculator.java
- package cn.limbo.spring.aop.impl;
- /**
- * Created by Limbo on 16/7/14.
- */
- public interface Calculator {
- int add(int i, int j);
- int sub(int i, int j);
- int mul(int i, int j);
- int div(int i, int j);
- }
CalculatorImpl.java
- package cn.limbo.spring.aop.impl;
- import org.springframework.stereotype.Component;
- /**
- * Created by Limbo on 16/7/14.
- */
- @Component("calculatorImpl")
- public class CalculatorImpl implements Calculator {
- @Override
- public int add(int i, int j) {
- return i + j;
- }
- @Override
- public int sub(int i, int j) {
- return i - j;
- }
- @Override
- public int mul(int i, int j) {
- return i * j;
- }
- @Override
- public int div(int i, int j) {
- return i / j;
- }
- }
LoggingAspect.java
- package cn.limbo.spring.aop.impl;
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.core.annotation.Order;
- import org.springframework.stereotype.Component;
- import java.util.Arrays;
- import java.util.List;
- import java.util.Objects;
- /**
- * Created by Limbo on 16/7/14.
- */
- //把这个类声明为切面:需要该类放入IOC容器中,再声明为一个切面
- @Order(0)//指定切面优先级,只越小优先级越高
- @Aspect
- @Component
- public class LoggingAspect {
- /**
- * 定义一个方法,用于声明切入点表达式,一般的,该方法中再不需要添加其他代码
- * 主要是为了重用路径,使用@Pointcut来声明切入点表达式
- * 后面的其他通知直接使用方法名来引用当前的切入点表达式
- */
- @Pointcut("execution(* cn.limbo.spring.aop.impl.Calculator.*(..))")
- public void declareJointPointExpression()
- {
- }
- //声明该方法是一个前置通知:在目标方法之前执行
- @Before("declareJointPointExpression()")
- // @Before("execution(* cn.limbo.spring.aop.impl.*.*(..))") 该包下任意返回值,任意类,任意方法,任意参数类型
- public void beforeMethod(JoinPoint joinPoint)
- {
- String methodName = joinPoint.getSignature().getName();
- List<Object> args = Arrays.asList(joinPoint.getArgs());
- System.out.println("The Method "+ methodName+" Begins With " + args);
- }
- //在目标方法执行之后执行,无论这个方法是否出错
- //在后置通知中还不能访问目标方法的返回值,只能通过返回通知访问
- @After("execution(* cn.limbo.spring.aop.impl.Calculator.*(int,int))")
- public void afterMethod(JoinPoint joinPoint)
- {
- String methodName = joinPoint.getSignature().getName();
- System.out.println("The Method "+ methodName+" Ends ");
- }
- /**
- * 在方法正常结束后执行的代码
- * 返回通知是可以访问到方法的返回值
- */
- @AfterReturning(value = "execution(* cn.limbo.spring.aop.impl.Calculator.*(..))",returning = "result")
- public void afterReturning(JoinPoint joinPoint , Object result)
- {
- String methodName = joinPoint.getSignature().getName();
- System.out.println("The Method " + methodName + " Ends With " + result);
- }
- /**
- *在目标方法出现异常的时候执行代码
- * 可以访问异常对象,且可以指定出现特定异常时再执行通知
- */
- @AfterThrowing(value = "execution(* cn.limbo.spring.aop.impl.Calculator.*(..))",throwing = "ex")
- public void afterThrowing(JoinPoint joinPoint,Exception ex)//Exception 可以改成 NullPointerException等特定异常
- {
- String methodName = joinPoint.getSignature().getName();
- System.out.println("The Method " + methodName + " Occurs With " + ex);
- }
- /**
- * 环绕通知需要ProceedingJoinPoint 类型参数 功能最强大
- * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint 类型的参数可以决定是否执行目标方法
- * 且环绕通知必须有返回值,返回值即为目标方法的返回值
- */
- @Around("execution(* cn.limbo.spring.aop.impl.Calculator.*(..))")
- public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint)
- {
- Object result =null;
- String methodName = proceedingJoinPoint.getSignature().getName();
- try {
- //前置通知
- System.out.println("The Method " + methodName + " Begins With " + Arrays.asList(proceedingJoinPoint.getArgs()));
- result = proceedingJoinPoint.proceed();
- // 返回通知
- System.out.println("The Method " + methodName + " Ends With " + result);
- } catch (Throwable throwable) {
- //异常通知
- throwable.printStackTrace();
- }
- System.out.println("The Method " + methodName + " Ends ");
- return result;
- }
- }
applicationContext.xml
- <?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:context="http://www.springframework.org/schema/context"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
- <!--配置自动扫描的包-->
- <context:component-scan base-package="cn.limbo.spring.aop.impl"></context:component-scan>
- <!--使Aspect注解起作用,自动为匹配的类生成代理对象-->
- <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- </beans>
用xml来配置aop
application-config.xml
- <?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:tx="http://www.springframework.org/schema/tx"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
- <bean id="userManager" class="com.tgb.aop.UserManagerImpl"/>
- <!--<bean id="aspcejHandler" class="com.tgb.aop.AspceJAdvice"/>-->
- <bean id="xmlHandler" class="com.tgb.aop.XMLAdvice" />
- <aop:config>
- <aop:aspect id="aspect" ref="xmlHandler">
- <aop:pointcut id="pointUserMgr" expression="execution(* com.tgb.aop.*.find*(..))"/>
- <aop:before method="doBefore" pointcut-ref="pointUserMgr"/>
- <aop:after method="doAfter" pointcut-ref="pointUserMgr"/>
- <aop:around method="doAround" pointcut-ref="pointUserMgr"/>
- <aop:after-returning method="doReturn" pointcut-ref="pointUserMgr"/>
- <aop:after-throwing method="doThrowing" throwing="ex" pointcut-ref="pointUserMgr"/>
- </aop:aspect>
- </aop:config>
- </beans>
一共有5类的通知,其中around最为强大,但是不一定最常用,aspect的底层实现都是通过代理来实现的,只能说这个轮子造的不错
2016.12.09更新
最近在实际项目中配置aop发现了几个问题,实际的项目配置的的模式是ssh即Spring4+SpringMVC+Hibernate4的模式。
基于注解的方式配置方式有些坑点:
1.发现SpringMVC中aop不起任何作用
经过排查和查找网上的资料,发现问题如下:
Spring MVC启动时的配置文件,包含组件扫描、url映射以及设置freemarker参数,让spring不扫描带有@Service注解的类。为什么要这样设置?因为springmvc.xml与applicationContext.xml不是同时加载,如果不进行这样的设置,那么,spring就会将所有带@Service注解的类都扫描到容器中,等到加载applicationContext.xml的时候,会因为容器已经存在Service类,使得cglib将不对Service进行代理,直接导致的结果就是在applicationContext 中的事务配置不起作用,发生异常时,无法对数据进行回滚。以上就是原因所在。
所以改进后的applicationContext.xml如下(只是更改自动扫描包的那个配置):
- <context:component-scan base-package="cn.limbo">
- <!--不要将Controller扫进来,否则aop无法使用-->
- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
- </context:component-scan>
spring-mvc.xml扫描包配置如下:
- <context:component-scan base-package="cn.limbo">
- <!--不要将Service扫进来,否则aop无法使用-->
- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
- </context:component-scan>
这样就可以成功解决ssh配置下不能使用aop的情况。看来扫描包的时候不能暴力直接全部扫描进来啊,真是成也扫描,败也扫描,手动哭笑不得。
Spring--AOP 例子的更多相关文章
- 一个简单的Spring AOP例子
转载自: http://www.blogjava.net/javadragon/archive/2006/12/03/85115.html 经过这段日子的学习和使用Spring,慢慢地体会到Sprin ...
- spring aop例子
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAATcAAAFWCAIAAACD6E2aAAAgAElEQVR4nO2df1gTV77/55/93z/2ee
- Spring5.0源码学习系列之Spring AOP简述
前言介绍 附录:Spring源码学习专栏 在前面章节的学习中,我们对Spring框架的IOC实现源码有了一定的了解,接着本文继续学习Springframework一个核心的技术点AOP技术. 在学习S ...
- c# spring aop的简单例子
刚刚完成了一个c#的spring aop简单例子,是在mac下用Xamarin Studio开发的.代码如下: 接口 using System; using System.Collections.Ge ...
- 峰Spring4学习(6)spring AOP的应用例子
一.AOP简介: 二.AOP实例: 三.使用的例子 需求:在student添加的前后,打印日志信息: 0)spring AOP需要引用的jar包: 1)StudentService.java接口: p ...
- Spring AOP 学习例子
http://outofmemory.cn/code-snippet/3762/Spring-AOP-learn-example 工作忙,时间紧,不过事情再多,学习是必须的.记得以前的部门老大 ...
- 学习AOP之深入一点Spring Aop
上一篇<学习AOP之认识一下SpringAOP>中大体的了解了代理.动态代理及SpringAop的知识.因为写的篇幅长了点所以还是再写一篇吧.接下来开始深入一点Spring aop的一些实 ...
- 学习AOP之认识一下Spring AOP
心碎之事 要说知道AOP这个词倒是很久很久以前了,但是直到今天我也不敢说非常的理解它,其中的各种概念即抽象又太拗口. 在几次面试中都被问及AOP,但是真的没有答上来,或者都在面上,这给面试官的感觉就是 ...
- spring aop
什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将 ...
- 基于Spring AOP的JDK动态代理和CGLIB代理
一.AOP的概念 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的 ...
随机推荐
- EasyUI 数据网格行过滤
前段时间发现一个GridView很有用的功能,可以筛选数据,实现起来很简单 一.添加一个引用,这个可以自己去网上下载 <script type="text/javascript&quo ...
- python基础——面向过程的编程思想及举例
面向过程的编程思想 1.面向过程的编程思想及举例 写程序时: 要先想功能,分步实现 2. os模块中walk输出目录中文件路径 os.walk() 方法用于通过在目录树中游走输出在目录中的文件名,向上 ...
- python基础—迭代器、生成器
python基础-迭代器.生成器 1 迭代器定义 迭代的意思是重复做一些事很多次,就像在循环中做的那样. 只要该对象可以实现__iter__方法,就可以进行迭代. 迭代对象调用__iter__方法会返 ...
- anguar使用指令写选项卡
今天,我们来学习一下angular中怎么使用指令来实现两个选项卡的问题. 首先,要先引入jQuery文件与angularjs文件. <!DOCTYPE html><html lang ...
- codeforces 798c Mike And Gcd Problem
题意: 给出一个数列,现在有一种操作,可以任何一个a[i],用a[i] – a[i+1]和a[i]+a[i+1]替代a[i]和a[i+1]. 问现在需要最少多少次操作,使得整个数列的gcd大于1. 思 ...
- 2018 php的flush和ob_flush不起作用 整理解决
2018 php的flush和ob_flush不起作用 整理解决成功解决. 要点 : 使用函数 str_repeat2处配置:检查php.ini.Nginx 中有下面两个设置使用方式:echo str ...
- 南京邮电大学java程序设计作业在线编程第四次作业
王利国的的 "Java语言程序设计第4次作业(2018)" 详细 主页 我的作业列表 作业结果详细 总分:100 选择题得分:40 1.下列方法定义中,正确的是() A.doub ...
- 南京邮电大学java程序设计作业在线编程第三次作业
王利国的"Java语言程序设计第3次作业(2018)"详细 作业结果详细 总分:100 选择题得分:60 1. 设有如下定义语句: String s1="My cat& ...
- POJ-1751 Highways---确定部分边的MST
题目链接: https://vjudge.net/problem/POJ-1751 题目大意: 有一个N个城市M条路的无向图,给你N个城市的坐标,然后现在该无向图已经有M条边了,问你还需要添加总长为多 ...
- Selenium_java coding
1)public class HelloWorld { // class 是类的意思 // 类名指的是class后面这个词,这个词是我们起的名 public static void main(Stri ...