一、Spring中的AOP
spring中的aop,可以通过配置的方法,实现动态代理的功能。
相应的动态代理以及静态代理实现在上一篇博客已经详细的写了出来
什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
依赖注入(DI)有助于应用对象之间的解耦,面向切面编程(AOP)有助于横切关注点与所影响的对象之间的解耦
AOP的一些相关名词
-
横切关注点:跨越影响应用程序多个模块的方法或功能。与业务逻辑无关,常见的横切关注点。比如:日志、缓存以及事务等
-
连接点( Joinpoint):被拦截的点,spring中的连接点点是方法
-
切入点(Pointcut):切入点就是指明对那些连接点进行拦截
-
切面(Aspect):是切入点和通知的结合,是一个类
-
通知(Advice):拦截到连接点之后所要做的事情。分别是:前置通知,后置通知,异常通知,最终通知,环绕通知
-
目标(Target):代理的目标对象
-
代理(Proxy):执行通知之后创建的对象
五种通知类型
- 前置通知-Before:连接点前面执行,前置通知不会影响连接点的执行
- 正常返回通知-After returning:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
- 异常返回通知-After throwing:在连接点抛出异常后执行。
- 返回通知-After:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
- 环绕通知-Arount:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。
二、使用spring实现aop
代码准备:
UserInfo接口和实现类
public interface IUserInfoDao {
//保存的方法
public abstract void save(UserInfo userInfo);
//更新的方法
public abstract void update(UserInfo userInfo);
//查询 返回值是一个UserInfo对象
UserInfo queryByLoginName(String uname);
void demo();
}
@Repository
public class UserInfoDao implements IUserInfoDao {
@Override
public void save(UserInfo userInfo) {
System.out.println("UserInfoDao save method invoke");
}
@Override
public void update(UserInfo userInfo) {
System.out.println("UserInfoDao update method invoke");
}
@Override
public UserInfo queryByLoginName(String uname) {
return new UserInfo(2, "root2", "123456789", "James2", false);
}
@Override
public void demo(){
System.out.println("demo");
}
}
@Service
public class UserInfoService {
@Autowired
private IUserInfoDao userInfoDao;
//注册
public void register(UserInfo userInfo){
UserInfo dbui = userInfoDao.queryByLoginName(userInfo.getUname());
//如果传回来的不是null,说明存在该用户名
if (dbui!=null){
throw new RuntimeException("用户名已经存在");
}
//保存
userInfoDao.save(userInfo);
}
//登录 传入的参数是接受的UserInfo 返回值是数据库返回的UserInfo
public UserInfo login(UserInfo userInfo){
UserInfo dbui = userInfoDao.queryByLoginName(userInfo.getUname());
//说明用户名不存在
if (dbui==null){
throw new RuntimeException("用户名或密码错误(Dao返回的是null--用户名不存在)");
}
//dbui是数据库返回的UserInfo userInfo是登录的UserInfo
if (!dbui.getUpass().equals(userInfo.getUpass())){
throw new RuntimeException("用户名或密码错误(两个密码不一致)");
}
return dbui;
}
//更新
public void update(UserInfo userInfo){
userInfoDao.update(userInfo);
}
public void demo(){
System.out.println("demo");
//int num=10/0;
}
}
实体类:
public class UserInfo {
private int uid;
private String uname;
private String upass;
private String unikename;
private boolean gender;
}
//省略了无参构造、有参构造以及set、get方法
spring的配置文件
<!--开启注解-->
<context:component-scan base-package="com.gx"></context:component-scan>
<!--配置切面类-->
<bean id="loggerAspect" class="com.gx.LoggerAspect"></bean>
<!--aop:config AOP的配置-->
<aop:config>
<!--切点的配置 所有的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.gx..*.*.*(..))"/>
<!--aop:aspect 切面的配置-->
<aop:aspect ref="loggerAspect" id="logAs">
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:around method="around" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
<aop:after-returning method="AfterReturning" pointcut-ref="pointcut"/>
<aop:after-throwing method="throwing" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
定义切面类
public class LoggerAspect {
//定义切面类
/**
* 类中写的是非核心业务代码,在执行的过程中,通过spring容器和AOP将切面类方法的代码和目标类的代码整合
*/
//前置通知
public void before(JoinPoint joinPoint){
/**
* getArgs():返回方法参数。
*
* getThis():返回代理对象。
*
* getTarget():返回目标对象。
*
* getSignature():返回建议使用的方法的描述。
*
* toString():打印有关所建议方法的有用说明。
*/
Object[] args = joinPoint.getArgs();//参数
String mname = joinPoint.getSignature().getName();
String cname = joinPoint.getTarget().getClass().getName();//目标对象
String pname = joinPoint.getThis().getClass().getName();//代理对象
// System.out.println("目标对象:"+cname+"<方法名称:"+mname+"<参数:"+args+"<代理对象:"+pname);
//String msg=String.format("目标对象:%s<方法名称:%s<参数:%s<代理对象:%s",cname,mname,args,pname);
//System.out.println(msg);
System.out.println("---LoggerAspect--->before(前置方法)非核心代码执行"+joinPoint.getTarget().getClass().getName());
}
//环绕通知 会返回一个对象
public Object around(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
Object result=null;
System.out.println("around环绕之前"+proceedingJoinPoint.getTarget().getClass().getName());
//执行目标方法。必须执行,否则后续代码不执行
result = proceedingJoinPoint.proceed();
System.out.println("around环绕后"+proceedingJoinPoint.getTarget().getClass().getName());
return result;
}
//after后置通知
public void after(JoinPoint joinPoint){
System.out.println("---LoggerAspect--->after(后置方法)非核心代码执行"+joinPoint.getTarget().getClass().getName());
}
//AfterReturning
public void AfterReturning(JoinPoint joinPoint){
System.out.println("---LoggerAspect--->AfterReturning(置方法)非核心代码执行"+joinPoint.getTarget().getClass().getName());
}
public void throwing(JoinPoint joinPoint){
System.out.println("---LoggerAspect--->throwing(异常方法)非核心代码执行"+joinPoint.getTarget().getClass().getName());
}
}
<!--配置切面类-->
<bean id="loggerAspect" class="com.gx.LoggerAspect"></bean>
配置切面类,定义切入点,并且声明五个通知方法
三、切面类配置
@Aspect 声明切面类
定义一个切入点
@Pointcut("execution(* com.gx..*.*.*(..))")
public void point(){}
以环绕通知为例
@Around("point()")
public Object around(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
Object result=null;
System.out.println("around环绕之前Annoation2"+proceedingJoinPoint.getTarget().getClass().getName());
//执行目标方法。必须执行,否则后续代码不执行
result = proceedingJoinPoint.proceed();
System.out.println("around环绕后Annoation2"+proceedingJoinPoint.getTarget().getClass().getName());
return result;
}
@Around(“point()”) 引入切入点
环绕通知必须执行
proceedingJoinPoint.proceed();
否则后续代码无法执行
@Component
@Aspect
public class AspectAnnotation {
//定义切面类
@Pointcut("execution(* com.gx..*.*.*(..))")
public void point(){}
@Before("point()")
public void before(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();//参数
String mname = joinPoint.getSignature().getName();
String cname = joinPoint.getTarget().getClass().getName();//目标对象
String pname = joinPoint.getThis().getClass().getName();//代理对象
System.out.println("---LoggerAspectAnnoation2--->before(前置方法)非核心代码执行"+joinPoint.getTarget().getClass().getName());
}
//环绕通知 会返回一个对象 抛出异常的
@Around("point()")
public Object around(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
Object result=null;
System.out.println("around环绕之前Annoation2"+proceedingJoinPoint.getTarget().getClass().getName());
//执行目标方法。必须执行,否则后续代码不执行
result = proceedingJoinPoint.proceed();
System.out.println("around环绕后Annoation2"+proceedingJoinPoint.getTarget().getClass().getName());
return result;
}
//after后置通知
@After("point()")
public void after(JoinPoint joinPoint){
System.out.println("---LoggerAspectAnnoation2--->after(后置方法)非核心代码执行"+joinPoint.getTarget().getClass().getName());
}
//AfterReturning
@AfterReturning("point()")
public void AfterReturning(JoinPoint joinPoint){
System.out.println("---LoggerAspectAnnoation2--->AfterReturning(方法)非核心代码执行"+joinPoint.getTarget().getClass().getName());
}
@AfterThrowing(pointcut = "point()",throwing = "t")
public void throwing(JoinPoint joinPoint,Throwable t){
System.out.println("---LoggerAspectAnnoation2--->throwing(异常方法)非核心代码执行"+joinPoint.getTarget().getClass().getName()+t.getMessage());
}
}
<!--注解配置-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- xml的另一种配置 通知-->
<!-- advisor 定义通知器-->
<bean id="advisor" class="com.gx.Aspect.LoggerAspectAdvisor"></bean>
<!-- 切点-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.gx..*.*.*(..))"/>
<aop:advisor advice-ref="advisor" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
aop:aspectj-autoproxy 说明
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。
执行结果
四、xml实现的两种方法
- 方式1:切面
<!--配置切面类-->
<bean id="loggerAspect" class="com.gx.LoggerAspect"></bean>
<!--aop:config AOP的配置-->
<aop:config>
<!--切点的配置 所有的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.gx..*.*.*(..))"/>
<!--aop:aspect 切面的配置-->
<aop:aspect ref="loggerAspect" id="logAs">
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:around method="around" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
<aop:after-returning method="AfterReturning" pointcut-ref="pointcut"/>
<aop:after-throwing method="throwing" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
- 方式2:通知
<bean id="advisor" class="com.gx.Aspect.LoggerAspectAdvisor"></bean>
<!-- 切点-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.gx..*.*.*(..))"/>
<aop:advisor advice-ref="advisor" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
五、AOP总结
-
AOP就是一个动态代理
-
横向编程,不同类不同方法的交叉业务