JavaEE程序设计【六】

6、Spring实现动态代理(Spring-ProxyFactoryBean)

注意:

  • 这里是org.aopalliance.intercept.MethodInterceptor
  • CGLib是net.sf.cglib.proxy.MethodInterceptor
1
2
3
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}

通过Object ret = invocation.proceed();执行被代理的方法

而JDK方式是Object returnValue = method.invoke(target, args);

CGLib是proxy.invokeSuper(obj, args)

使用ProxyFactoryBean进行AOP

第一步:创建一个业务类

1
2
3
4
5
6
7
8
9
10
11
12
package com.yjc.autoproxys;

public class DoSomeServiceImpl {

public void doSome() {
System.out.println("doSome===============================");
}

public void doSome2() {
System.out.println("doSome2==========================================");
}
}

第二步:创建增强类,实现环绕增强要实现的MethodInterceptor 接口,重写invoke

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.yjc.aroundproxy;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.ThrowsAdvice;
public class AroundProxyAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("前置===============");
      //用于启动目标方法执行的
Object proceed = invocation.proceed();
System.out.println("后置===============");
return proceed;
}
}

第三步:在Spring的核心配置文件中实现增强

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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">
   <!--将增强类和目标业务都放入到Spring容器中-->
<bean id="advice" class="com.yjc.aroundproxy.AroundProxyAdvice"></bean>

<bean id="dosome" class="com.yjc.aroundproxy.DoSome"></bean>

<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyTargetClass" value="true"/>
<property name="interceptorNames" value="advice"/> <!--如果用多种增强方式,value的值使用逗号(,)分割-->
<property name="target" ref="dosome"/>
</bean>

</beans>

第四步:测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.yjc.aroundproxy;


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AroundProxyTest {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("com/yjc/aroundproxy/application_aroundproxy.xml");
DoSome proxyFactory = applicationContext.getBean("proxyFactoryBean", DoSome.class);
proxyFactory.doSome();
proxyFactory.doSome2();
}
}

测试成功

  • 1、创建目标类

  • 2、创建切面类

  • 3、创建代理类实现MethodInterceptor接口,在invoke方法内设置增强

    • import java.lang.reflect.Method;
      import org.aopalliance.intercept.MethodInterceptor;
      import org.aopalliance.intercept.MethodInvocation;
  • 3、配置Spring XML文件,设置工厂

  • 配置目标类bean

    • 配置代理类bean
    • 配置工厂org.springframework.aop.framework.ProxyFactoryBean
      • 必须:配置注入制定目标类:target<property name="target" ref="userDao" />
      • 必须:配置目标类实现的接口:proxyInterfaces<property name="proxyInterfaces" value="cn.edu.ch3.jdk.IUserDao" />
      • 必须:配置注入代理实现类:interceptorNames``
      • 配置实现方式proxyTargetClass:true:使用 cglib,false(默认):使用 jdk 动态代理<property name="proxyTargetClass" value="true" />
  • 4、通过getBean获取对象,执行方法

1
2
3
4
5
6
7
8
> 		String xmlPath = "applicationContext.xml";
> ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
> // 从Spring容器获得内容
> IUserDao userDao = (IUserDao) applicationContext.getBean("userDaoProxy");
> // 执行方法
> userDao.addUser();
> userDao.deleteUser();
>

7、AspectJ

Spring AOP,AspectJ, CGLIB 有点晕

浅谈AOP以及AspectJ和Spring AOP

漫谈AOP开发之初探AOP及AspectJ的用法

Spring AOP也是对目标类增强,生成代理类。但是与AspectJ的最大区别在于—Spring AOP的运行时增强,而AspectJ是编译时增强。

曾经以为AspectJ是Spring AOP一部分,是因为Spring AOP使用了AspectJ的Annotation。使用了Aspect来定义切面,使用Pointcut来定义切入点,使用Advice来定义增强处理。虽然使用了Aspect的Annotation,但是并没有使用它的编译器和织入器。其实现原理是JDK 动态代理,在运行时生成代理类。

