Spring-AOP
学而时习之,不亦说乎!
--《论语》
aopalliance(Aspect-Oriented Programming Alliance)为AOP制定了一些规范:

可以看到,全是接口,spring已经将这些接口整合到了aop的包里面,因此,我们在使用的时候不需要引入这个包。

AOP联盟为通知Advice定义了org.aopalliance.aop.Advice,这个接口是AOP最重要的接口之一。
Spring按照通知Advice在目标类方法连接点的位置,可以分为五类:
1)前置通知:org.springframework.aop.MethodBeforeAdvice(在目标方法执行前实施增强)
2)后置通知:org.springframework.aop.AfterReturningAdvice(在目标方法执行后实施增强)
3)环绕通知:org.aopalliance.intercept.MethodInterceptor(在目标方法执行前后实施增强)
4)异常通知:org.springframework.aop.ThrowsAdvice(在方法抛出异常后实施增强)
5)引介通知:org.springframework.aop.IntroductionInterceptor(在目标类中添加一些新的方法和属性)
这里的五个通知类型都实现了Advice接口,最重要的通知:环绕通知。因为环绕通知需要手动执行,所以,可以说是可以代替其他几个通知的。
try {
前置通知
方法执行
后置通知
} catch (Exception e) {
异常通知
}
使用Spring创建单个代理对象:
1)项目整体结构如下:

