Spring学习笔记----AOP编程

来源:互联网 发布:优思得云计算科技 编辑:程序博客网 时间:2024/06/10 20:51

先用代码讲一下什么是传统的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@Componentpublic 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的情况。看来扫描包的时候不能暴力直接全部扫描进来啊,真是成也扫描,败也扫描,手动哭笑不得。





0 0