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

原文地址:https://www.cnblogs.com/zby9527/p/6946952.html