前言
我们学习 spring 的 aop,就是通过配置的方式,实现上一篇的功能。
AOP相关术语
- target(目标对象): 代理的目标对象。
就是我们需要增强的那个类,如LaoZong.class - proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。
就是自定义的代理的对象 $Proxy0.class - joinPoint(连接点):程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。Spring仅支持方法的连接点:即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。
就是LaoZong.class中的eat()、sleep()方法前后。 - pointCut(切入点):就是在目标类中要实际增强的方法
就是要增强的eat()和sleep() - weave(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
就是把MiShu类中的方法放到eat()和sleep()前后的过程。 - 引介Introduction:引介是一种特殊的增强,它为类添加一些属性和方法(课程不使用)
- advice(通知):将代理对象中的方法应用到目标类的过程中产生的结果。
给eat(),sleep()提供加强方法的类,如:MiShu类. - aspect(切面):所有的切入点和代理对象的方法组成在一起 构成了切面。
就是通知和切入点的结合。
Spring AOP的准备
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
</dependencies>
IUser
//1 必须要有一个接口
public interface IUser {
void work();
boolean paly();
}
UserImpl
//2 类与接口是实现关系
public class UserImpl implements IUser {
//连接点
@Override
public void work() {
System.out.println("我加班我快乐");
}
@Override
public boolean play(){
System.out.println("越玩越颓废!");
return true;
}
}
Advice
//通知
public class Advice {
public void writeLog(){
System.out.println("写一个开发备忘录");
}
}
Spring AOP的Xml配置
<?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">
<!--在此进行AOP基于配置-->
<!--1:创建目标类对象:UserImpl-->
<bean id="userImpl" class="cn.cyl.service.impl.UserImpl"/>
<!--2:创建增强对象,即配置通知:Advice-->
<bean id="advice" class="cn.cyl.service.Advice"/>
<!--3:开始基于AOP增强
3.1 我要对UserImpl类的work方法进行后置增强,
在work方法之后先执行Advice类中writeLog方法
-->
<aop:config >
<!--1:配置切入点(配置要增强的方法)
id:表示给要增强的方法切入点进行一个编号
execution:表示哪个类中的方法
-->
<aop:pointcut id="work" expression="execution(public void cn.cyl.service.impl.UserImpl.work())"/>
<!--2:配置切面:增强的方式->在work方法执行之后调用一下writeLog
ref:用来指定增强类
-->
<aop:aspect ref="advice">
<!--配置通知的类型-->
<!--配置增强的细节:要用那个方法去给work增强,是放在work的前面和后边-->
<aop:after method="writeLog" pointcut-ref="work"/>
</aop:aspect>
</aop:config>
</beans>
UserImplTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserImplTest {
@Qualifier("userImpl")
@Autowired
IUser iUser;
@Test
public void work() {
iUser.work();
//xml文件中未增强play()方法
iUser.play();
System.out.println(iUser);
}
}
运行结果
xml文件中未增强paly()方法,所以play()方法执行后不会执行writeLog()方法
我加班我快乐!
写一个开发备忘录
越玩越颓废!
cn.cyl.service.impl.UserImpl@2e1d27ba
AOP切面编程-切面表达式
- (1)什么是切面表达式
execution([修饰符] 返回值类型 包.类.方法(参数列表));
- (2)切面表达式有什么用?
符合表达式的方法,会被增强
使用* 表示任意的内容
使用..
可以表示包与子包下面的类
使用..
可以写在方法(…)表示任意参数
<aop:config>
<!--
切面表达式:
execution([修饰符] 返回值类型 包.类.方法(参数列表) );
1:完全写法
execution(public void cn.cyl.service.impl.UserImpl.work(int) );
2:修饰符省略
execution( void cn.cyl.service.impl.UserImpl.work(int) );
3:简化2-返回值类型写通配符
execution(* cn.cyl.service.impl.UserImpl.work(int) );
4:简化3-包名写通配符
execution(* *.*.*.UserImpl.work(int) );
5:简化4-包名通配符简化
execution(* *..UserImpl.work(int) );
6:简化5-类名通配符
execution(* *..*.work(int) );
7:简化6-参数写通配符
execution(* *..*.work(..) );
-->
<!--
表示UserImpl类中的所有带参数和不带参数的方法
<aop:pointcut id="mthods" expression="execution(* *..UserImpl.*(..))"/>
-->
<!--表示UserImpl类中的所有不带参数的方法-->
<aop:pointcut id="mthods" expression="execution(* *..UserImpl.*())"/>
<aop:aspect ref="advice">
<aop:after method="writeLog" pointcut-ref="work"/>
</aop:aspect>
</aop:config>
再次执行UserImplTest
运行结果
我加班我快乐!
写一个开发备忘录
越玩越颓废!
写一个开发备忘录
cn.cyl.service.impl.UserImpl@7e9131d5
可以发现play()方法被增强了。
AOP切面编程-增强方式
- (1)增强方式的方式有哪些?
实质是增强的四个可选调用位置 - (2)每个位置有对应的名称
- 示例
IUser
public interface IUser {
void add(User user);
void deleteById(Integer id);
}
UserImpl
public class UserImpl implements IUser {
@Override
public void add(User user) {
System.out.println("add ...");
}
@Override
public void deleteById(Integer id) {
System.out.println("deleteById ...");
//除0异常
System.out.println(1/0);
}
Advice
public class Advice {
//方法之前执行
public void before(){
System.out.println("before————前面记录日志");
}
//程序正常执行后执行,出现异常不执行
public void afterReturning(){
System.out.println("afterReturning————后面记录日志");
}
//出现异常执行
public void afterThrow(){
System.out.println("afterThrow————异常记录日志");
}
//最终增强,最终都会执行
public void after(){
System.out.println("after————最终记录日志");
}
}
applicationContext.xml
<aop:config>
<aop:pointcut id="all" expression="execution(* cn.cyl.service..*.*(..))"/>
<aop:aspect ref="advice">
<!--try-->
<!--前置增强,方法之前执行-->
<aop:before method="before" pointcut-ref="all"/>
<!--后置增强,程序正常运行返回,出现异常不执行-->
<aop:after-returning method="afterReturning" pointcut-ref="all"/>
<!--catch-->
<!--异常增强,出现异常执行-->
<aop:after-throwing method="afterThrow" pointcut-ref="all"/>
<!--finally-->
<!--最终增强,最终都会执行-->
<aop:after method="after" pointcut-ref="all"/>
</aop:aspect>
</aop:config>
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserImplTest {
@Autowired
IUser iUser;
@Test
public void work1() {
//没有异常,正常运行
iUser.add(null);
}
@Test
public void work2(){
//有异常
iUser.deleteById(1);
}
}
执行结果
work1()
work2()
- (3)arround环绕
简化上面的代码
重点:
一个参数:ProceedingJoinPoint joinPoint
:连接点,也就是目标类中所有方法。
一个方法:joinPoint.proceed();
//切到原来的目标方法,进行执行。等价于动态代理 obj = method.invoke(finalPerson, objects); - 示例
Advice
public class Advice {
//环绕增强
//参数:目标类中的所有方法
public void around(ProceedingJoinPoint joinPoint){
try {
//执行before
before();
System.out.println("around————");
joinPoint.proceed();
afterReturning();
} catch (Throwable throwable) {
//补救方法
afterThrow();
throwable.printStackTrace();
}finally {
//释放资源
after();
}
}
}
applicationContext.xml
<aop:config>
<aop:pointcut id="all" expression="execution(* cn.cyl.service..*.*(..))"/>
<aop:aspect ref="advice">
<!--环绕增强-->
<aop:around method="around" pointcut-ref="all"/>
</aop:aspect>
</aop:config>
执行结果
work01
work02
AOP切面编程-注解***
- (1)spring注解配置
一个xml开启注解自动代理
几个注解:
- 切面@Aspect+四个注解 @Before @AfterReturning @AfterThrowing@After
- 切面@Aspect+@Arround
- (2)示例
在配置文件中导入 context 的名称空间
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--包扫描创建对象,扫描包与它下面的子包的所有类-->
<context:component-scan base-package="cn.cyl.service"/>
<!--开启自动AOP代理-->
<!--声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。-->
<aop:aspectj-autoproxy/>
</beans>
资源使用注解配置
UserImpl
@Component
public class UserImpl implements IUser {
@Override
public void add(User user) {
System.out.println("add ...");
}
@Override
public void deleteById(Integer id) {
System.out.println("deleteById ...");
//除0异常
System.out.println(1/0);
}
在通知类上使用@Aspect 注解声明为切面
@Component
@Aspect //让本类作为一个切面
public class Advice {
//方法之前执行
//try{}
// @Before("execution(* cn.cyl.service..*.*(..))")
public void before(){
System.out.println("before————前面记录日志");
}
//出现异常不执行
// @AfterReturning("execution(* cn.cyl.service..*.*(..))")
public void afterReturning(){
System.out.println("afterReturning————后面记录日志");
}
//catch
//出现异常执行
// @AfterThrowing("execution(* cn.cyl.service..*.*(..))")
public void throwable(){
System.out.println("throwable————异常记录日志");
}
//最终增强,最终都会执行
// @After("execution(* cn.cyl.service..*.*(..))")
public void after(){
System.out.println("after————最终记录日志");
}
//环绕增强
//参数:目标类中的所有方法
@Around("execution(* cn.cyl.service..*.*(..))")
public void around(ProceedingJoinPoint point){
try {
//执行before
before();
System.out.println("around————");
point.proceed();
afterReturning();
} catch (Throwable throwable) {
//补救方法
throwable();
throwable.printStackTrace();
}finally {
//释放资源
after();
}
}
}