您的位置:首页 > 编程语言 > Java开发

一点一滴学习Spring(四)之AOP

2016-12-14 21:25 405 查看

一、AOP术语

描述切面的常用术语有通知(advice)、切点(pointcut)和连接点(joinpoint)通知(Advice):

Spring切面可以应用5种类型的通知:

1、前置通知(Before):在目标方法被调用之前调用通知功能;

2、后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;

3、返回通知(Afert-returning):在目标方法成功执行之后调用通知;

4、异常通知(After-throwing):在目标方法抛出异常后调用通知;

5、环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为;

连接点(joinpoint):

连接点是在应用在执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为。

切点(pointcut):我们通常使用明确的类名和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定切点。

二、Spring对AOP的支持

Spring提供了4种类型的AOP支持

1、基于代理的经典SpringAOP;

2、纯pojo切面

3、@AspectJ注解驱动的切面

4、注入式AspectJ切面

三、通过切点来选择连接点

切点用于准确定位应该在什么地方应用切面的通知。通知和切点是切面的最基本元素。

Spring借助AspectJ的切点表达式语言来定义Spring切面



四、编写切点

1、execution

execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?) 除了返回类型模式、方法名模式和参数模式外,其它项都是可选的;

2、args()和@args()

args()函数的入参是类名,@args()函数的入参必须是注解类的类名。虽然args()允许在类名后使用+通配符后缀,但该通配符在此处没有意义:添加和不添加效果都一样;

3、@annotation、@within()和@target()

@annotation、@within()和@target()函数的入参必须都是注解类的类名。@annotation

是应用于java类中的某方法上的注解而@within()和@target()应用于java类上的注解;

为了阐述Spring中的切面,我们需要有个主题来定义切面的切点。为此我们定义一个IPersonBeanService接口

public interface IPersonBeanService {

void save();

void testDependency();
}


假设我们想编写IPersonBeanService的save()方法触发通知

execution1(*2 com.cn.service.IPersonBeanService3. save4.(..)5)

1-execution:定义方法切点的指示器

2-*:任意返回值

3-com.cn.service.IPersonBeanService:方法所在类

4-save:方法名称

5-(..):使用任意参数

我们可以使用“&&”操作符把多个指示器连接在一起形成and关系(切点必须匹配所有的指示器),使用“||”操作符把指示器连接在一起形成or关系,而是用“!”操作符来辨识非操作

在xml中则使用and代替“&&”,or代替“||”,not代替“!”。

在切点中选择bean

除了上述表中所有的指示器外,Spring还引入了一个新的bean()指示器,他允许我们在切点表达式中使用bean的ID来标识bean。Bean()使用bean ID或bean名称作为参数来限定切点只能匹配特定的bean。

Execution定义详解:

execution(public * *(..))

匹配所有目标类的public方法,第一个代表返回类型,第二个代表方法名,而..代表任意入参的方法;

execution(* *To(..))l

匹配目标类所有以To为后缀的方法。第一个*代表返回类型,而*To代表任意以To为后缀的方法;

execution(* com.cn.Waiter.*(..))l

匹配Waiter接口的所有方法,第一个代表返回任意类型,com.cn.Waiter.代表Waiter接口中的所有方法;

execution(* com.cn.Waiter+.*(..))

匹配Waiter接口及其所有实现类的方法

在类名模式串中,“.”表示包下的所有类,而“..”表示包、子孙包下的所有类。execution(* com.cn.*(..))匹配com.cn包下所有类的所有方法;

execution(* com.cn..*(..))

匹配com.cn包、子孙包下所有类的所有方法,如com.cn.dao,com.cn.servier以及 com.cn.dao.user包下的所有类的所有方法都匹配。“..”出现在类名中时,后面必须跟“*”,表示包、子孙包下的所有类;

execution(* com...*Dao.find(..))

匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名必须以find为前缀。如com.cn.UserDao#findByUserId()、com.cn.dao.ForumDao#findById()的方法都匹配切点。

