Spring AOP学习笔记
一、AOP
1.什么是 AOP
AOP:Aspect-Oriented Programming面向切面编程,是面向对象编程的一种补充。
将程序中的交叉业务(事务,日志)代码提取出来,封装成切面,由AOP容器在合适的时机(位置)将封装的切面动态的织入到具体的业务逻辑中。
注意:AOP不是Spring特有的
1.1 应用场合
适用于具有横切逻辑的场合,如事务管理,日志记录,性能监测,异常通知,访问控制。
1.2 作用
- 不改变原有代码的基础上,动态的添加新的功能
- 模块化(方便维护,可扩展性更强)
1.3连接点
- 连接点JoinPoint
程序执行的某个特定的位置,如方法执行前,方法调用后,方法抛出异常时或者方法调用前后。 - 切入点PointCut
定位查找到需要的连接点,即切点 - 增强Advice也称为通知
在切点上执行的一段程序代码,用来实现某些功能 - 目标对象Target
将执行增强处理的目标类 - 织入Weaving
将增强添加到目标类具体切入点上的过程 - 代理Proxy
一个类被织入增强后,会产生代理类 - 切面Aspect
切点和增强的组合 - 引介Introduction也称为引入
2. 实现原理
2.1 代理模式
2.1.1 概念
为其他对象提供一种代理,以控制对这个对象的访问,起到中介的作用。通过代理对象来访问目标对象,可以增强额外的操作,扩展目标对象的功能。
2.1.2 分类:
- 静态代理
代理类是程序员创建或工具生成的。
所谓静态代理就是程序运行前,就已经存在代理类的字节码文件。
缺点:代理对象需要和目标对象实现相同的接口,如果接口增加了方法,目标对象和代理对象都需要进行维护。 - 动态代理
代理类是程序在运行期间由JVM通过反射等机制动态生成的,自动生成代理类和代理对象。
所谓动态就是指在程序运行前,不存在代理类的字节码文件,在运行的时候动态生成的。
2.1.3 代理三要素
- 与目标类实现相同接口
- 找到目标类的实例
- 交叉业务逻辑,要执行的操作
动态代理的两种技术
- jdk技术
Proxy.newProxyInstance(
classLoader, //目标类的类加载器
interfaces, //目标类实现的接口
InvacationHandler //交叉业务逻辑
);
缺点:目标对象必须实现一个或多个接口,如果目标类没有实现接口则无法使用。
- cglibj技术(适用于无接口时使用)
如果没有实现接口,则通过继承来实现
步骤:
1.添加jar包
2.通过Enhancer类调用create方法进行操作
用法:
<!--cglib依赖-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${cglib.version}</version>
</dependency>
Enhancer.create(
class,//目标类的类型
InvocationHandler,//交叉业务逻辑
);
2.2 AOP原理
Spring AOP就是使用动态代理
- 对于实现接口的目标类,使用的是JDK的动态代理
- 对于没有实现接口的目标类,使用的是CGLIB的动态代理
3.Spring AOP的配置方式
3.1 三种配置方式
- Spring AOP 1.x,使用ProxyFactoryBean手动代理
- Spring AOP 2.x,基于命名空间的配置
- Annotation,基于注解的配置(推荐)
3.2 Advice类型
Spring AOP支持五种类型的通知注意:多种Advice之间不能有耦合,即多个Advice之间不能有业务交叉
二、Spring AOP 1.x
1.基本用法
1.1添加jar包
<!--Spring AOP-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
1.2 配置目标类
<!--配置目标类-->
<bean id="userServiceTarget" class="aop04.service.impl.UserServiceImpl"/>
1.3 配置Advice
定义增强类,并实现相同接口
<!--配置增强类-->
<bean id="logAdvice" class="aop04.advice.LogAdvice"/>
1.4 配置Pointcut
定义切入点,配置位置信息,指定哪些类的哪些方法需要被增强
使用NameMatchMethodPointcutAdvisor对Pointcut进行Advice
Advisor是Pointcut+Advice的配置器,Advisor=Pointcut+Advice
<!--配置要增强的切入点,织入的过程-->
<bean id="logAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<!--指定Advice-->
<property name="advice" ref="logAdvice"/>
<!--配置切入点,指定要匹配的方法名-->
<property name="mappedNames">
<list>
<value>login</value>
<value>logout</value>
</list>
</property>
</bean>
1.5 配置代理
使用ProxyFactoryBean配置代理
<!--配置代理-->
<bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="userServiceTarget"/><!--目标类的实例-->
<property name="interfaces"><!--目标类的接口列表-->
<list>
<value>aop04.service.UserService</value>
</list>
</property>
<property name="interceptorNames"><!--交叉业务逻辑-->
<list>
<value>logAdvisor</value>
</list>
</property>
</bean>
三、Spring AOP 2.x
1.简介
基于命名空间的配置,原理时使用后处理器,配置更简单
特点:
- 简化配置
- 非侵入性,编写通知时不需要实现任何接口
- 使用AspectJ表达式定义切点
2.基本用法
2.1配置Advice
定义增强类,不需要实现任何接口,但有多种写法
写法
2.2配置Pointcut并进行织入
四、AspectJ表达式
1.简介
切入点表达式,一种表达式,用来定义切入点的位置
2.用法
2.1within
语法:within(包名.类名)
匹配该类中的所有方法
2.1.1 具体实现
<!--Spring AOP 2.x-->
<!--配置目标类-->
<bean id="userService" class="aop06.service.impl.UserServiceImpl"/>
<!--配置Advice-->
<bean id="logAdvice" class="aop06.advice.LogAdvice"/>
<!--配置切入点,并进行织入-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pc" expression="within(aop06.service.impl.UserServiceImpl)"/>
<!--织入-->
<aop:aspect ref="logAdvice">
<!--将logAdvice中的log方法以前置通知的方式织入到对应的切入点中-->
<!--<aop:before method="log" pointcut-ref="pc"/>-->
<!--<aop:after-returning method="log2" pointcut-ref="pc" returning="returnValue"/>-->
<!--<aop:after-throwing method="log3" pointcut-ref="pc" throwing="e"/>-->
<aop:around method="log4" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
2.2 execution
匹配特定包中的特定类中的特定返回值类型的特定参数的特定方法。
语法:execution(表达式)
表达式:返回值类型 包名 类名 方法名(参数类型)
通配符:*和..
<!--匹配返回值类型为void并且在aop06包下的service下的impl下的ProductServiceImpl实现类下的deleteById方法并且参数为int类型-->
<aop:pointcut id="pc2" expression="execution(void aop06.service.impl.ProductServiceImpl.deleteById(int))"/>
<!--匹配任意返回值类型并且在aop06包下的service包下的impl包中的任意类中的任意方法并且参数列表也是任意的-->
<aop:pointcut id="pc3" expression="execution(* aop06.service.impl.*.*(..))"/>
五、IoC的注解
1.简介
Spring中提供了一系列的注解,来替代配置文件中的配置
实际开发中,建议使用注解+配置文件的形式
2.IoC注解使用
2.1 步骤:
- 扫描包
<!--扫描包,可以扫描多个-->
<context:component-scan base-package="ioc"/>
<context:component-scan base-package="com.hxx"/>
2.2 常用注解
2.2.1 组件的定义
- @Component :定义bean组件,添加到IoC容器中,不区分组件类型
- @Repository:表示Dao层组件
- @Service:表示Service层组件
- @Controller:表示Action层组件
- 数据装配
注意:注解方式的装配是直接使用属性进行装配,可以不要set方法- 简单类型
- @Value
- 简单类型
@Value("666")
private int num;
@Value("true")
private Boolean flag;
@Value("${jdbc.username}")
private String username;
@Value("java.lang.String")
private Class clazz;
@Value("classpath:ioc/applicationContext.xml")
private Resource resource;
<!--读取属性文件-->
<!--<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="ioc/info.properties"/>
</bean>-->
<context:property-placeholder location="classpath:ioc/info.properties"/>
其他bean的引用
/**
* 方式1:使用@Autowired
* 自动装配:默认按byType,如果有多个同类型的bean,则按照byName
* 结合Qualifier按照指定byName注入
* 方式2:使用@Resource,javaEE提供
*/
//@Autowired
//@Qualifier("ob")
@Resource
private OtherBean otherBean;
集合的装配
//集合的装配使用@Resource注解,默认按照名字进行装配
@javax.annotation.Resource
private Integer[] arrays;
@javax.annotation.Resource
private List<OtherBean> list;
@javax.annotation.Resource
private Set<OtherBean> set;
@javax.annotation.Resource
private Map<OtherBean,Class> map;
@javax.annotation.Resource
private Properties p;
<!--集合类型的装配-->
<util:list id="arrays">
<value>1</value>
<value>2</value>
<value>3</value>
</util:list>
<util:list id="list">
<ref bean="otherBean"/>
<ref bean="otherBean"/>
<ref bean="ob"/>
<bean class="ioc.OtherBean">
<property name="msg" value="嘻嘻嘻"/>
</bean>
</util:list>
<util:set id="set">
<ref bean="otherBean"/>
<ref bean="otherBean"/>
<ref bean="ob"/>
<bean class="ioc.OtherBean">
<property name="msg" value="嘻嘻嘻"/>
</bean>
</util:set>
<util:map id="map">
<entry key-ref="otherBean" value="java.lang.String"/>
</util:map>
<util:properties id="p">
<prop key="key1">value1</prop>
<prop key="key2">value2</prop>
</util:properties>
- 生命周期
//相当于init-method属性
@PostConstruct
public void init(){
System.out.println("SpringBean.init");
}
//相当于destroy-method属性
@PreDestroy
public void destroy(){
System.out.println("SpringBean.destroy");
}
-
bean实例化的时机
使用@Lazy
注解将实例化的时机更改为调用bean时再进行实例化 -
scope作用域
默认是单例的,可以使用@Scope("prototype")
注解将作用域改为非单例的。
六、AOP的注解
1.配置Advice
定义增强类,添加@Component和@Aspect
@Component
@Aspect //表示这是一个切面
public class LogAdvice {
//定义切点表达式
@Pointcut("execution(* aop.service.impl.*ServiceImpl.*(..))")
public void pc(){
}
@Pointcut("execution(* aop.service.impl.*.*(..))")
public void pc2(){
}
/*@Before("pc()")
public void before(JoinPoint joinPoint){
System.out.println("LogAdvice.before,name:"+joinPoint.getSignature().getName()+
",args:"+ Arrays.toString(joinPoint.getArgs())+",target:"+joinPoint.getTarget());
}
@AfterReturning(value = "pc()",returning = "returnValue")
public void afterReturning(JoinPoint joinPoint,Object returnValue){
System.out.println("LogAdvice.afterReturning,returnValue:"+returnValue);
}
@AfterThrowing(value = "pc2()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Exception e){
System.out.println("LogAdvice.afterThrowing,e"+e);
}*/
@Around("pc()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知执行前。。。");
Object proceed = joinPoint.proceed();
System.out.println("环绕通知执行后。。。");
return proceed;
}
}
2.配置Pointcut并指定通知的类型
@Pointcut(切点表达式)
@Before(切点方法+())
@AfterReturning()
@AfterThrowing()
@Around()
3.织入
<!--
自动创建代理,并织入到切面
proxy-target-class默认值是false,为JDK的动态代理
true代表使用CGLIB
-->
<aop:aspectj-autoproxy proxy-target-class="true"/>