spring aop 详解(一)

一、AOP的核心概念
AOP(Aspect Oriented Programming),是面向切面编程的技术。AOP基于IoC基础,是对OOP的有益补充,流行的AOP框架有Sping AOP、AspectJ

AOP技术它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性

1、切面(aspect)

散落在系统各处的通用的业务逻辑代码,如上图中的日志模块,权限模块,事务模块等,切面用来装载pointcut和advice

2、通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

3、连接点(joinpoint)

被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

4、切入点(pointcut)

拦截的方法,连接点拦截后变成切入点

6、目标对象(Target Object)

代理的目标对象,指要织入的对象模块,如上图的模块一、二、三

7、织入(weave)

通过切入点切入,将切面应用到目标对象并导致代理对象创建的过程

8、AOP代理(AOP Proxy)

AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理

五种类型的通知
Before advice:在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。 
< aop:before>

After advice:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 
< aop:after>

After returnadvice:在某连接点正常完成后执行的通知,不包括抛出异常的情况。 
< aop:after-returning>

Around advice:包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法执行的前后实现逻辑,也可以选择不执行方法 
< aop:around>

Afterthrowing advice:在方法抛出异常退出时执行的通知。 
< aop:after-throwing>

什么时候该用AOP:
假如业务模块一、二、三都需要日志记录,那么如果都在三个模块内写日志逻辑,那么会有两个问题: 
1. 打破模块的封装性 
2. 有很多重复代码 
解决重复代码问题,可以通过封装日志逻辑为一个类,然后在各个模块需要的地方通过该类来试下日志功能,但是还是不能解决影响模块封装性的问题。 
那么AOP就可以解决,它使用切面,动态地织入到各模块中(实际就是使用代理来管理模块对象),这样既解决了重复代码问题又不会影响模块的封装性

二、Spring AOP
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理,要在Spring 中使用AOP,还需要加入这两个jar包 
1、aopalliance.jar 
2、aspectjweaver.jar

Spring中 AOP中的两种代理: 
1. Java动态代理,这样就可以为任何接口实例创建代理了,默认使用 
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

Spring AOP的使用步骤
一、定义具体业务逻辑模块(目标对象) 
二、定义切面(即实现通知逻辑) 
三、实现切面逻辑

三、基于Schema的Spring AOP实例
第一步、定义具体业务逻辑模块(目标对象)
两个业务逻辑模块都是基于接口

TestAOPDaoImpl .java

public class TestAOPDaoImpl implements TestAOPDao{

    @Override
    public void addUser() {
        System.out.println("添加成功");
    }

}
1
2
3
4
5
6
7
8
TestAOPServiceImpl.java

public class TestAOPServiceImpl implements TestAOPService{

    @Autowired
    private TestAOPDao testAOPDao;

    @Override
    public void addUser() {
        testAOPDao.addUser();
    }

}
1
2
3
4
5
6
7
8
9
10
11
第二步和第三步、 定义切面(即实现通知逻辑)
JointPoint是连接点,aop创建代理后会返回一个连接点,然后在通知中可以通过该连接点实现我们的切面逻辑

日志切面

public class LogAdivice{

    public void myBeforeAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——前置通知——" + methodname);
    }

    public void myAfterAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——后置通知——" + methodname);
    }

    /**
     * 环绕通知将决定要不要执行连接点
     * @throws Throwable 
     */
    public void myAroundAdivice(ProceedingJoinPoint point) throws Throwable{
        System.out.println("环绕通知,执行代码前");
        //选择执行
        point.proceed();
        System.out.println("环绕通知,执行代码后");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
时间切面:

public class TimeAdvice {

    public void timeBefore(){
        System.out.println("beforeTime = " + System.currentTimeMillis());
    }

    public void timeAfter(){
        System.out.println("afterTime = " + System.currentTimeMillis());
    }
}
1
2
3
4
5
6
7
8
9
10
在applicationContext中配置切面:

<context:annotation-config/>
    <bean id="testAOPDao" class="com.ssh.dao.impl.TestAOPDaoImpl"/>
    <bean id="testAOPService" class="com.ssh.service.impl.TestAOPServiceImpl"/>
    <bean id="logAdivice" class="com.ssh.adivice.LogAdivice"/>
    <bean id="timeAdvice" class="com.ssh.adivice.TimeAdvice"/>

    <aop:config>
       <!-- 配置一个切面 -->
       <aop:aspect id="logaop" ref="logAdivice" order="2">
           <!-- 定义切入点,表示对service的所有方法都进行拦截 -->
           <aop:pointcut expression="execution(* com.ssh.service.TestAOPService.*(..))" id="testpointcut"/>
           <!-- 定义前置通知 -->
           <aop:before method="myBeforeAdivice" pointcut-ref="testpointcut"/>
           <!-- 定义后置通知 -->
           <aop:after-returning method="myAfterAdivice" pointcut-ref="testpointcut"/>
           <!-- 定义环绕通知 -->
           <aop:around method="myAroundAdivice" pointcut-ref="testpointcut"/>
       </aop:aspect>

       <!-- 定义另一个切面 -->
       <aop:aspect id="timeaop" ref="timeAdvice" order="1">
           <!-- 定义切入点,表示对service的所有方法都进行拦截 -->
           <aop:pointcut expression="execution(* com.ssh.service.TestAOPService.*(..))" id="testpointcut"/>
           <!-- 定义前置通知 -->
           <aop:before method="timeBefore" pointcut-ref="testpointcut"/>
           <!-- 定义后置通知 -->
           <aop:after-returning method="timeAfter" pointcut-ref="testpointcut"/>
       </aop:aspect>
    </aop:config>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
当有多个切面时,Spring默认是按照切面定义的顺序来执行,也可以通过order属性来配置切面的执行属性,order=1 早于 order=2执行

测试结果
public class AOPTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        TestAOPService service = (TestAOPService) context.getBean("testAOPService");
        service.addUser();
    }

}
1
2
3
4
5
6
7
8
9
10


