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的方式。