三、Spring之AOP

1. 代理模式

(1) 什么是代理模式?

代理模式是面向对象编程的23种基础设计模式之一。为其他对象(代理对象)提供一种代理以控制对这个对象(源对象)的访问。

就是说,声明一个代理对象去控制对源的对象的访问。代理模式的作用就是提供一个代理对象对源对象的控制访问。

(2) 为什么使用代理?

可以隐藏目标类的具体实现;在不修改目标类代码的情况下能够对其功能进行增强。

(3) 代理分类:代理分为静态代理和动态代理

2. 静态代理

若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下, 静态代理中的代理类和目标类会实现同一接口或是派生自相同的父类。

静态代理的实现(目标类和代理类实现相同接口):

为SomeSerivce接口函数添加一个返回值

//主业务接口

public interface SomeService {

String doSome();

}

修改他的实现类(目标类):

public class SomeServiceImpl implements SomeService {

//...

@Override

public String doSome() {

return "China";

}

}

定义代理类:

//静态代理类,要和目标类实现相同接口

public class ServiceProxy implements SomeService {

SomeService target;

public ServiceProxy() {

super();

}

public ServiceProxy(SomeService target) {

super();

this.target = target;

}

public String doSome(){

return target.doSome().toUpperCase();

}

}

测试类代码:

public static void main(String[] args) {

//定义目标对象

SomeService target = new SomeServiceImpl();

//定义目标对象的代理对象

SomeService proxy = new ServiceProxy(target);

String result = proxy.doSome();

System.out.println(result);

}

3. 动态代理

代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代

码中的“指示”动态生成的。

常用的动态代理有两类:JDK动态代理和CGLIB动态代理

如果目标对象实现了接口,采用JDK的动态代理

如果目标对象没有实现了接口,必须采用CGLIB动态代理

(1) JDK动态代理(JDK 提供的代理实现,不需要额外jar包)

测试类代码:

public class SomeTest {

public static void main(String[] args) {

//定义目标对象,final是为了让内部类调用

final SomeService target = new SomeServiceImpl();

//定义目标对象的代理对象,用Proxy类的静态方法获取

SomeService proxy = (SomeService) Proxy.newProxyInstance(

target.getClass().getClassLoader(),//目标类的类加载器 target.getClass().getInterfaces(),//目标类实现的所有接口

new InvocationHandler() {//调用处理器

//proxy:代理对象

//method:目标方法

//args:目标方法参数

@Override

public Object invoke(Object proxy, Method method, Object[] args)  throws  Throwable {

String result = (String) method.invoke(target, args);

return result.toUpperCase();}

});

String result1 = proxy.doSome();

System.out.println(result1);

}

}

(2) CGLIB动态代理(引入cglib的jar包)

导入cglib-nodep-3.1.jar包,该机制不需要实现任何接口。

目标类修改,去掉接口继承:

public class SomeServiceImpl {

public SomeServiceImpl() {

System.out.println("无参构造器执行!");

}

public String doSome() {

return "China";

}

}

创建一个动态工厂类:

//cglib动态代理工厂

public class CglibProxyFactory implements MethodInterceptor{

private SomeServiceImpl target;

public CglibProxyFactory() {

super();

}

public CglibProxyFactory(SomeServiceImpl target) {

super();

this.target = target;

}

//创建cglib代理对象的方法

public SomeServiceImpl proxyCreator(){

//创建增强器

Enhancer enhancer = new Enhancer();

//指定父类(指定目标类)

enhancer.setSuperclass(SomeServiceImpl.class);

//指定回调接口对象

enhancer.setCallback(this);

//创建cglib代理对象

return (SomeServiceImpl) enhancer.create();

}

@Override

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

String result = (String) method.invoke(target, args);

return result.toUpperCase();

}

}

测试类代码:

public class SomeTest {

public static void main(String[] args) {

//定义目标对象

SomeServiceImpl target = new SomeServiceImpl();

//定义目标对象的代理对象

SomeServiceImpl proxy = new CglibProxyFactory(target).proxyCreator();

String result1 = proxy.doSome();

System.out.println(result1);

}

}

