3.1.3 Spring之AOP

三、SpringAOP

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.RELEASEcom.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("最终通知方法执行!");

}

}

猜你喜欢

转载自www.cnblogs.com/kendyho/p/10748321.html