基于 XML 的 AOP 开发
问题1:在通知方法中如何定义切入点表达式?
问题2:如何配置切面?
问题3:在配置类上如何开启AOP注解功能?
1.1 快速入门
①导入 AOP 相关坐标
②创建目标接口和目标类(内部有切点)
③创建切面类(内部有增强方法)
④将目标类和切面类的对象创建权交给 spring
⑤在 applicationContext.xml 中配置织入关系
⑥测试代码
1.1.1实现步骤
第一步 导入 AOP 相关坐标
<!--导入spring的context坐标,context依赖aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
第二步 创建目标接口和目标类(内部有切点)
/*
目标接口
*/
public interface TargetInterface {
public void method();
}
@Component
public class Terget implements TargetInterface {
@Override
public void method() {
System.out.println("Target 正在运行中....");
}
}
第三步 创建切面类(内部有增强方法)
public class MyAspect {
//前置增强方法
public void before(){
System.out.println("前置代码增强.....");
}
}
导入aop命名空间
第四步 在 applicationContext.xml 中配置织入关系
配置切点表达式和前置增强的织入关系
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<!--配置Target的method方法执行时要进行myAspect的before方法前置增强-->
<aop:before method="before" pointcut="execution(public void com.by.aop.Target.method())"></aop:before>
</aop:aspect>
</aop:config>
<!--优化代码-->
<aop:config>
<aop:aspect ref="myAspect">
<aop:pointcut id="mypointcut" expression="execution(public void com.by.aop.Target.method())"/>
<aop:before method="before" pointcut-ref="mypointcut"></aop:before>
</aop:aspect>
</aop:config>
第六步 测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.method();
}
}
1.2 XML 配置 AOP 详解
1.2.1 切点表达式的写法
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
-
访问修饰符可以省略
-
返回值类型、包名、类名、方法名可以使用星号* 代表任意
-
包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
-
参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表
例如:
execution(public void com.by.aop.Target.method())
execution(void com.by.aop.Target.*(..))
execution(* com.by.aop.*.*(..))
execution(* com.by..*.*(..))
execution(* *..*.*(..))
1.2.2.通知的类型
通知的配置语法:
<aop:通知类型 method=“切面类中方法名” pointcut=“切点表达式"></aop:通知类型>
通知执行顺序
1.2.3 切点表达式的抽取
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<aop:pointcut id="myPointcut" expression="execution(* com.by.aop.*.*(..))"/>
<aop:before method="before" pointcut-ref="myPointcut"></aop:before>
</aop:aspect>
</aop:config>
1.3 知识要点
-
aop织入的配置
<aop:config>
<aop:aspect ref=“切面类”>
<aop:before method=“通知方法名称” pointcut=“切点表达式"></aop:before>
</aop:aspect>
</aop:config>
-
通知的类型:前置通知、后置通知、环绕通知、异常抛出通知、最终通知
-
切点表达式的写法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
2.基于注解的 AOP 开发
2.1 AOP案例思路分析
-
案例设定:测定接口执行效率
-
简化设定:在接口执行前输出当前系统时间
-
开发模式:XML or ==注解==
-
思路分析:
-
导入坐标(pom.xml)
-
制作连接点方法(原始操作,dao接口与实现类)
-
制作共性功能 (通知类与通知)
-
定义切入点
-
绑定切入点与通知关系(切面)
-
2.2 AOP入门案例实现
【第二步】定义dao接口与实现类
public interface BookDao {
public void save();
public void update();
}
@Repository
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println(System.currentTimeMillis());
System.out.println("book dao save ...");
}
@Override
public void update() {
System.out.println("book dao update ...");
}
}
【第三步】定义通知类,制作通知方法
//通知类必须配置成Spring管理的bean
@Component
public class MyAdvice {
public void method(){
System.out.println(System.currentTimeMillis());
}
}
【第四步】定义切入点表达式、配置切面(绑定切入点与通知关系)
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
//设置切入点,@Pointcut注解要求配置在方法上方
@Pointcut("execution(void com.by.dao.BookDao.save())")
public void pt(){
}
//设置在切入点pt()的前面运行当前操作(前置通知)
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
【第五步】在配置类中进行Spring注解包扫描和开启AOP功能
@Configuration
@ComponentScan("com.by")
//开启注解开发AOP功能
@EnableAspectJAutoProxy
public class SpringConfig {
}
测试类和运行结果
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.update();
}
}
3. 注解+配置文件结合案例
基于注解+配置的aop开发步骤:
①创建目标接口和目标类(内部有切点)
②创建切面类(内部有增强方法)
③将目标类和切面类的对象创建权交给 spring
④在切面类中使用注解配置织入关系
⑤在配置文件中开启组件扫描和 AOP 的自动代理
⑥测试
3.1 AOP入门案例实现
第一步 创建目标接口和目标类(内部有切点)
public interface TargetInterface {
public void method();
}
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
}
}
第二步 创建切面类(内部有增强方法)
public class MyAspect {
//前置增强方法
public void before(){
System.out.println("前置代码增强.....");
}
}
第三步 将目标类和切面类的对象创建权交给 spring
@Component("target")
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
}
}
@Component("myAspect")
public class MyAspect {
public void before(){
System.out.println("前置代码增强.....");
}
}
第四步 在切面类中使用注解配置织入关系
@Component("myAspect")
@Aspect
public class MyAspect {
@Before("execution(* com.by.aop.*.*(..))")
public void before(){
System.out.println("前置代码增强.....");
}
}
第五步 在配置文件中开启组件扫描和 AOP 的自动代理
<!--组件扫描-->
<context:component-scan base-package="com.by.aop"/>
<!--aop的自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
第六步 测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.method();
}
}
4. AOP工作流程【理解】
什么是目标对象?什么是代理对象?
4.1 AOP工作流程
-
Spring容器启动
-
读取所有切面配置中的切入点
-
初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
-
匹配失败,创建原始对象
-
匹配成功,创建原始对象(目标对象)的代理对象
-
-
获取bean执行方法
-
获取的bean是原始对象时,调用方法并执行,完成操作
-
获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
-
4.2 AOP核心概念
目标对象(Target):被代理的对象,也叫原始对象,该对象中的方法没有任何功能增强。
代理对象(Proxy):代理后生成的对象,由Spring帮我们创建代理对象。
4.3 在测试类中验证代理对象
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.update();
//打印对象的类名
System.out.println(bookDao.getClass());
}
}
回顾切入点表达式
5.1 语法格式
-
切入点:要进行增强的方法
-
切入点表达式:要进行增强的方法的描述方式execution(void com.by.dao.BookDao.update())execution(void com.by.dao.impl.BookDaoImpl.update())
-
描述方式一:执行com.by.dao包下的BookDao接口中的无参数update方法
-
描述方式二:执行com.by.dao.impl包下的BookDaoImpl类中的无参数update方法
-
-
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)execution(public User com.by.service.UserService.findById(int))
-
动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
-
访问修饰符:public,private等,可以省略
-
返回值:写返回值类型
-
包名:多级包使用点连接
-
类/接口名:
-
方法名:
-
参数:直接写参数的类型,多个类型用逗号隔开
-
异常名:方法定义中抛出指定异常,可以省略
-
5.2 通配符
目的:可以使用通配符描述切入点,快速描述。
-
:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
匹配com.by包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
execution(public * com.by.*.UserService.find*(*))
-
.. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
execution(public User com..UserService.findById(..))
-
+:专用于匹配子类类型
execution(* *..*Service+.*(..))
5.3 书写技巧
-
所有代码按照标准规范开发,否则以下技巧全部失效
-
描述切入点通==常描述接口==,而不描述实现类
-
访问控制修饰符针对接口开发均采用public描述(==可省略访问控制修饰符描述==)
-
返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
-
==包名==书写==尽量不使用..匹配==,效率过低,常用*做单个包描述匹配,或精准匹配
-
==接口名/类名==书写名称与模块相关的==采用*匹配==,例如UserService书写成*Service,绑定业务层接口名
-
==方法名==书写以==动词==进行==精准匹配==,名词采用匹配,例如getById书写成getBy,selectAll书写成selectAll
-
参数规则较为复杂,根据业务方法灵活调整
-
通常==不使用异常==作为==匹配==规则