4. Aop介绍

面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP的功能将切面织入到主

业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全

检查、事务、日志等。

若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这

样,会使主业务逻辑变的混杂不清。

Aop基本术语介绍

切面:切面泛指交叉业务逻辑。比如事务处理、日志处理就可以理解为切面。常用的切面

有通知与顾问。实际就是对主业务逻辑的一种增强。

织入:织入是指将切面代码插入到目标对象的过程。

连接点:连接点指切面可以织入的位置。

切入点:切入点指切面具体织入的位置。

通知(Advice):通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之

后执行等。通知类型不同,切入时间不同。

顾问(Advisor):顾问是切面的另一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。 不仅指定了切入时间点,还可以指定具体的切入点。

5. Spring对AOP的实现(基于Schema-based方式)

编程环境搭建:导入两个jar包(aop/aopalliance)

spring-aop-4.3.16.RELEASE和com.springsource.org.aopalliance-1.0.0.jar(AOP依赖包)

常用通知分类:

前置通知(MethodBeforeAdvice):目标方法执行前执行

后置通知(AfterReturningAdvice):目标方法执行后执行

环绕通知(MethodInterceptor:目标方法执行之前和之后都执行

异常处理通知(ThrowsAdvice):当目标方法有异常的时候执行

通知的用法步骤:

定义目标类

定义通知类

注册目标类

注册通知切面

注册代理工厂Bean类对象ProxyFactoryBean

客户端访问动态代理对象

(1) 前置通知

接口类添加一个方法doOther:

public interface SomeService {

void doSome();

String doOther();

}

 

实现类方法:

public String doOther() {

System.out.println("doOther()方法执行!");

return "love";

}

 

定义一个切面类:

//切面:前置通知

public class MyMethodBeforeAdvice implements MethodBeforeAdvice {

/**

* method:目标方法

* args:目标方法参数列表

* target:目标对象

*/

@Override

public void before(Method method, Object[] args, Object target) throws Throwable {

System.out.println("前置通知的before()方法执行!");

}

}

连接源码:

按着ctrl点击MethodBeforeAdvic,点击attach Source...,选External  loctaion,再点Extenrnal File...,找到spring-framework-4.1.6.RELEASE源码,打开。

applicationContext.xml中注册目标类

<bean id="someServiceImpl" class="com.mypack.service.impl.SomeServiceImpl"></bean>

<!-- 注册切面:前置通知 -->

<bean id="myMethodBeforeAdvice" class="com.mypack.aspects.MyMethodBeforeAdvice"></bean>

<!-- 注册代理 -->

<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">

<!-- 指定目标对象 -->

<property name="target" ref="someServiceImpl"></property>

<!-- 指定目标类实现的所有接口 -->

<property name="interfaces" value="com.mypack.service.SomeService"></property>

测试类代码:

public void someTest01(){

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

SomeService service = ac.getBean("proxyFactoryBean", SomeService.class);

service.doSome();

String result = service.doOther();

System.out.println(result);

}

结果证明是在方法执行之前执行前置通知

(2) 后置通知

定义一个切面类:

public class MyAfterReturningAdvice implements AfterReturningAdvice {

/**

* returnValue:目标方法的返回值

*/

@Override

public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {

System.out.println("后置通知的afterReturning()方法执行!returnValue:"+ returnValue);

if(returnValue!=null){

System.out.println("后置通知的afterReturning()方法执行!returnValue:"+((String)returnValue).toUpperCase());

}

}

}

 

xml中注册:

<!-- 注册切面:后置通知 -->

<bean id="myAfterReturningAdvice" class="com.mypack.aspects.MyAfterReturningAdvice"></bean>

 

 

(3) 环绕通知

切面类实现MethodInterceptor接口

//切面:环绕通知

public class MyMethodInterceptor implements MethodInterceptor {

/**

* invocation:方法调用器

*/

@Override

public Object invoke(MethodInvocation invocation) throws Throwable {

System.out.println("环绕通知:目标方法执行之前!");

//调用执行目标方法

Object result = invocation.proceed();

if(result!=null){

result = ((String)result).toUpperCase();

}

System.out.println("环绕通知:目标方法执行之后!");

return result;

}

}

 

xml中注册:

<!-- 注册切面:环绕通知 -->

<bean id="myMethodInterceptor" class="com.mypack.aspects.MyMethodInterceptor"></bean>

 

<!-- 指定切面 -->

<property name="interceptorNames" value="myThrowsAdvice"></property>

</bean>

 

 

(4) 异常处理通知

切面类实现ThrowsAdvice接口,这个接口并没有方法必须实现,为标识接口

//切面:异常通知

public class MyThrowsAdvice implements ThrowsAdvice {

public void afterThrowing(Exception ex){ //从接口中获得一个没声明的方法

System.out.println("异常通知执行!");

}

}

 

xml中注册:

<!-- 注册切面:异常通知 -->

<bean id="myThrowsAdvice" class="com.mypack.aspects.MyThrowsAdvice"></bean>

8. AspectJ对AOP的实现

对于AOP这种编程思想, 很多框架都进行了 实现。 Spring就是其中之一, 可
以完成面向切面编程。 然而, AspectJ也实现了 AOP的功能, 且其实现方式更为简捷,
使用更为方便, 而且还支持注解式开发。 所以, Spring又将AspectJ的对于AOP的实
现也引 入到了 自 己的框架中。
在Spring中使用AOP开发时, 一般使用AspectJ的实现方式。

9. AspectJ的通知类型

AspectJ中常用的通知有五种类型:

前置通知,后置通知,环绕通知,异常通知,最终通知。

最终通知无论程序执行是否正常,该通知都会执行。类似于try..catch中finally代码块。

10. AspectJ的切入点表达式

execution([modifier-pattern]) 访问权限类型

ret-type-pattern  返回值类型

[declaring-type- pattern]  全限定性类型

Name-pattern(param-pattern)  方法名(参数名)

[throws-pattern]  抛出异常类型

)

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中明显就是方法