通过方法入参定义切点

切点表达式中方法入参部分比较复杂,可以使用“”和“..”通配符,其中“”表示任意类型的参数,而“..”表示任意类型参数且参数个数不限。

execution(* joke(String,int)))

匹配joke(String,int)方法,且joke()方法的第一个入参是String,第二个入参是int。它匹配 NaughtyWaiter#joke(String,int)方法。如果方法中的入参类型是java.lang包下的类,可以直接使用类名,否则必须使用全限定类名,如joke(java.util.List,int);

execution(* joke(String,*)))l

匹配目标类中的joke()方法,该方法第一个入参为String,第二个入参可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都匹配,但joke(String s1,double d2,String s3)则不匹配;

execution(* joke(String,..)))

匹配目标类中的joke()方法,该方法第 一个入参为String,后面可以有任意个入参且入参类型不限,如joke(String s1)、joke(String s1,String s2)和joke(String s1,double d2,String s3)都匹配。

execution(* joke(Object+)))

匹配目标类中的joke()方法,方法拥有一个入参,且入参是Object类型或该类的子类。它匹配joke(String s1)和joke(Client c)。如果我们定义的切点是execution(* joke(Object)),则只匹配joke(Object object)而不匹配joke(String cc)或joke(Client c)。

五、使用注解定义切面

1、Spring使用AspecJ注解来声明通知方法

@After:通知方法会在目标方法返回或抛出异常后调用

@AfterReturning:通知方法会在目标方法成功返回后调用

@AfterThrowing:通知方法会在目标方法抛出异常后调用

@Around:通知方法会将目标方法封锁起来

@Before:通知方法会在目标方法调用之前调用

@Pointcut:定义切点。

用法示例:

在AspectLog中,excudeService()方法使用了@Pointcut注解。为@Pointcut注解设置的值是一个切点表达式。

excudeService()方法的内容并不重要,在这里他实际上应该是空的。其实该方法本身只是一个标识,供@Pointcut注解依附。

简单的小示例:

@Aspect
@Configuration
public class AspectLog {

@Pointcut("execution(* service.IPerformanceService.*(..))")
public void excudeService(){

}

@Before("excudeService()")
public void doBeforeInServiceLayer(){
System.out.println("before.....");
}

@Around("excudeService()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("before proceed");
Object obj = pjp.proceed();
System.out.println("after proceed");
return obj;
}

@After("excudeService()")
public void doAfterInServiceLayer(JoinPoint joinPoint) {
System.out.println("after.....");
}

}


public interface IPerformanceService {

public String testPerformence();
}


@Service("performenceService")
public class PerformenceServiceImpl implements IPerformanceService{

@cold
@Override
public String testPerformence(){
return "performence";
}
}


@Configuration
@ComponentScan(basePackageClasses=IPerformanceService.class)
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AppConfig {

}


@EnableAspectJAutoProxy(proxyTargetClass=true):启用AspectJ自动代理

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={AppConfig.class,AspectLog.class})
public class TestSpringBean_Aspect {

@Autowired
private IPerformanceService performanceService;

@Test
public void test() {
System.out.println(performanceService.testPerformence()+"~~~");
}

}


执行打印结果

before proceed

before…..

after proceed

after…..

performence~~~

由打印结果可得出执行顺序:Around方法的pjp.proceed()执行之前 —> before方法 —> Around方法的pjp.proceed()执行之后 —> after方法

在XML中声明切面

1、在xml头部声明AOP的命名空间

<?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"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

</beans>


AOP配置元素:用途

<aop:advisor>:
定义AOP通知器

<aop:after>:
定义AOP后置通知(不管被通知的方法是否执行成功)

<aop:after-returning>:
定义AOP返回通知

<aop:after-throwing>:
定义AOP异常通知

<aop:around>:
定义AOP环绕通知

<aop:aspect>:
定义一个切面

<aop:aspectj-autoproxy>:
启用@AspectJ注解驱动的切面

