一,回顾
1.控制反转(IOC)
以前创建对象,由我们自己决定,现在我们把管理对象的声明周期权力交给spring;
2.依赖注入(DI)
A对象需要B对象的支持,spring就把B注入给A,那么A就拥有了B;
底层通过反射技术实现(Class字节码)
3.具体实现
(1)实体类
(2)把实体类注册到xml配置文件中
(3)通过context上下文对象(spring容器)获取进行使用
二,AOP简介
AOP(Aspect Oriented Programming) 面向切面编程(横向);
OOP面向对象编程(纵向编程)
AOP技术利用"横切",解刨开封装的对象内容,将那些影响了多个类的公共行为封装到一个可重用的模块,并将其命名为"Aspect",即切面,切面就是与主业务无关,却为主业务模块提供额外的扩展,便于减少系统代码的重复性,降低模块之间的耦合性,并利于系统的可操作性和维护性;
AOP底层使用代理技术实现,代理内部有反射技术的影子
三AOP基本概念
1.Aspect(切面)
通常是一个类,里面可以定义切入点和通知
2.JoinPoint(连接点)
被拦截的点,因为spring只支持方法类型的连接点,所以在spring中连接点指定就是被拦截的方法,实际上连接点还可以是字段或者构造器
3.advice(通知)
指拦截到的连接点(方法)之后要执行的增强代码,通知分为:前置,后置,异常,最终,环绕通知(before,after,afterThrowing,afterReturing,around)
4.pointcut(切入点)
对连接点(方法)进行拦截的定义,在程序主要体现为输入切入点表达式
5.tarage(目标对象)
代理的目标对象
6.proxy(代理)
AOP框架自动创建的代理对象
四,spring aop基于xml方式实现
1,添加依赖
<!-- spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.0.RELEASE</version> </dependency> <!-- aop依赖的jar包 --> <!-- aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version> </dependency> <!--aspectjrt --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> <scope>provided</scope> </dependency>
2、UserServiceImpl业务类
public class UserServiceImpl { public boolean delete(int id){ System.out.println("UserServiceImpl.delete..."+id); return true; }
3、TranAop切面类
此类为切面类,给业务层类中方法添加增强代码
public class TranAop { //前置通知 public void before(){ System.out.println("TranAop.before..."); } //返回值后通知 public void afterReturning(){ System.out.println("TranAop.afterReturning..."); } //异常通知 public void afterThrowing(Exception ex){ System.out.println("TranAop.afterThrowing..."+ex.getMessage()); } //后置通知(最终) public void after(){ System.out.println("TranAop.after..."); } //环绕通知 public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("TranAop.around.start"); Object[] args = pjp.getArgs(); Object result = null; //try { result = pjp.proceed();//执行目标方法 //} catch (Throwable e) { // System.out.println("发生异常了哦:"+e.getMessage()); //} System.out.println("TranAop.around.end"); return result; } }
4、beans.xml
我们需要再beans.xml中配置aop;需要在xml中添加头部schema文件引用
<?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" 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 --> <bean id="user1" class="com.yujun.maven.service.UserServiceImpl"></bean> <bean id="tranAop" class="com.yujun.maven.aop.TranAop"></bean> <!-- spring aop 配置 --> <aop:config> <!-- 声明切面类 id:唯一标识 ref:引用bean的id--> <aop:aspect id="myAspect" ref="tranAop"> <!-- 声明切入点(拦截哪些方法) 拦截service包中所有类中所有方法--> <aop:pointcut expression="execution(* com.yujun.maven.service.*.*(..))" id="myPointcut"/> <!-- 实际开发中,下面5中通知,根据实际情况进行合理的选中,不是说像我们现在这样5种都配置 --> <!-- 前置通知 method:切面类中的方法名--> <aop:before method="before" pointcut-ref="myPointcut"/> <!-- 后置通知 --> <aop:after method="after" pointcut-ref="myPointcut"/> <!-- 最终通知 --> <aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/> <!-- 异常通知 throwing:是切面类中方法的形参名--> <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="ex"/> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="myPointcut"/> </aop:aspect> </aop:config> </beans>
5、测试
public class Demo1 { public static void main(String[] args) { //context上下文 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserServiceImpl bean = context.getBean("user1", UserServiceImpl.class); bean.delete(10);
运行后的控制台输出结果:
TranAop.before...
TranAop.around.start
UserServiceImpl.delete...10
TranAop.around.end
TranAop.afterReturning...
TranAop.after...
可以看到在目标方法UserServiceImpl.delete的前后有额外的输出语句,也就表名之前我们的aop配置生效;
这里没有看到有关于异常通知的输出,原因是我们目标方法delete没有发生异常,所以就不会执行异常通知;
若想看到异常通知被执行,只需要在目标方法中添加一个可以触发异常的语句即可,当前前提是我们的环绕通知中没有对目标方法进行异常处理;
五,springaop基于注解的方式实现
下面我们使用注解方式来实现aop技术,相对来说注解方式比xml配置更简洁,所以现在实际开发中我们都会优先考虑使用注解方式;
1、AserviceImpl业务类
业务类,我们通常会统一放在service包中;
public class AServiceImpl { public int m1(String s){ System.out.println("AServiceImpl.m1..."+s); return 100; } }
2、LogAop切面类
//组件:会把当前类当做bean注册到spring容器中,等效于xml中<bean /> @Component //切面类:等效于xml中的<aop:aspect /> @Aspect public class LogAop { //切入点(拦截哪些类中的哪些方法):等效于xml中<aop:pointcut /> @Pointcut("execution(* com.yujun.maven.service.*.*(..))") private void pointCut(){ } //前置通知:等效于xml中<aop:before />,里面的pointCut()字符串是本类中的切入点方法 @Before("pointCut()") public void before(){ System.out.println("LogAop.before..."); } //返回通知:等效于xml中<aop:after-returning /> @AfterReturning(pointcut="pointCut()",returning="result") public void afterReturning(Object result){ System.out.println("LogAop.afterReturning..."+result); } //异常通知:等效于xml中<aop:after-throwing /> @AfterThrowing(pointcut="pointCut()",throwing="ex") public void afterThrowing(Exception ex){ System.out.println("LogAop.afterThrowing..."+ex.getMessage()); } //最终通知:等效于xml中<aop:after /> @After("pointCut()") public void after(){ System.out.println("LogAop.after..."); } //环绕通知:等效于xml中<aop:around /> @Around("pointCut()") public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("LogAop.around.start..."); Object result = pjp.proceed();//执行目标方法 System.out.println("LogAop.around.end..."+result); return result; } }
此类中我们使用很多注解,每个注解的含义都有详细的说明,这里就不多做额外的解释;
主要说一下@Component注解,次注解是spring的基础注解,被此注解标注的类会被当做组件注册到spring容器中,也就相当于之前我们在xml中配置<bean />,也就是说只要在类上标注此注解,我们就不用像之前一样在xml中使用<bean />标签注册实例了;
3、beans-anno.xml
此xml配置文件我们需要添加context的schema文件头引用
<?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"> <!-- 开启扫描包:把base-package指定的包中的类中的组件注册到spring容器中--> <context:component-scan base-package="com.yujun.maven.aop"/> <!-- 为 @AspectJ标注的类提供代理--> <aop:aspectj-autoproxy/> </beans>
<context:component-scan />标签配置的作用就是开启扫包功能,它会扫描base-package属性中指定的包中所有类,然后把类上标注有@Component, @Repository, @Service, @Controller, @RestController, @ControllerAdvice, @Configuration等注解的类注册添加到spring容器中;
<aop:aspectj-autoproxy/>标签则是为使用@AspectJ注解风格的切面类提供代理,若没有此配置,我们的目标方法时没有aop技术支持的
4、@Service注解
//组件:业务逻辑层的组件,等效于<bean /> @Service public class AServiceImpl { public int m1(String s){ System.out.println("AServiceImpl.m1..."+s); return 100; } }
对于AserviceImpl业务类,我们之前是通过在xml中配置<bean />标签来注册实例,那么这里我们还可以选择使用@Service注解,此注解标注的类会被当做一个服务性质组件实例被注册到spring容器中,等效于xml中<bean />标签配置实例;
注意的是我们还需要在bean-anno.xml中新增一个扫包器,因为此业务类在service包中;
<context:component-scan base-package="com.yujun.maven.service"/>
5、测试
public class Demo2 { public static void main(String[] args) { //context上下文 ApplicationContext context = new ClassPathXmlApplicationContext("beans-anno.xml"); AServiceImpl a1 = context.getBean(AServiceImpl.class); a1.m1("admin"); } }
运行之后控制台输出
LogAop.around.start...
LogAop.before...
AServiceImpl.m1...admin
LogAop.around.end...100
LogAop.after...
LogAop.afterReturning...100
可以看到我们注解版的aop技术已经成功实现,就是输出的语句顺序和执行xml方法稍微不同,但是最终的功能实现是没有差别的;
六、小结
对于上述的两种方式aop实现,需要自己下来多多测试,然后认真理解其执行流程,这样才能对aop面向切面技术有一个更深的理解和认识;
在aop注解版方式中,我们介绍的@Component和@Service两个注解以及 xml配置中的<context:component-scan />标签扫描器,我们在后面章节也就着重说明;