的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中可以使

用以下符号:

符号

意义

*

0至多个任意字符

..

用在方法参数中,表示任意多个参数。

用在包名后,表示单签包及其子包路径

+

用在类名后,表示取当前类及其子类

用在接口后,表示当前接口及其实现类

11. AspectJ的切入点表达式

举例:

execution(public * *(..))

指定切入点为:任意公共方法。

execution(* set *(..))

指定切入点为:任何一个以“set”开始的方法。

execution(* com.xyz.service.*.*(..))

指定切入点为:定义在service包里的任意类的任意方法。

execution(* com.xyz.service..*.*(..))

指定切入点为:定义在service包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。

execution(* *.service.*.*(..))

指定只有一级包下的serivce子包下所有类(接口)中的所有方法为切入点

execution(* *..service.*.*(..))

指定所有包下的serivce子包下所有类(接口)中的所有方法为切入点

12. 搭建AspectJ的开发环境

(1) 导入两个Jar包

spring-aspects-4.3.16.RELEASE

com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

(2) 引入AOP约束

http://www.springframework.org/schema/aop //AOP约束

http://www.springframework.org/schema/aop/spring-aop.xsd"  //AOP注解约束

13. AspectJ对于AOP的实现有两种方式

(1) 注解方式

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"

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.xsd">

 

    <!-- 注册目标类 -->

<bean id="someServiceImpl" class="com.mypack.service.impl.SomeServiceImpl"></bean>

    <!-- 注册切面 -->

    <bean id="myAspect" class="com.mypack.aspects.MyAspect"></bean>

    <!-- 注册自动代理 -->

    <aop:aspectj-autoproxy/>    

</beans>

切面类:

@Aspect  //表明当前类是一个切面

