一.spring AOP
1.什么是AOP
Aop的全称是Aspect-Oriented Programing,即面向切面编程,是面向对象编程(OOP:Object-Oriented Programing)的一种延伸.AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时再讲这些提取出来的代码应用到需要执行的地方。
2.AOP术语
Aspect(切面)、JoinPoint(连接点)、Pointcut(切入点)、Advice(通知增强处理)、Target Object(目标对象)、Proxy(代理)、Weaving(织入)。
3.AspectJ开发
AspectJ是一个基于java语言的AOP框架。AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ;另一种是基于注解的申明式AspectJ
(1)基于XML的声明式AspectJ
常用元素的配置用例:
<!--定义切面Bean-->
<bean id="myAspect" class="com.ssm.aspectj.MyAspect"/>
<aop:config>
<!--配置切入点-->
<aop:pointcut id="myPointCut" expression="execution(* com.ssm.dao.impl.UserDaoImpl1.*(..))" />
<!--配置切面-->
<aop:aspect id="aspect" ref="myAspect">
<!--配置通知-->
<!--前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!--后置通知-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut"
returning="returnVal" />
<!--环绕通知-->
<aop:around method="myAround" pointcut-ref="myPointCut" />
<!--异常通知-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!--最终通知-->
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
</beans>
1.配置切面:
使用<aop:aspect>元素,该元素会将一个一斤定义好的spring bean转换成切面bean,所以要在配置文件中先定义一个普通的spring bean。定义完成后,通过<aop:aspect>元素的ref属性即可引用该bean。属性有id,ref。
2.配置切入点:
使用<aop:pointcut>元素来定义的。当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,可以被多个切面所共享。当<aop:pointcut>元素作为<aop:aspect>元素的子元素定义时,表示该切入点只对当前切面有效。属性有id,expression(用于指定切入点关联的切入点表达式)execution(* com.ssm.dao.impl.UserDaoImpl1.*(..)),第一个*指目标方法的返回值类型代指所有类型;com.ssm.dao.impl.UserDaoImpl1.*代指需要被代理的目标方法,第二个*代指所有方法;(..)代指目标方法的任意参数。
3.配置通知
在<aop:aspect>的子元素配置5种常用通知,这些元素不支持在使用子元素,但在使用时可以指定一些属性:
属性名称 | 描述 |
pointcut | 用于指定一个切入点表达式,spring将在匹配该表达式的连接点是植入该通知 |
pointcut-ref | 制定一个已经存在的切入点名称,如上面例子里面的myPoint.通常pointcut和pointcut-ref两个属性只需要使用其中之一就可以了 |
method | 指定一个方法名,指定将切面bean中的该方法转换为增强处理 |
throwing | 只对<after-throwing>元素有效,用于指定一个形参名,异常通知方法可以通过该形参名访问目标方法所抛出的异常 |
returning | 只对<after-retruning>元素有效,用于制定一个形参名,后置通知方法可以通过该形参访问目标方法的返回值 |
以下为一个通过xml声明式的例子
(1)创建一个接口 UserDao.java
package com.ssm.dao;
public interface UserDao1 {
public void addUser();
public void deleteUser();
}
(2)创建一个实现类 UserDaoImpl.java
package com.ssm.dao.impl;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import com.ssm.dao.UserDao1;
public class UserDaoImpl1 implements UserDao1{
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
(3)创建一个切面类MyAspect.java
package com.ssm.aspectj;
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;
import org.springframework.stereotype.Component;
public class MyAspect {
public void myBefore(JoinPoint joinPoint){
System.out.print("前置通知:模拟执行权限检查...");
System.out.print("目标类值:"+joinPoint.getTarget());
System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature()
.getName());
}
public void myAfterReturning(JoinPoint joinPoint)
{
System.out.print("后置通知:模拟记录日志...");
System.out.println("被植入增强处理的目标方法
为:"+joinPoint.getSignature().getName());
}
public Object myAround(ProceedingJoinPoint proceddingJoinPoint) throws Throwable
{
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
//执行当前目标方法
Object obj=proceddingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务");
return obj;
}
public void myAfterThrowing(JoinPoint joinPoint,Throwable e)
{
System.out.println("异常通知:出错了"+e.getMessage());
}
public void myAfter()
{
System.out.println("最终通知:模拟方法结束后释放资源...");
}
}
(4)创建xml文件ApplicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<bean id="userDao" class="com.ssm.dao.impl.UserDaoImpl1"/>
<bean id="myAspect" class="com.ssm.aspectj.MyAspect"/>
<aop:config>
<aop:pointcut id="myPointCut" expression="execution(* com.ssm.dao.impl.UserDaoImpl1.*(..))" />
<aop:aspect id="aspect" ref="myAspect">
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal" />
<aop:around method="myAround" pointcut-ref="myPointCut" />
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e" />
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
</beans>
(5)创建测试类TestXmlAspectJ,java
package com.ssm.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.ssm.dao.UserDao1;
public class TestXmlAspectj {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("ApplicationContext3.xml");
UserDao1 userDao=(UserDao1)applicationContext.getBean("userDao");
userDao.addUser();
}
}
结果是:
前置通知:模拟执行权限检查...目标类值:com.ssm.dao.impl.UserDaoImpl1@78b1cc93被植入增强处理的目标方法为:addUser
环绕开始:执行目标方法之前,模拟开启事务...
添加用户
最终通知:模拟方法结束后释放资源...
环绕结束:执行目标方法之后,模拟关闭事务
后置通知:模拟记录日志...被植入增强处理的目标方法为:addUser
(2)基于注解的声明式AspectJ
注解介绍
注解名称 | 描述 |
@Aspect | 用于定义一个切面 |
@Pointcut | 用于定义一个切入点表达式,在使用时还需定义 一个包含名字和任意参数的方法参数,来表示切入点名称,实际上,这个方法签名就是个返回值为void且方法体为空的普通方法。 |
@Before | 用于定义前置通知,通常要制定一个value属性值,该属性值用于制定一个切入点表达式(可以是已有的切入点,也可以是直接定义切入点表达式) |
@AfterReturning | 用于定义后置通知,在使用时可以指定pointcut或value,returning属性,其中pointcut和value两个属性一样都指定切入点。returning属性值用于表示Advice()方法中定义与此同名的形参,该形参可用于访问目标方法的返回值 |
@Around | 用于定义环绕通知,在使用时需要制定一个value属性,该属性用于指定通知被植入的切入点 |
@AfterThrowing | 用于定义异常通知。在使用时可指定pointcut/value和throwing属性。其中pointcut/value与@AfterReturning一样。throwing属性值,用于指定一个形参名开表示Advice()方法中可以定义与此同名的形参,该形参可用于访问目标方法抛出的异常 |
@After | 用于定义最终通知,无论是否有异常,该通知都会执行,使用时需要制定一个value值,用于指定该通知Neo植入的切入点 |
@DeclareParents | 用于定义引介通知,(不需掌握) |
例子:(用xml配置例子)
创建一个MyAspect.java
package com.ssm.aspectj;
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;
import org.springframework.stereotype.Component;
@Aspect//将该类定义前面
@Component//由于该类在spring中时作为组件使用的
public class MyAspect1 {
//定义切点表达式
@Pointcut("execution(* com.ssm.dao.impl.UserDaoImpl1.*(..))")
//使用一个返回值为void,方法体为空的方法来命名切入点
public void myPointCut()
{
}
//前置通知
@Before(value = "myPointCut()")
public void myBefore(JoinPoint joinPoint){
System.out.print("前置通知:模拟执行权限检查...");
System.out.print("目标类值:"+joinPoint.getTarget());
System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
//后置通知
@AfterReturning(value="myPointCut()")
public void myAfterReturning(JoinPoint joinPoint)
{
System.out.print("后置通知:模拟记录日志...");
System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
//环绕通知
@Around(value="myPointCut()")
public Object myAround(ProceedingJoinPoint proceddingJoinPoint) throws Throwable
{
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
//执行当前目标方法
Object obj=proceddingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务");
return obj;
}
//异常通知
@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e)
{
System.out.println("异常通知:出错了"+e.getMessage());
}
//最终通知
@After(value="myPointCut()")
public void myAfter()
{
System.out.println("最终通知:模拟方法结束后释放资源...");
}
}
在目标类UserDaoImpl.java(该类在上面有)中添加@repository("userDao")
package com.ssm.dao.impl;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import com.ssm.dao.UserDao1;
@repository("userDao")
public class UserDaoImpl1 implements UserDao1{
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
创建applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<!--指定需要少扫描的包,使注解生效-->
<context:component-scan base-package="com.ssm"/>
<!--启动基于注解的声明AspectJ支持-->
<aop:aspectj-autoproxy/>
</beans>
创建测试类TestAnnotation.java
package com.ssm.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.ssm.dao.UserDao1;
public class TestXmlAspectj {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("ApplicationContext3.xml");//引号里要与你所建xml配置文件同名
UserDao1 userDao=(UserDao1)applicationContext.getBean("userDao");
userDao.addUser();
}
}
结果与上面一样