Spring AOP 的两种实现方式分别是注解@(基于AspectJ)和XML配置,虽然方式不同,但万变不离其宗,最终都是运用java反射和动态代理技术(这是JDK方式)或者java反射和CGlib(CGlib方式)。这是Spring内部支持的两种方式。
jdk方式:运用了动态代理,因此必须有接口实现。
CGlib方式:继承类,并不关心接口,因为没有用动态代理嘛。
本文主要针对java反射和动态代理技术(这是JDK方式)讲解注解和XML配置的两种AOP的实现。
Aspect注解方式:
首先创建Maven工程:eclipse 里直接默认创建就可,创建时我的名字命名是:Group id:com.dfy,Artifact id:SpringAOP,那么我的默认包名是:com.dfy.SpringAOP ,ps:使用Maven,当然前提是你需要安装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.dfy</groupId>
<artifactId>SpringAOP</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringAOP</name>
<url>http://maven.apache.org</url>
<!--我一般习惯把我所引用包的版本集中放在这里,这样比较直观-->
<properties>
<spring.version>4.1.3.RELEASE</spring.version>
<aspectj.version>1.6.11</aspectj.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--测试包,自动生成-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--不用说,肯定是spring aop包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring上下文包,在加载spring配置文件时用到-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--使用AspectJ方式注解需要相应的包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<!--使用AspectJ方式注解需要相应的包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</project>
看一下工程结构吧:
首先必须有接口和接口的实现。代码如下:
CustomerManager接口:
package com.dfy.SpringAOP;
public interface CustomerManager {
public void addCustomer(String name,String password);
public void deleteCustomer(String name);
public String getCustomerById(int id);
public void updateCustomer(int id,String name,String password);
}
接口的实现:CustomerManageImpl类
package com.dfy.SpringAOP;
public class CustomerManagerImpl implements CustomerManager {
public void addCustomer(String name, String password) {
System.out.print("加入了客户: "+name+"密码是: "+password);
}
public void deleteCustomer(String name) {
System.out.println("删除了客户: "+name);
}
public String getCustomerById(int id) {
System.out.println("找到了用户");
return "dfy";
}
public void updateCustomer(int id, String name, String password) {
System.out.println("更改了用户基本信息");
}
}
注解的逻辑类:AspectJAdvice
package com.dfy.SpringAOP;
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;
@Aspect
public class AspectJAdvice {
/**
* Pointcut
* 定义Pointcut,Pointcut名称为aspectjMethod,必须无参,无返回值
* 只是一个标识,并不进行调用
*/
@Pointcut("execution(* get*(..))")
private void aspectJMethod(){};
@Before("aspectJMethod()")
public void doBefore(JoinPoint joinPoint){
System.out.println("----dobefore()开始----");
System.out.println("执行业务逻辑前做一些工作");
System.out.println("通过jointPoint获得所需内容");
System.out.println("----dobefore()结束----");
}
@Around("aspectJMethod()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("----doAround()开始----");
System.out.println("此处可做一些类似before的工作");
//核心逻辑
Object retval=pjp.proceed();
System.out.println("此处可做一些类似after的工作");
System.out.println("----doAround()结束----");
return retval;
}
@After(value="aspectJMethod()")
public void doAfter(JoinPoint joinPoint){
System.out.println("----doAfter()开始----");
System.out.println("执行核心逻辑之后,所做工作");
System.out.println("通过jointPoint获得所需内容");
System.out.println("----doAfter()结束----");
}
@AfterReturning(value="aspectJMethod()",returning="retval")
public void doReturn(JoinPoint joinPoint, String retval){
System.out.println("AfterReturning()开始");
System.out.println("Return value= "+retval);
System.out.println("此处可对返回结果做一些处理");
System.out.println("----AfterReturning()结束----");
}
@AfterThrowing(value="aspectJMethod()", throwing="e")
public void doThrowing(JoinPoint joinPoint,Exception e){
System.out.println("-----doThrowing()开始-----");
System.out.println(" 错误信息:"+e.getMessage());
System.out.println(" 此处意在执行核心业务逻辑出错时,捕获异常,并可做一些日志记录操作等等");
System.out.println(" 可通过joinPoint来获取所需要的内容");
System.out.println("-----End of doThrowing()------");
}
}
最后Spring的配置文件(名字随意定,我这里文件名是applicationContext.xml, 顺便插一句废话,struts里面的配置文件必须叫struts.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"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.dfy.SpringAOP"/>
<!-- 启用Spring对基于@AspectJ aspects的配置支持 -->
<!-- 激活自动代理功能 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="customerManager" class="com.dfy.SpringAOP.CustomerManagerImpl"></bean>
<bean id="aspectJAdvice" class="com.dfy.SpringAOP.AspectJAdvice"></bean>
</beans>
测试类APP(我没用test包里面的测试类,不习惯):
package com.dfy.SpringAOP;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello Spring AOP!" );
BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");
CustomerManager customerManager=(CustomerManager) factory.getBean("customerManager");
customerManager.getCustomerById(2015);
}
}
XML配置方式:(需要编写的类相对多些,)
同样再次新建一个Maven工程,使用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>MySpring</groupId>
<artifactId>SpringTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringTest</name>
<url>http://maven.apache.org</url>
<properties>
<commons-lang.version>2.6</commons-lang.version>
<spring.version>4.1.3.RELEASE</spring.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--我这里用了spring-mvc,没用aop,context,咦?奇怪了,呵呵,其实,只引入spring-mvc,maven就会帮我们关联引入aop,context-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons-lang.version}</version>
</dependency>
</dependencies>
</project>
看一下工程结构吧:
具体代码如下:
接口:ServiceBean
package com.SpringTest;
interface ServiceBean {
void addUser(String userName,String password);
void deleteUser(String userName);
boolean findUser(String userName);
String getPassword(String userName);
}
接口实现类:ServiceBeanImpl
package com.SpringTest;
import java.util.HashMap;
import java.util.Map;
public class ServiceBeanImpl implements ServiceBean {
private String dir;
private Map map=new HashMap();
public void addUser(String userName,String password){
if(!map.containsValue(userName))
map.put(userName, password);
else
throw new RuntimeException("user has already exited!");
}
public void deleteUser(String userName){
if(!map.containsKey(userName))
throw new RuntimeException("user isn't exited");
map.remove(userName);
}
public boolean findUser(String userName){
return map.containsKey(userName);
}
public String getPassword(String userName){
return(String) map.get(userName);
}
public void setDir(String dir){
this.dir=dir;
System.out.println("set user to:"+dir);
}
}
打印日志类LogAdvice实现了M ethodBeforeAdvice的before方法,这是每次方法执行前要插入的方法即插入自定义的日志信息,代码如下:
package com.SpringTest;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class LogAdvisor implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("我要打印日志喽! [log] "+target.getClass().getName()+"."+method.getName()+"( )");
}
}
Spring的配置文件beans.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="serviceTarget" class="com.SpringTest.ServiceBeanImpl"/>
<bean id="logAdvisor" class="com.SpringTest.LogAdvisor"/>
<!-- 通过配置bean 实现了动态代理 proxyInterfaces:代理接口 target:实体类 intercepterNames:所动态生成的代理类拦截器-->
<bean id="service" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces"><value>com.SpringTest.ServiceBean</value></property>
<property name="target"><ref local="serviceTarget"/></property>
<property name="interceptorNames">
<list>
<value>logAdvisor</value>
</list>
</property>
</bean>
</beans>
测试类App:
package com.SpringTest;
/**
* 利用Spring aop的组件, 实现简单的打印日志,拦截方法以修改方法,体验spring的轻量级。
*/
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource("beans.xml"));
ServiceBean service = (ServiceBean)factory.getBean("service");
service.addUser("hehe", "111");
service.addUser("haha", "222");
service.findUser("haha");
service.deleteUser("haha");
service.addUser("heihei", "333");
System.out.println("hehe's password is "+service.getPassword("hehe"));
}
}
结果:
在每个方法运行之前,都打印了自定义的日志信息。
前面插入的切片,是针对所有方法的,如何针对特定方法加入切片呢?其实就是拦截特定方法,现在我们添加一个拦截类PasswordAdvice, 继承了MethodInterceptor,拦截getPassword方法以更改密码,并修改为* 。代码如下:
package com.SpringTest;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class PasswordAdvisor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
Object ret=invocation.proceed();
if(ret==null)
return null;
String password=(String)ret;
StringBuffer alter=new StringBuffer(password.length());
for(int i=0;i<password.length();i++){
alter.append("*");
}
return alter.toString();
}
}
修改bean.xml后(红色代码是相对前面添加的内容),如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="serviceTarget" class="com.SpringTest.ServiceBeanImpl"/>
<bean id="logAdvisor" class="com.SpringTest.LogAdvisor"/>
<span style="color:#ff0000;"><bean id="passwordAdvisorTarget" class="com.SpringTest.PasswordAdvisor"/>
</span>
<span style="color:#ff0000;"><!-- advice 指出建议修改方法的类 的实现 pattern 匹配getPassword修改此方法 -->
<bean id="passwordAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="passwordAdvisorTarget"/>
</property>
<property name="patterns">
<list>
<value>.*getPassword</value>
</list>
</property>
</bean>
</span>
<!-- 通过配置bean 实现了动态代理 proxyInterfaces:代理接口 target:实体类 intercepterNames:所动态生成的代理类拦截器-->
<bean id="service" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces"><value>com.SpringTest.ServiceBean</value></property>
<property name="target"><ref local="serviceTarget"/></property>
<property name="interceptorNames">
<list>
<value>logAdvisor</value>
<span style="color:#ff0000;"><value>passwordAdvisor</value></span>
</list>
</property>
</bean>
</beans>
再次运行App类,密码奇迹般的被拦截,显示为***,如图:
只拦截getPassword方法,并更改密码。