2)创建maven项目,pom.xml如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zby</groupId> <artifactId>aop</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.8.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.3.8.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.8.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.10</version> </dependency> </dependencies> </project>
其中的aspectjweaver只有在创建多个代理对象才用到。
3)创建UserService接口:
package com.zby.service;
public interface UserService {
void saveUser(String username, String password);
}
4)创建UserService接口实现类UserServiceImpl:
package com.zby.service.impl;
import org.springframework.stereotype.Service;
import com.zby.service.UserService;
@Service("userService")
public class UserServiceImpl implements UserService {
public void saveUser(String username, String password) {
System.out.println("save user[username=" + username + ",password=" + password + "]");
}
}
5)创建实现指定接口MyInterceptor的切面类MethodInterceptor:
package com.zby.interceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
System.out.println("I get datasource here and start transaction");
invocation.proceed();
System.out.println("I get datasource here and end transaction");
} catch (Exception e) {
System.out.println("a exception is catched.");
}
return null;
}
}
6)创建spring的配置文件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" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 目标类 --> <bean id="userService" class="com.zby.service.impl.UserServiceImpl"></bean> <!-- 切面类 --> <bean id="myInterceptor" class="com.zby.interceptor.MyInterceptor"></bean> <!-- 代理类 --> <!-- * 使用工厂bean FactoryBean ,底层调用 getObject() 返回特殊bean * ProxyFactoryBean 用于创建代理工厂bean,生成特殊代理对象 interfaces : 确定接口们 通过<array>可以设置多个值 只有一个值时,value="" target : 确定目标类 interceptorNames : 通知 切面类的名称,类型String[],如果设置一个值 value="" optimize :强制使用cglib <property name="optimize" value="true"></property> 底层机制 如果目标类有接口,采用jdk动态代理 如果没有接口,采用cglib 字节码增强 如果声明 optimize = true ,无论是否有接口,都采用cglib --> <bean id="proxyUserService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="interfaces" value="com.zby.service.UserService "></property> <property name="target" ref="userService"></property> <property name="interceptorNames" value="myInterceptor"></property> <property name="optimize" value="true"></property> </bean> </beans>
7)创建测试类:
package com.zby.test;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.zby.service.UserService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class ProxyTest {
@Resource(name = "userService")
private UserService userService;
@Resource(name = "proxyUserService")
private UserService proxyUserService;
@Test
public void testProxy() {
System.out.println("before Proxy......");
userService.saveUser("zby", "1234567890");
System.out.println("After Proxy......");
proxyUserService.saveUser("zby", "1234567890");
}
}
8)控制台打印结果:
六月 05, 2017 8:42:38 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames 信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] 六月 05, 2017 8:42:38 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners 信息: Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute] 六月 05, 2017 8:42:38 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners 信息: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource] 六月 05, 2017 8:42:38 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners 信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext] 六月 05, 2017 8:42:38 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners 信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@498d1538, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@4d6c3541, org.springframework.test.context.support.DirtiesContextTestExecutionListener@7b1c661c] 六月 05, 2017 8:42:38 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [applicationContext.xml] 六月 05, 2017 8:42:38 下午 org.springframework.context.support.GenericApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.GenericApplicationContext@2f5ca208: startup date [Mon Jun 05 20:42:38 CST 2017]; root of context hierarchy before Proxy...... save user[username=zby,password=1234567890] After Proxy...... I get datasource here and start transaction save user[username=zby,password=1234567890] I get datasource here and end transaction 六月 05, 2017 8:42:39 下午 org.springframework.context.support.GenericApplicationContext doClose 信息: Closing org.springframework.context.support.GenericApplicationContext@2f5ca208: startup date [Mon Jun 05 20:42:38 CST 2017]; root of context hierarchy
使用Spring创建多个代理对象:
1)本例在上面例子中改造,首先修改applicationContext为:
<?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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 目标类 --> <bean id="userService" class="com.zby.service.impl.UserServiceImpl"></bean> <!-- 切面类 --> <bean id="myInterceptor" class="com.zby.interceptor.MyInterceptor"></bean> <!-- 1 .导入aop命名空间 2 .使用 <aop:config>进行配置 proxy-target-class="true" 声明时使用cglib代理 <aop:pointcut> 切入点 ,从目标对象获得具体方法 <aop:advisor> 特殊的切面,只有一个通知 和 一个切入点 advice-ref 通知引用 pointcut-ref 切入点引用 3 .切入点表达式 execution(* com.zby.service.*.*(..)) 选择方法 返回值任意 包 类名任意 方法名任意 参数任意 --> <aop:config proxy-target-class="true"> <aop:pointcut expression="execution(* com.zby.service.*.*(..))" id="myPointCut"/> <aop:advisor advice-ref="myInterceptor" pointcut-ref="myPointCut"/> </aop:config> </beans>
2)编写测试方法:
package com.zby.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.zby.service.UserService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class ProxyTest {
@Autowired
private UserService userService;
@Test
public void testProxy() {
System.out.println("After Proxy......");
userService.saveUser("zby", "1234567890");
}
}
3)控制台打印结果:
六月 05, 2017 8:32:02 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames 信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] 六月 05, 2017 8:32:02 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners 信息: Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute] 六月 05, 2017 8:32:02 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners 信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext] 六月 05, 2017 8:32:02 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners 信息: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource] 六月 05, 2017 8:32:02 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners 信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@43b1510a, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@2e3f8a3e, org.springframework.test.context.support.DirtiesContextTestExecutionListener@4d8d042a] 六月 05, 2017 8:32:03 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [applicationContext.xml] 六月 05, 2017 8:32:03 下午 org.springframework.context.support.GenericApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.GenericApplicationContext@6cecf7fa: startup date [Mon Jun 05 20:32:03 CST 2017]; root of context hierarchy After Proxy...... I get datasource here and start transaction save user[username=zby,password=1234567890] I get datasource here and end transaction
总结:这两种方法看起来一样,但是又有点不一样。前者适合给单个bean代理,后者适合给多个bean代理。后者看起来配置比较灵活,但是切入点表达式需要理解。有一个需要注意的是,当我们的类没有实现接口的时候,会使用CGLIB代理,但是在这儿我们没有引入CGLIB的包!因为spring-core整合了CGLIB的包:

最重要的是,这两种,我基本都只看到别的框架在用,我们自己用,还有更简单的spring整合aspectJ的方式。

浙公网安备 33010602011771号