public class MyAspect {

//该注解表明当前方法是前置通知方法

@Before("execution(* *..service.*.doSome(..))")

public void before(){

System.out.println("前置通知方法执行!");

}

//该注解表明当前方法是后置通知方法

@AfterReturning(value="execution(* *..service.*.doOther(..))",returning="result")  //设一个返回值

public void afterReturning(Object result){

System.out.println("后置通知方法执行! 目标方法的返回值是"+result);

}

//该注解表明当前方法是环绕通知方法

@Around("execution(* *..service.*.doOther(..))")

public Object around(ProceedingJoinPoint pjp) throws Throwable{

System.out.println("环绕通知:目标方法执行之前!");

String result = (String) pjp.proceed();

if(result!=null){

result = result.toUpperCase();

}

System.out.println("环绕通知:目标方法执行之后!");

return result;

}

//该注解表明当前方法是异常通知方法

@AfterThrowing(value="execution(* *..service.*.doSome(..))",throwing="ex")

public void throwing(Exception ex){

System.out.println("异常通知方法执行! 异常信息为:"+ex);

}

//该注解表明当前方法是最终通知方法

@After("execution(* *..service.*.doSome(..))")

public void after(){

System.out.println("最终通知方法执行!");

}

}

(2) XML方式

xml文件代码:

<!-- 注册目标类 -->

<bean id="someServiceImpl" class="com.mypack.service.impl.SomeServiceImpl"></bean>

<!-- 注册切面 -->

<bean id="myAspect" class="com.mypack.aspects.MyAspect"></bean>

<!-- AOP配置 -->

<aop:config>

<!-- 定义切入点 -->

<aop:pointcut expression="execution(* *..service.*.doSome(..))" id="doSomePC"/>

<aop:pointcut expression="execution(* *..service.*.doOther(..))" id="doOtherPC"/>

<aop:aspect ref="myAspect">

<!-- <aop:before method="before" pointcut-ref="doSomePC"/> -->

<!-- <aop:before method="before(org.aspectj.lang.JoinPoint)" pointcut-ref="doSomePC"/> -->

<!-- <aop:after-returning method="afterReturning(java.lang.Object)" pointcut-ref="doOtherPC" returning="result"/> -->

<!-- <aop:around method="around" pointcut-ref="doOtherPC"/> -->

<!-- <aop:after-throwing method="throwing(java.lang.Exception)" pointcut-ref="doSomePC" throwing="ex"/> -->

<aop:after method="after" pointcut-ref="doSomePC"/>

</aop:aspect>

</aop:config>

切面类代码:

public class MyAspect {

//该注解表明当前方法是前置通知方法

public void before(){

System.out.println("前置通知方法执行!");

}

public void before(JoinPoint jp){

System.out.println("前置通知方法执行!jp="+jp);

}

//该注解表明当前方法是后置通知方法

public void afterReturning(Object result){

System.out.println("后置通知方法执行! 目标方法的返回值是"+result);

}

//该注解表明当前方法是环绕通知方法

public Object around(ProceedingJoinPoint pjp) throws Throwable{

System.out.println("环绕通知:目标方法执行之前!");

String result = (String) pjp.proceed();

if(result!=null){

result = result.toUpperCase();

}

System.out.println("环绕通知:目标方法执行之后!");

return result;

}

//该注解表明当前方法是异常通知方法

public void throwing(Exception ex){

System.out.println("异常通知方法执行! 异常信息为:"+ex);

}

//该注解表明当前方法是最终通知方法

public void after(){

System.out.println("最终通知方法执行!");

}

}

