一、SpringAOP 面向切面
1.定义
AOP(Aspect-Oriented Programming: 面向切面编程):将那些与业务无关,却为业务模块所共同调用的逻辑(例如事务处理、日志管理、权限控制等)封装抽取成一个可重用的模块,这个模块被命名为"切面"(Aspect),便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性;
2.Spring AOP 基于动态代理实现:
○ 如果被代理的对象,已经实现某个接口,则 Spring AOP 会使用 JDK Proxy(反射),基于接口的方式,创建代理对象(JDK动态代理的核心是InvocationHandler接口和Proxy类);
○ 如果被代理的对象,没有实现某个接口,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib,基于继承的方式,生成一个被代理对象的子类来作为代理(Cglib动态代理的核心是MethodInterceptor接口和Enhancer类);
3.AOP通知类型
AOP将抽取出来的共性功能称为通知;通知类型:以通知在上下文中的具体位置作为划分
- 前置通知(Before)
- 返回通知(After-returning)
- 异常通知(After-throwing)
- 后置通知(After)
- 环绕通知(Around)
4.AOP连接点(Join point)
AOP将所有的方法都视为连接点,不管是接口里面的抽象方法,还是实现类里面的重写方法,都是连接点
5.AOP切点(Pointcut)
AOP将可能被抽取共性功能的方法称为切入点。切入点是连接点的子集
6.AOP目标对象(Target):
AOP目标对象(Target)就是挖掉功能的方法对应的类生的对象,这种对象是无法直接完成最终工作的
7.AOP织入(Weaving):
AOP织入(Weaving)就是将挖掉的功能回填的动态过程
8.AOP切面:切点+通知
9.SpringAOP+AspectJ实现步骤
- 添加依赖,aop与aspectj表达式的依赖
- 创建spring的主配置文件,bean内的命名空间要添加aop的
- 创建业务代码并编写日志记录代码(事务管理代码)
- 将业务层与日志记录层注入spring容器
- <aop:config>--aop配置
aop:aspect--aop切面
aop:before--通知内容与通知类型
10.切点表达式配置语法:
execution(修饰符 返回值 包名称.类名称.方法名称(参数列表))
eg:
execution(public void com.apesource.service.ServiceImp.findAll())
①修饰符可以省略代表任意
execution(返回值 包名称.类名称.方法名称(参数列表))
②返回值可以使用"*"代表任意
execution(* 包名称.类名称.方法名称(参数列表))
③包名可以使用"*"代表任意名称
execution(* *.*.*.类名称.方法名称(参数列表))
④包名可以使用".."代表任意个数
execution(* *...类名称.方法名称(参数列表))
⑤类名与方法名可以使用"*"代表任意
execution(* *...*.*(参数列表))
⑥参数列表可以使用".."代表任意个数任意类型
execution(* *...*.*(..))
如果有参数
int======>int
String===>java.lang.String
二、使用xml方式实现在每个业务方法之前输出日志
案例:
1.坐标
java
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
2.IAccountService接口
java
public interface IAccountService {
//新增
public void save(int i);
//修改
public void update();
//删除
public void delete();
}
3.AccountServiceImp实现类
java
public class AccountServiceImp implements IAccountService{
public void save(int i) {
System.out.println("业务层的新增方法"+i);
}
public void update() {
System.out.println("业务层的修改方法");
//int a=10/0;
}
public void delete() {
System.out.println("业务层的删除方法");
}
}
4.创建一个util工具类包用来写Logger类
定义了一个名为Logger
的类,它包含了一系列用于日志记录的方法,这些方法可以在方法执行的不同阶段(如前置、返回、异常、后置)进行日志记录。此外,它还包含一个特殊的方法arroundMethod
,这是一个环绕通知的实现,用于在目标方法执行前后进行日志记录,并处理异常
java
public class Logger {
public void beforeMethod() {
System.out.println("日志类Logger中调用beforeMethod方法进行日志记录");
}
public void returnMethod() {
System.out.println("日志类Logger中调用returnMethod方法进行日志记录");
}
public void throwMethod() {
System.out.println("日志类Logger中调用throwMethod方法进行日志记录");
}
public void afterMethod() {
System.out.println("日志类Logger中调用afterMethod方法进行日志记录");
}
public Object arroundMethod(ProceedingJoinPoint pjp) {
Object obj = null;
try {
System.out.println("环绕通知===前置通知");
//切点方法
Object[] ars = pjp.getArgs();
obj = pjp.proceed(ars);
System.out.println("环绕通知===返回通知");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("环绕通知===异常通知");
} finally {
System.out.println("环绕通知===后置通知");
return obj;
}
}
}
5.applicationContext.xml
java
<!--注入业务层-->
<bean id="accountServiceImp" class="com.ztt.service.AccountServiceImp"></bean>
<!--注入日志记录层(通知)-->
<bean id="logger" class="com.ztt.util.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="aopAspect" ref="logger">
<!--切点-->
<aop:pointcut id="dian" expression="execution(* com.ztt.service.*.*(..))"/>
<!--通知-->
<!-- <aop:before method="beforeMethod" pointcut-ref="dian"></aop:before>-->
<!-- <aop:after-returning method="returnMethod" pointcut-ref="dian"></aop:after-returning>-->
<!-- <aop:after-throwing method="throwMethod" pointcut-ref="dian"></aop:after-throwing>-->
<!-- <aop:after method="afterMethod" pointcut-ref="dian"></aop:after>-->
<aop:around method="arroundMethod" pointcut-ref="dian"></aop:around>
</aop:aspect>
</aop:config>
</beans>
6.测试类
java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Test01 {
@Autowired
public IAccountService service;
@Test
public void show1(){
service.update();
System.out.println("==============");
// service.save(1);
// System.out.println("==============");
// service.update();
}
}
三、使用注解方式实现在每个业务方法之前输出日志
1.IAccountService接口
java
public interface IAccountService {
//新增
public void save(int i);
//修改
public void update();
//删除
public void delete();
}
2.AccountServiceImp接口实现类
java
@Service
public class AccountServiceImp implements IAccountService{
public void save(int i) {
System.out.println("业务层的新增方法"+i);
}
public void update() {
System.out.println("业务层的修改方法");
int a=10/0;
}
public void delete() {
System.out.println("业务层的删除方法");
}
}
3.Logger类
java
/**
* 日志记录
*/
@Component
@Aspect//切面
public class Logger {
@Pointcut("execution(* com.ztt.service.AccountServiceImp.update())")
public void dian() {
}
//前置通知
@Before("dian()")
public void BeforeLogger() {
System.out.println("前置通知==>日志类logger中调用BeforeLogger方法进行日志记录");
}
//返回通知
@AfterReturning("dian()")
public void AfterReturningLogger() {
System.out.println("返回通知==>日志类logger中调用AfterReturningLogger方法进行日志记录");
}
//异常通知
@AfterThrowing("dian()")
public void AfterThrowingLogger() {
System.out.println("异常通知==>日志类logger中调用AfterThrowingLogger方法进行日志记录");
}
//后置通知
@After("dian()")
public void AfterLogger() {
System.out.println("后置通知==>日志类logger中调用AfterLogger方法进行日志记录");
}
//环绕通知
//@Around("dian()")
public Object AroundLogger(ProceedingJoinPoint pjp) {
Object returnobj = null;//保存主业务方法的返回值
try {
//1.前置通知
System.out.println("环绕通知===》前置通知");
Object[] objs = pjp.getArgs();//主业务方法的参数
returnobj = pjp.proceed(objs);//调用主业务方法
//3.后置通知
System.out.println("环绕通知===》后置通知");
} catch (Throwable tw) {
//4.异常通知
System.out.println("环绕通知===》异常通知");
} finally {
//5.最终通知
System.out.println("环绕通知===》最终通知");
}
return returnobj;
}
}
4.applicationContext.xml
java
<!--扫描注入-->
<context:component-scan base-package="com.ztt"></context:component-scan>
<!--开启spring注解的aop动态代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
5.测试类
java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Test01 {
@Autowired
public IAccountService service;
@Test
public void show1(){
service.update();
System.out.println("===================");
}
}
四、使用配置类方式实现在每个业务方法之前输出日志
在注解方式的基础上使用配置类替换掉配置文件applicationContext.xml
1.ApplicationConfig类
java
@Configuration
@ComponentScan(basePackages = "com.ztt")
@EnableAspectJAutoProxy
public class ApplicationConfig {
}