为了启用 Spring 对 @AspectJ 方面配置的支持,并保证 Spring 容器中的目标 Bean 被一个或多个方面自动增强,必须在 Spring 配置文件中添加如下配置<aop:aspectj-autoproxy/>当启动了 @AspectJ 支持后,在 Spring 容器中配置一个带 @Aspect 注释的 Bean,Spring 将会自动识别该 Bean,并将该 Bean 作为方面 Bean 处理。方面Bean与普通 Bean 没有任何区别,一样使用 <bean…/> 元素进行配置,一样支持使用依赖注入来配置属性值。

Aspect

Aspect被翻译方面或者切面,相当于OOP中的类,就是封装用于横插入系统的功能。例如日志、事务、安全验证等。

JoinPoint

JoinPoint(连接点)是AOP中的一个重要的关键概念。JoinPoint可以看做是程序运行时的一个执行点。打个比方,比如执行System.out.println(“Hello”)这个函数,println()就是一个joinpoint;再如给一个变量赋值也是一个joinpoint;还有最常用的for循环,也是一个joinpoint。

理论上说,一个程序中很多地方都可以被看做是JoinPoint,但是AspectJ中,只有下面所示的几种执行点被认为是JoinPoint:

Pointcuts

怎么从一堆一堆的JPoints中选择自己想要的JPoints呢?恩,这就是Pointcuts的功能。一句话,Pointcuts的目标是提供一种方法使得开发者能够选择自己感兴趣的JoinPoints

advice(处理逻辑)

advice通知或者增强(Advisor)是我们切面功能的实现,它是切点的真正执行的地方。比如像写日志到一个文件中,advice(包括:before、after、around等)在jointpoint处插入代码到应用程序中。我们来看一看原AspectJ程序和反编译过后的程序。看完下面的图我们就大概明白了AspectJ是如何达到监控源程序的信息了。

关键词说明示例
before()before advice表示在JPoint执行之前,需要干的事情
after()after advice表示JPoint自己执行完了后,需要干的事情。
after():returning(返回值类型)after():throwing(异常类型)returning和throwing后面都可以指定具体的类型,如果不指定的话则匹配的时候不限定类型假设JPoint是一个函数调用的话,那么函数调用执行完有两种方式退出,一个是正常的return,另外一个是抛异常。 注意,after()默认包括returning和throwing两种情况
返回值类型 around()before和around是指JPoint执行前或执行后备触发,而around就替代了原JPointaround是替代了原JPoint,如果要执行原JPoint的话,需要调用proceed
Target

Target指的是需要切入的目标类或者目标接口。

Proxy

Proxy是代理,AOP工作时是通过代理对象来访问目标对象。其实AOP的实现是通过动态代理,离不开代理模式,所以必须要有一个代理对象。

Weaving

Weaving即织入,在目标对象中插入切面代码的过程就叫做织入。

(1)Spring AOP与Aspect

当你不用Spring AOP提供的注解时,Spring AOP和AspectJ没半毛钱的关系,前者是JDK动态代理,用到了CGLIB(Code Generation Library),CGLIB是一个代码生成类库,可以在运行时候动态是生成某个类的子类。代理模式为要访问的目标对象提供了一种途径,当访问对象时,它引入了一个间接的层。后者是静态代理,在编译阶段就已经编译到字节码文件中。

当你用到Spring AOP提供的注入@Before、@After等注解时,Spring AOP和AspectJ就有了关系。在开发中引入了org.aspectj:aspectjrt:1.6.11org.aspectj:aspectjweaver:1.6.11两个包,这是因为Spring AOP使用了AspectJ的Annotation,使用了Aspect来定义切面,使用Pointcut来定义切入点,使用Advice来定义增强处理。虽然Spring AOP使用了Aspect的Annotation,但是并没有使用它的编译器和织入器。