<aop:before>:
定义一个AOP前置通知

<aop:config>:
顶层的AOP配置元素。大多数的
<aop:*>
元素必须包含在
<aop:config>
元素内

<aop:declare-parents>:
以透明方式为被通知的对象引入额外的接口

<aop:pointcut>:
定义一个切点

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"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<bean id="performanceService" class="service.PerformenceServiceImpl"></bean>
<bean id="aspectLogBean" class="Config.AspectLogBean"></bean>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<aop:config>
<aop:aspect ref="aspectLogBean">
<aop:pointcut expression="execution(* service.IPerformanceService.*(..))" id="excudeService"/>
<aop:before method="doBeforeInServiceLayer" pointcut-ref="excudeService"/>
<aop:after method="doAfterInServiceLayer" pointcut-ref="excudeService"/>
<aop:around method="doAround" pointcut-ref="excudeService"/>
</aop:aspect>
</aop:config>
</beans>


关于SpringAOP配置元素,第一个需要注意的事项是大多数的AOP配置元素必须在
<aop:config>
元素的上下文内使用。这条规则有几种意外场景,但是把bean声明为一个切面时,

我们总是从
<aop:config>
元素开始配置的。

<aop:config>
元素内,我们可以声明一个或多个通知器、切面或者切点。

上述,我们使用
<aop:aspect>
元素生命了一个简单的切面,ref元素应用了一个POJO Bean,该bean实现了切面的功能。

<aop:pointcut>
定义了一个切点,它被多个通知通过pointcut-ref引用

jar包:

aopalliance.jar 、 aspectjweaver-1.8.9.jar、spring-aop-4.1.6.RELEASE.jar、spring-aspects-4.1.6.RELEASE.jar

项目中实际应用示例:

一、定义切面:用于做页面商品访问量统计

@Aspect
@Component
public class StatisticsAspect {

@Autowired
private IISpringRedis springRedisService;

private Logger logger = LoggerFactory.getLogger(StatisticsAspect.class);
private Object[] paramsArray = null;

@Around("@annotation(com.isgo.gallerydao.core.support.annotation.Statistics)")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{
paramsArray = joinPoint.getArgs();
Object result = joinPoint.proceed();
return result;
}

/**
* @Title: doAfterInServiceLayer
* @Description: 将点击量存于缓存
* @param statistics    参数
* @return void    返回类型
* @throws
*/
@After(value = "@annotation(statistics)", argNames = "statistics")
public void doAfterInServiceLayer(Statistics statistics) {
try {
if(null!=paramsArray && paramsArray.length>=statistics.paramterPlace()){
String foreignKey = paramsArray[statistics.paramterPlace()-1].toString();
String key = new StringBuffer(RedisKeyPrefixEnum.ART_EXT.getValue()).append(foreignKey).append("_")
.append(statistics.type().getValue()).toString();
ArtExt artExt = springRedisService.get(key, ArtExt.class);
if(null==artExt){
artExt = new ArtExt();
}
Integer count = artExt.getExt01()==null?0:artExt.getExt01();
artExt.setForeignKey(foreignKey);
artExt.setType(statistics.type().getValue());
artExt.setExt01(count+1);
springRedisService.saveOrUpdate(key, artExt);
ArtExt artExt_new = springRedisService.get(key, ArtExt.class);
logger.info("foreignKey:{},type:{},hits:{}",artExt_new.getForeignKey(),
artExt_new.getType(),artExt_new.getExt01());
}
} catch (Exception e) {
StackTraceElement[] trace = new StackTraceElement[1];
trace[0] = e.getStackTrace()[0];
e.setStackTrace(trace);
StringBuffer sb = new StringBuffer("exception---");
if(null!=trace[0]){
sb.append("className:{").append(trace[0].getClassName()).append("} ;")
.append("methodName:{").append(trace[0].getMethodName()).append("} ;")
.append("lineNumber:{").append(trace[0].getLineNumber()).append("} ;")
.append("cause:{").append(e).append("}");
}
logger.info("save hits fail:"+sb.toString());
}
}
}