3.1.3 Spring之AOP的更多相关文章

  1. Spring基于AOP的事务管理

                                  Spring基于AOP的事务管理 事务 事务是一系列动作,这一系列动作综合在一起组成一个完整的工作单元,如果有任何一个动作执行失败,那么事务 ...

  2. Spring实现AOP的4种方式

    了解AOP的相关术语:1.通知(Advice):通知定义了切面是什么以及何时使用.描述了切面要完成的工作和何时需要执行这个工作.2.连接点(Joinpoint):程序能够应用通知的一个“时机”,这些“ ...

  3. spring的AOP

    最近公司项目中需要添加一个日志记录功能,就是可以清楚的看到谁在什么时间做了什么事情,因为项目已经运行很长时间,这个最初没有开来进来,所以就用spring的面向切面编程来实现这个功能.在做的时候对spr ...

  4. Spring(五)AOP简述

    一.AOP简述 AOP全称是:aspect-oriented programming,它是面向切面编号的思想核心, AOP和OOP既面向对象的编程语言,不相冲突,它们是两个相辅相成的设计模式型 AOP ...

  5. Spring中AOP原理,源码学习笔记

    一.AOP(面向切面编程):通过预编译和运行期动态代理的方式在不改变代码的情况下给程序动态的添加一些功能.利用AOP可以对应用程序的各个部分进行隔离,在Spring中AOP主要用来分离业务逻辑和系统级 ...

  6. Spring之AOP面向切片

       一.理论基础: AOP(Aspectoriented programming)面向切片/服务的编程,在Spring中使用最多的是对事物的处理.而AOP这种思想在程序中很多地方可以使用的,比如说, ...

  7. 利用CGLib实现动态代理实现Spring的AOP

    当我们用Proxy 实现Spring的AOP的时候, 我们的代理类必须实现了委托类的接口才能实现. 而如果代理类没有实现委托类的接口怎么办? 那么我们就可以通过CGLib来实现 package cn. ...

  8. spring之aop概念和配置

    面向切面的一些概念: 简单说: 连接点就一些方法,在这些方法基础上需要额外的一些业务需求处理. 切入点就是方法所代表的功能点组合起来的功能需求. 通知就是那些额外的操作. 织入就是使用代理实现整个切入 ...

  9. Spring的AOP与代理

    spring 支持两种注入方式: setter/constructor 支持多种配置方式: xml/java5注解/java类配置 支持两种事务管理: 声明性/编程性 实际上上述方式只有一个就能保证系 ...

  10. Spring 实践 -AOP

    Spring 实践 标签: Java与设计模式 AOP引介 AOP(Aspect Oriented Programing)面向切面编程采用横向抽取机制,以取代传统的纵向继承体系的重复性代码(如性能监控 ...

随机推荐

  1. Vue中的template标签的使用和在template标签上使用v-for

    我们知道  .vue 文件的基本结构是: <template> ........ </template> <script> export default { nam ...

  2. module.exports与exports

    API文档是枯燥的,下面本人收集了一些论坛经常有人疑问和开源代码中经常遇到的案例供大家研究一下. module.exports与exports的区别 每一个node.js执行文件,都自动创建一个mod ...

  3. Python学习(四十一)—— Djago进阶

    一.分页 Django的分页器(paginator) view from django.shortcuts import render,HttpResponse # Create your views ...

  4. Python中os与sys两模块的区别

    <os和sys的官方解释> ➤os os: This module provides a portable way of using operating system dependent ...

  5. 字符串函数之Strtok()函数

    Strtok()函数详解:   该函数包含在"string.h"头文件中 函数原型: char* strtok (char* str,constchar* delimiters ) ...

  6. PAT Basic 1032

    1032 挖掘机技术哪家强 (20 分) 为了用事实说明挖掘机技术到底哪家强,PAT 组织了一场挖掘机技能大赛.现请你根据比赛结果统计出技术最强的那个学校. 输入格式: 输入在第 1 行给出不超过 1 ...

  7. H5页面测试实战总结

    如何判断是否是H5页面: 基本上只要对那个view长按,然后看是不是有反应,比如手机震动(Android).或者出现文字选择粘贴(Android/iOS),那么就是WebView! 横屏竖屏相互切换, ...

  8. shell grep

    grep "str" file > /dev/null if [ $? -eq 1]; then echo "no str" else echo &quo ...

  9. php 识别二维码(转载)

    近段需要写一个通过PHP来识别二维码的功能,在网上查了很久才解决问题.以此来记录下来解决问题的方法. 最开始找的方法是一个叫 php-zbarcode 的扩展,自己照着网上的安装步骤安装了 Image ...

  10. Linux学习笔记(三)

    昨天将计算机概论部分算是看完了,然后我又将后面的内容过了一遍,因为我本身对Linux有一些了解,所以就跳到这文件权限与目录管理配置这章了,话不多说,开始做笔记. Linux文件权限与目录配置 群组 所 ...