参考《Spring高级程序设计》
引入是Spring提供的AOP功能的重要组成部分。使用引入可以动态地在现有的对象中添加新的功能。当我们在现有的对象中添加的功能是一个横切关注点而用传统的面向对象方法难以实现时,我们就可以利用引入动态的添加该功能了。
Spring文档中列举了两个典型的引入用法:对象锁定和对象篡改检测。
我们主要对对象篡改检测进行分析:
现在我们构建一个统计信息收集框架。篡改检测的逻辑被封装在CallTracker接口中,他的一个实现连同自动篡改检测的拦截逻辑被一起引入到合适的对象中。
CallTracker接口
package com.spring.ch06.service;
public interface CallTracker {
void markNormal();
void markFailing();
int countNormalCalls();
int countFailingCalls();
String describe();
}
CallTracker接口的一个实现
package com.spring.ch06.service;
public class DefaultCallTracker implements CallTracker {
private int normalCalls;
private int failingCalls;
@Override
public int countFailingCalls() {
return failingCalls;
}
@Override
public int countNormalCalls() {
return normalCalls;
}
@Override
public String describe() {
return toString();
}
@Override
public void markFailing() {
this.failingCalls++;
}
@Override
public void markNormal() {
this.normalCalls++;
}
public String toString(){
final StringBuilder sb=new StringBuilder();
sb.append("DefaultCallTracker");
sb.append("{normalCalls=").append(this.normalCalls);
sb.append(",failingCalls=").append(this.failingCalls);
sb.append("}");
return sb.toString();
}
}
创建一个方面
package com.spring.ch06.service;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class CallTrackerAspect {
@Pointcut("execution(* com.spring.ch06.service.*.*(..))")
private void serviceCall(){}
/*
* 声明我们将使用DefaultCallTracker实现把CallTracker接口引入到com.spring.ch06.service
* 包下的所有的类中
*/
@DeclareParents(
value="com.spring.ch06.service.*",
defaultImpl=DefaultCallTracker.class)
public static CallTracker mixin;
/*
* serviceCall()代表@Pointcut,而this(tracker)将匹配实现了CallTracker接口的对象上所有方法
* 执行。SpringAOP会将追踪器参数绑定到被通知的对象上。
*/
@AfterReturning(
value="serviceCall()&&this(tracker)",
argNames="tracker")
public void normallCall(CallTracker tracker){
tracker.markNormal();
}
@AfterThrowing(
value="serviceCall()&&this(tracker)",
throwing="t",
argNames="tracker,t")
public void failingCall(CallTracker tracker,Throwable t){
tracker.markFailing();
}
}
现有对象:
package com.spring.ch06.service;
import com.spring.ch06.common.User;
public interface UserService {
public void login(String username);
void setAdministratorUsername(String administratorUsername);
User findById(long id);
}
package com.spring.ch06.service;
import com.spring.ch06.common.SecurityContext;
import com.spring.ch06.common.User;
public class DefaultUserService implements UserService{
private String administratorUsername = "janm";
public void login(String username) {
if (this.administratorUsername.equals(username)) {
SecurityContext.setCurrentUser(username);
}
}
public void setAdministratorUsername(String administratorUsername) {
this.administratorUsername = administratorUsername;
}
public User findById(long id) {
User user = new User();
user.setUsername(String.valueOf(id));
user.setPassword("Very secret password");
return user;
}
}
package com.spring.ch06.service;
import java.math.BigDecimal;
import java.util.Date;
public interface StockService {
long getStockLevel(String sku);
long getPredictedStockLevel(String sku);
void applyDiscounts(Date cutoffDate, BigDecimal maximumDiscount);
}
package com.spring.ch06.service;
import java.util.Date;
import java.math.BigDecimal;
/**
* @author janm
*/
public class DefaultStockService implements StockService {
public long getStockLevel(String sku) {
try {
Thread.sleep(2000L);
} catch (InterruptedException ignored) {
}
return getPredictedStockLevel(sku) / 2L;
// return ((StockService)AopContext.currentProxy()).getPredictedStockLevel(sku) / 2L;
}
public long getPredictedStockLevel(String sku) {
return 6L * sku.hashCode();
}
public void applyDiscounts(Date cutoffDate, BigDecimal maximumDiscount) {
// do some work
}
}
测试:
package com.spring.ch06;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.math.BigDecimal;
import java.util.*;
import com.spring.ch06.service.CallTracker;
import com.spring.ch06.service.StockService;
import com.spring.ch06.service.UserService;
public class IntroductionDemo {
public static void main(String[] args) {
//ApplicationContext ac=new ClassPathXmlApplicationContext("/introductiondemo1.xml");
ApplicationContext ac=new ClassPathXmlApplicationContext("/introductiondemo2.xml");
UserService userService=(UserService)ac.getBean("userService");
describeTracker(userService);
userService.login("janm");
userService.setAdministratorUsername("x");
describeTracker(userService);
StockService stockService=(StockService)ac.getBean("stockService");
describeTracker(stockService);
try{
stockService.getStockLevel(null);
}catch(Exception ignored){
}
System.out.println(stockService.getStockLevel("ABC"));
stockService.applyDiscounts(new Date(), new BigDecimal("10.0"));
describeTracker(stockService);
}
public static void describeTracker(Object o){
CallTracker t=(CallTracker)o;
System.out.println(t.describe());
}
}
配置文件:采用两种方式:注解和AOP命名空间配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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="userService" class="com.spring.ch06.service.DefaultUserService"/>
<bean id="stockService" class="com.spring.ch06.service.DefaultStockService"/>
<bean class="com.spring.ch06.service.CallTrackerAspect"/>
<!--
使用CallTrackerAspect的userService bean类型是JdkDynamicAopProxy,它不是一个DefaultUserService
的实例。这个代理双双实现了UserService接口和CallTracker混入体接口。它拦截对所有方法的调用并将UserService
接口上的调用委托给DefaultUserService.这个代理也创建了一个DefaultCallTracker(在@DeclareParents注解中被定义)
的实例并将CallTracker接口上所有的调用委托给DefaultCallTracker实例。
-->
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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
">
<bean id="userService" class="com.spring.ch06.service.DefaultUserService"/>
<bean id="stockService" class="com.spring.ch06.service.DefaultStockService"/>
<bean id="aspectBean" class="com.spring.ch06.service.AspectBean"/>
<!-- AOP命名空间中的引入 (引入通过代理被通知对象,向已有对象中添加额外的方法,
并让代理实现那些接口,它是我们在原有是想上声明的接口)-->
<aop:config>
<aop:aspect id="aroundAspect" ref="aspectBean">
<aop:declare-parents types-matching="com.spring.ch06.service.*"
implement-interface="com.spring.ch06.service.CallTracker"
default-impl="com.spring.ch06.service.DefaultCallTracker"/>
<aop:after-returning method="normalCall"
arg-names="tracker"
pointcut="execution(* com.spring.ch06.service.*.*(..)) and this(tracker)"/>
<aop:after-throwing method="failingCall"
arg-names="tracker"
pointcut="execution(* com.spring.ch06.service.*.*(..)) and this(tracker)"/>
</aop:aspect>
</aop:config>
<!--
使用CallTrackerAspect的userService bean类型是JdkDynamicAopProxy,它不是一个DefaultUserService
的实例。这个代理双双实现了UserService接口和CallTracker混入体接口。它拦截对所有方法的调用并将UserService
接口上的调用委托给DefaultUserService.这个代理也创建了一个DefaultCallTracker(在@DeclareParents注解中被定义)
的实例并将CallTracker接口上所有的调用委托给DefaultCallTracker实例。
-->
</beans>
package com.spring.ch06.service;
import org.aspectj.lang.JoinPoint;
import com.spring.ch06.common.User;
public class AspectBean {
public void normalCall(CallTracker tracker){
tracker.markNormal();
}
public void failingCall(CallTracker tracker){
tracker.markFailing();
}
}