二、定义切面:用于做web请求切面日志

@Aspect   //定义一个切面
@Configuration
public class LogRecordAspect {

private Logger logger = LoggerFactory.getLogger(LogRecordAspect.class);
private String requestPath = null ; // 请求地址
private String encoding=null;
private String httpMethod =null;
private Map<String,String> headerMap = new HashMap<String,String>();
private Map<?, ?> paramter = null ; // 传入参数
private Map<String, Object> outputParamMap = null; // 存放输出结果
private long startTimeMillis = 0; // 开始时间
private long endTimeMillis = 0; // 结束时间

// 定义切点Pointcut
@Pointcut("execution(* com.gallery.*.*controller..*(..))")
public void excudeService() {
}

/**
*
* @Title:doBeforeInServiceLayer
* @Description: 方法调用前触发
*  记录开始时间
* @author shaojian.yu
* @date 2014年11月2日 下午4:45:53
* @param joinPoint
*/
@Before("excudeService()")
public void doBeforeInServiceLayer(JoinPoint joinPoint) {
startTimeMillis = System.currentTimeMillis(); // 记录方法开始执行的时间
}

@Around("excudeService()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
// 获取输入字符编码
encoding = request.getCharacterEncoding();
// 获取请求地址
requestPath = request.getRequestURL().toString();
httpMethod =request.getMethod();
paramter = request.getParameterMap();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
headerMap.put(key, value);
}
if ("POST".equals(httpMethod)) {
Object[] paramsArray = pjp.getArgs();
try {
paramter = argsArrayToString(paramsArray);
} catch (Exception e) {

}
}
// 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
outputParamMap = new HashMap<String, Object>();
Object result = null;
try {
result = pjp.proceed();
outputParamMap.put("result", result);
} catch (Exception e) {
StackTraceElement[] trace = new StackTraceElement[1];
trace[0] = e.getStackTrace()[0];
e.setStackTrace(trace);
StringBuffer sb = new StringBuffer("exception---");
if(null!=trace[0]){
sb.append("className:{").append(trace[0].getClassName()).append("} ;")
.append("methodName:{").append(trace[0].getMethodName()).append("} ;")
.append("lineNumber:{").append(trace[0].getLineNumber()).append("} ;")
.append("cause:{").append(e).append("}");
}
outputParamMap.put("result", sb.toString());
throw e;
}
return result;
}

/**
*
* @Title:doAfterInServiceLayer
* @Description: 方法调用后触发
*  记录结束时间
* @author shaojian.yu
* @date 2014年11月2日 下午4:46:21
* @param joinPoint
*/
@After("excudeService()")
public void doAfterInServiceLayer(JoinPoint joinPoint) {
endTimeMillis = System.currentTimeMillis(); // 记录方法执行完成的时间
this.printOptLog();
}
/**
*
* @Title:printOptLog
* @Description: 输出日志
* @author shaojian.yu
* @date 2014年11月2日 下午4:47:09
*/
private void printOptLog() {
String optTime = DateUtil.getSysDateTimeString();
logger.info("\nAddress: "+ requestPath+"\n"
+"Encoding: "+encoding+"\n"
+"Http-Method: "+httpMethod+"\n"
+"Op_time: "+optTime+"\n"
+"Pro_time: "+(endTimeMillis - startTimeMillis) +"ms\n"
+"Paramter: "+JSON.toJSONString(paramter) +"\n"
+"Header: "+JSON.toJSONString(headerMap)+"\n"
+"result: "+JSON.toJSONString(outputParamMap) +"\n"
+"-------------------------------------------------------------------------------------------------");
}