Spring AOP其实现原理是JDK动态代理,在运行时生成代理类。为了启用Spring对@AspectJ切面配置的支持,并保证Spring容器中的目标Bean被一个或多个切面自动增强,必须在Spring配置文件中添加如下配置

1
<aop:aspectj-autoproxy/>

当启动了@AspectJ支持后,在Spring容器中配置一个带@Aspect注释的Bean,Spring将会自动识别该 Bean,并将该Bean作为切面Bean处理。切面Bean与普通Bean没有任何区别,一样使用<bean.../>元素进行配置,一样支持使用依赖注入来配置属性值。

AspectJ和Spring AOP都是对目标类增强,生成代理类。

AspectJ是在编译期间将切面代码编译到目标代码的,属于静态代理;Spring AOP是在运行期间通过代理生成目标类,属于动态代理。

AspectJ是静态代理,故而能够切入final修饰的类,abstract修饰的类;Spring AOP是动态代理,其实现原理是通过CGLIB生成一个继承了目标类(委托类)的代理类,因此,final修饰的类不能被代理,同样static和final修饰的方法也不会代理,因为static和final方法是不能被覆盖的。在CGLIB底层,其实是借助了ASM这个非常强大的Java字节码生成框架。关于CGLB和ASM的讨论将会新开一个篇幅探讨。

Spring AOP支持注解,在使用@Aspect注解创建和配置切面时将更加方便。而使用AspectJ,需要通过.aj文件来创建切面,并且需要使用ajc(Aspect编译器)来编译代码。

​ 在Spring 2.0中,Pointcut的定义包括两个部分:Pointcut表示式(expression)和Pointcut签名(signature)

1
2
3
4
5
6
//Pointcut表示式
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
//Point签名
private void log(){}
然后要使用所定义的Pointcut时,可以指定Pointcut签名如下:
@Before("og()")

这种使用方式等同于以下方式,直接定义execution表达式使用

1
@Before("execution(* com.savage.aop.MessageSender.*(..))")

(2)Spring AOP中Aspect的XML方式使用

Spring - AOP基于XML(aop:aspect)

1、定义目标类接口

1
2
3
4
5
6
7
8
9
10
public interface CustomerBo {

void addCustomer();

String addCustomerReturnValue();

void addCustomerThrowException() throws Exception;

void addCustomerAround(String name);
}

2、实现目标类

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
public class CustomerBoImpl implements CustomerBo{

@Override
public void addCustomer() {
System.out.println("addCustomer() is running");
}

@Override
public String addCustomerReturnValue() {
System.out.println("addCustomerReturnValue() is running");
return "abc";
}

@Override
public void addCustomerThrowException() throws Exception {
System.out.println("addCustomerThrowException() is running");
throw new Exception("Error");
}

@Override
public void addCustomerAround(String name) {
System.out.println("addCustomerAround() is running, args: " + name);
}

}

3、定义切面类

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.ray.aspect;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class LoggingAspect {

public void logBefore(JoinPoint joinPoint) {

System.out.println("logBefore() is running!");
System.out.println("getName() : " + joinPoint.getSignature().getName());
System.out.println("******");
}

public void logAfter(JoinPoint joinPoint) {

System.out.println("logAfter() is running!");
System.out.println("getName() : " + joinPoint.getSignature().getName());
System.out.println("******");

}

public void logAfterReturning(JoinPoint joinPoint, Object result) {

System.out.println("logAfterReturning() is running!");
System.out.println("getName() : " + joinPoint.getSignature().getName());
System.out.println("Method returned value is : " + result);
System.out.println("******");

}

public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {

System.out.println("logAfterThrowing() is running!");
System.out.println("getName() : " + joinPoint.getSignature().getName());
System.out.println("Exception : " + error);
System.out.println("******");

}

public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {

System.out.println("logAround() is running!");
System.out.println("getName() : " + joinPoint.getSignature().getName());
System.out.println("getArgs() : " + Arrays.toString(joinPoint.getArgs()));

System.out.println("Around before is running!");
joinPoint.proceed();
System.out.println("Around after is running!");

System.out.println("******");

}

}

4、配置XML

  • (1)开启Aspect注解<aop:aspectj-autoproxy/>(XML方式非必须)
  • (2)引入目标类、切面类的BEAN
  • (3)配置切入
    • <aop:config></aop:config>引入配置区域
    • <aop:aspect id="aspectLoggging" ref="logAspect" > </aop:aspect>引入各个切面的配置(可设置多个)
    • <aop:pointcut id="pointCutBefore" expression="execution(* com.ray.customer.bo.CustomerBo.addCustomer(..))" />引入切点
    • <aop:after method="logAfter" pointcut-ref="pointCutAfter" />设置增强方式和增强方法,注意切点引用
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?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">

<aop:aspectj-autoproxy/>

<bean id="customerBo" class="com.ray.customer.bo.impl.CustomerBoImpl"/>

<!-- Aspect -->
<bean id="logAspect" class="com.ray.aspect.LoggingAspect"/>

<aop:config>
<aop:aspect id="aspectLoggging" ref="logAspect" >

<!-- @Before -->
<aop:pointcut id="pointCutBefore"
expression="execution(* com.ray.customer.bo.CustomerBo.addCustomer(..))" />

<aop:before method="logBefore" pointcut-ref="pointCutBefore" />

<!-- @After -->
<aop:pointcut id="pointCutAfter"
expression="execution(* com.ray.customer.bo.CustomerBo.addCustomer(..))" />

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

<!-- @AfterReturning -->
<aop:pointcut id="pointCutAfterReturning"
expression="execution(* com.ray.customer.bo.CustomerBo.addCustomerReturnValue(..))" />

<aop:after-returning method="logAfterReturning" returning="result"
pointcut-ref="pointCutAfterReturning" />


<!-- @AfterThrowing -->
<aop:pointcut id="pointCutAfterThrowing"
expression="execution(* com.ray.customer.bo.CustomerBo.addCustomerThrowException(..))" />

<aop:after-throwing method="logAfterThrowing" throwing="error"
pointcut-ref="pointCutAfterThrowing" />


<!-- @Around -->
<aop:pointcut id="pointCutAround"
expression="execution(* com.ray.customer.bo.CustomerBo.addCustomerAround(..))" />
<aop:around method="logAround" pointcut-ref="pointCutAround" />
</aop:aspect>

</aop:config>
</beans>

5、获取bean并测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.ray.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.ray.customer.bo.CustomerBo;

public class Test {

public static void main(String[] args) throws Exception {

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
CustomerBo customer = (CustomerBo) context.getBean("customerBo");

// customer.addCustomer();

// customer.addCustomerReturnValue();

// customer.addCustomerThrowException();

customer.addCustomerAround("ray");
}
}

注意:AspectJ方式不要求必须是实现接口的类,实现接口不实现接口都行,我们编程经常面向接口,所以以实现了接口为例

1、定义目标类接口

2、实现目标类

3、定义切面类

4、配置XML

  • (1)开启Aspect注解<aop:aspectj-autoproxy/>
  • (2)引入目标类、切面类的BEAN
  • (3)配置切入
    • <aop:config></aop:config>引入配置区域
    • <aop:aspect id="aspectLoggging" ref="logAspect" > </aop:aspect>引入各个切面的配置(可设置多个)
    • <aop:pointcut id="pointCutBefore" expression="execution(* com.ray.customer.bo.CustomerBo.addCustomer(..))" />引入切点
    • <aop:after method="logAfter" pointcut-ref="pointCutAfter" />设置增强方式和增强方法,注意切点引用

5、获取bean并测试

(3)Spring AOP中Aspect的注解方式使用

注意:AspectJ方式不要求必须是实现接口的类,实现接口不实现接口都行,我们编程经常面向接口,所以以实现了接口为例

1、定义目标类接口

2、实现目标类

3、定义切面类

4、配置注解

  • (1)XML文件中开启Aspect注解<aop:aspectj-autoproxy/>
  • (2)、声明Aspect是一个切面,以及声明为组件使得能够被SpringIOC识别装配
    • @Aspect @Component
  • (3)、使用一个无返回值空方法声明切点
  • (4)、在各个方法前配置增强方式

5、获取bean并测试

接口

1
2
3
4
public interface IUserDao {
public void addUser();
public void deleteUser();
}

目标方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Repository("userDao")
public class UserDaoImpl implements IUserDao{

@Override
public void addUser() {
// TODO Auto-generated method stub
System.out.println("添加用户");
}

@Override
public void deleteUser() {
// TODO Auto-generated method stub
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
* 切面类,在此类中编写通知
*/
@Aspect
@Component
public class MyAspect {
// 定义切入点表达式
@Pointcut("execution(* cn.edu.ccc.ch3.jdk.*.*(..))")
// 使用一个返回值为 void、方法体为空的方法来命名切入点
private void myPointCut() {
}

// 前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知 :模拟执行权限检查...,");
System.out.print("目标类是:" + joinPoint.getTarget());
System.out.println(",被织入增强处理的目标方法为:" + joinPoint.getSignature().getName());
}

// 后置通知
@AfterReturning(value = "myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志...,");
System.out.println("被织入增强处理的目标方法为:" + joinPoint.getSignature().getName());
}

// 环绕通知
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
// 执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
// 结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
return obj;
}

// 异常通知
@AfterThrowing(value = "myPointCut()", throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知:" + "出错了" + e.getMessage());
}

// 最终通知
@After("myPointCut()")
public void myAfter() {
System.out.println("最终通知:模拟方法结束后的释放资源...");
}
}
①关于空方法定义切点

在Spring 2.0中,Pointcut的定义包括两个部分:Pointcut表示式(expression)和Pointcut签名(signature)

1
2
3
4
5
6
//Pointcut表示式
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
//Point签名
private void log(){}
然后要使用所定义的Pointcut时,可以指定Pointcut签名如下:
@Before("og()")

这种使用方式等同于以下方式,直接定义execution表达式使用

1
@Before("execution(* com.savage.aop.MessageSender.*(..))")

Spring AOP只支持以Spring Bean的方法执行组作为连接点,所以可以把切入点看作所有能和切入表达式匹配的Bean方法。切入点定义包含两个部分:

  • 一个切入点表达式:用于指定切入点和哪些方法进行匹配
  • 一个包含名字和任意参数的方法签名:将作为切入点的名称

​ 在@AspectJ风格的AOP中,切入点签名采用一个普通的方法定义(方法体通常为空)来提供(方法名即为切点名),且该方法的返回值必须为void,切入点表达式需使用@Pointcut注解来标注。下面的代码片段定义了一个切入点,这个切入点将匹配任何名为transfer的方法的执行:

1
2
> `//使用@Pointcut注解时指定切入点表达式` `@Pointcut``(``"execution(* transfer(..))"``)` `//使用一个返回值为void,方法体为空的方法来命名切入点,方法名即为切点名` `private` `void` `myPointcut(){}` 
>

​ 切入点表达式,也就是组成@Pointcut注解的值,是规范的AspectJ 5切入点表达式。如果想要了解更多的关于AspectJ切入点语言,请参见AspectJ编程指南。

Spring中的AOP(六)——定义切入点和切入点指示符

②关于proceedingJoinPoint

JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.

常用API

方法名功能
Signature getSignature();获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs();获取传入目标方法的参数对象
Object getTarget();获取被代理的对象
Object getThis();获取代理对象
ProceedingJoinPoint对象

ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中,
添加了以下两个方法。

添加了以下两个方法。

1
2
Object proceed() throws Throwable //执行目标方法 
Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法

Spring AOP中JoinPoint的用法

-----------------------本文结束 感谢阅读-----------------------
坚持原创技术分享,您的支持将鼓励我继续创作!恰饭^.^~