四、基于@AspectJ注解的AOP实现
第一步、定义具体业务逻辑模块(目标对象)
第一步和上面一样

TestAOPDaoImpl .java

public class TestAOPDaoImpl implements TestAOPDao{

    @Override
    public void addUser() {
        System.out.println("添加成功");
    }

}
1
2
3
4
5
6
7
8
TestAOPServiceImpl.java

public class TestAOPServiceImpl implements TestAOPService{

    @Autowired
    private TestAOPDao testAOPDao;

    @Override
    public void addUser() {
        testAOPDao.addUser();
    }

}
1
2
3
4
5
6
7
8
9
10
11
第二步和第三步、 定义切面(即实现通知逻辑)
重点是定义切入点

@Aspect
public class LogAdivice{

    //定义一个方法作为切入点id
    @Pointcut("execution(* com.ssh.service.TestAOPService.*(..))")
    private void allMethod(){}

    @Before("allMethod()")
    public void myBeforeAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——前置通知——" + methodname);
    }

    @AfterReturning("allMethod()")
    public void myAfterAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——后置通知——" + methodname);
    }

    /**
     * 环绕通知将决定要不要执行连接点
     * @throws Throwable 
     */
    @Around("allMethod()")
    public void myAroundAdivice(ProceedingJoinPoint point) throws Throwable{
        System.out.println("环绕通知,执行代码前");
        //执行
        point.proceed();
        System.out.println("环绕通知,执行代码后");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
在applicationContext的配置:

<!-- 打开自动扫描(隐式打开注解管理器) -->
    <!-- <context:component-scan base-package="com.ssh"/> -->
    <context:annotation-config/>
    <bean id="testAOPDao" class="com.ssh.dao.impl.TestAOPDaoImpl"/>
    <bean id="testAOPService" class="com.ssh.service.impl.TestAOPServiceImpl"/>
    <bean id="logAdivice" class="com.ssh.adivice.LogAdivice"/>
    <bean id="timeAdvice" class="com.ssh.adivice.TimeAdvice"/>

    <!-- 打开aop注解管理器 -->
    <aop:aspectj-autoproxy/>
1
2
3
4
5
6
7
8
9
10
五、Java代码使用AOP
public class TestControlFlowPointcut {

    public static void main(String[] args) {
        //只有TargetCaller中的方法才会被拦截
        ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class);
        BeforeAdvice beforeAdvice = new MethodBeforeAdvice() {
            public void before(Method method, Object[] objects, Object o) throws Throwable {
                System.out.println(method.getClass().getSimpleName() + ":" +
                        method.getName() + " - before logic ");
            }
        };

        // Spring 中的 Aspect,装载pointcut和advice
        PointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, beforeAdvice);

        // Spring 基本织入器weaver
        ProxyFactory weaver = new ProxyFactory();
        weaver.setTarget(new TargetObject());   //指定代理目标对象
        weaver.addAdvisor(advisor);  //指定方面

        Object proxy = weaver.getProxy();

        //直接调用Targetobject的方法不会被拦截
        ((TargetObject)proxy).targetMethod();

        //使用ControlFlowPointcut指定的类中的方法才会被拦截
        TargetCaller caller = new TargetCaller();
        caller.setTarget((TargetObject)proxy);
        caller.callMethod();
    }
}
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值