/**
* 请求参数拼装
*
* @param paramsArray
* @return
*/
@SuppressWarnings("rawtypes")
private Map<?,?> argsArrayToString(Object[] paramsArray) {
Object jsonObj=null;
Map params = new HashMap();
if (paramsArray != null && paramsArray.length > 0) {
for (int i = 0; i < paramsArray.length; i++) {
jsonObj = JSON.toJSON(paramsArray[i]);
params = JSON.parseObject(JSON.toJSONString(jsonObj));
}
}
return params;
}

}


三、切面日志:service中方法调用切面日志

@Aspect
@Component
public class SystemLogAspect {

private Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);

private String method =null;
private String paramter = null ; // 传入参数
private Map<String, Object> outputParamMap = null; // 存放输出结果
private long startTimeMillis = 0; // 开始时间
private long endTimeMillis = 0; // 结束时间
private String className = null;

// 定义切点Pointcut
@Pointcut("@within(com.isgo.gallerydao.core.support.annotation.Log)")
public void excudeService() {
}

/**
*
* @Title:doBeforeInServiceLayer
* @Description: 方法调用前触发
*  记录开始时间
* @author chen.danwei
* @date 2014年11月2日 下午4:45:53
* @param joinPoint
*/
@Before("excudeService()")
public void doBeforeInServiceLayer(JoinPoint joinPoint) {
startTimeMillis = System.currentTimeMillis(); // 记录方法开始执行的时间
}

@Around("excudeService()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{
className = joinPoint.getSignature().toLongString();
method = joinPoint.getSignature().getName();
Object[] paramsArray = joinPoint.getArgs();
paramter = argsArrayToString(paramsArray);
outputParamMap = new HashMap<String, Object>();
Object result =null;
try {
result = joinPoint.proceed();
outputParamMap.put("result", result);
} catch (Throwable e) {
StackTraceElement[] trace = new StackTraceElement[1];
trace[0] = e.getStackTrace()[0];
e.setStackTrace(trace);
StringBuffer sb = new StringBuffer("exception---");
if(null!=trace[0]){
sb.append("className:{").append(trace[0].getClassName()).append("} ;")
.append("methodName:{").append(trace[0].getMethodName()).append("} ;")
.append("lineNumber:{").append(trace[0].getLineNumber()).append("} ;")
.append("cause:{").append(e).append("}");
}
outputParamMap.put("result", sb.toString());
throw e;
}
return result;
}

@After("excudeService()")
public void doAfterInServiceLayer(JoinPoint joinPoint) {
endTimeMillis = System.currentTimeMillis(); // 记录方法执行完成的时间
this.printOptLog();
}

/**
* @Title:printOptLog
* @Description: 输出日志
* @author chen.danwei
* @date 2014年11月2日 下午4:47:09
*/
private void printOptLog() {
String optTime = DateTimeUtil.getYYYYMMddHHmmss(new Date());
try {
logger.info("\nClass: "+className+"\n"
+"Method: "+method+"\n"
+"Op_time: "+optTime+"\n"
+"Pro_time: "+(endTimeMillis - startTimeMillis) +"ms\n"
+"Paramter: "+paramter+"\n"
+"result: "+JSON.json(outputParamMap) +"\n"
+"-------------------------------------------------------------------------------------------------");
} catch (IOException e) {
logger.info("\nClass: "+className+"\n"
+"Method: "+method+"\n"
+"Op_time: "+optTime+"\n"
+"Pro_time: "+(endTimeMillis - startTimeMillis) +"ms\n"
+"paramter or outputParamMap turn to json String exception \n"
+"-------------------------------------------------------------------------------------------------");
}
}

/**
* 请求参数拼装
*
* @param paramsArray
* @return
*/
private String argsArrayToString(Object[] paramsArray) {
StringBuffer sb=new StringBuffer();
if (paramsArray != null && paramsArray.length > 0) {
for (int i = 0; i < paramsArray.length; i++) {
try {
sb.append(JSON.json(paramsArray[i])+";");
} catch (IOException e) {
logger.info("argsArrayToString method exception");
}
}
}
return sb.toString();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: