1.SpringAop是什么
SpringAOP的全称是(Aspect Oriented Programming)中文翻译过来是面向切面编程。理)完成的。
AOP中关键性概念
连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出.
目标(Target):被通知(被代理)的对象
注1:完成具体的业务逻辑
通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理)
注2:完成切面编程
代理(Proxy):将通知(公共部分的代码,日志记录)应用到目标(具体的业务逻辑方法)对象后创建的对象(代理=目标+通知),
注3:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的
切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点。
(也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)
适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)
如何实现aop?
1.aop:即面向切面编程
2.aop带来的好处:"专心做事"
3. 封装到AOP的通知中:
// 记录日志
log.info("调用 doSameBusiness方法,参数是:"+lParam);
当doSameBusiness被调用时(连接点),将通知(日志记录)应用到doSameBusiness(目标)
通知+目标=代理对象,只有完整的代理对象才具备AOP的特性
如果只有目标,则只会去做核心业务逻辑处理,不会收集日志;
如果只有通知,则只会去做日志记录,不会执行核心业务逻辑;
通知应用到目标上才能得到代理对象(通知+目标=代理)
只有完整的代理对象才具备AOP的特性,而AOP的公共代码(日志收集)是写在通知中的!!
这里我们用实例来演示一下aop:
我们先创建好我们的项目
1.1导入好我们的相关依赖
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
注:如果我们的依赖有很多版本一致的,可以这样做
后面我们在下面需要写上版本的引用此处就行(${spring.version})
1.2创建我们所需要用到的包
1.3.把我们需要用到的业务逻辑放入biz层中
package example1;
public interface IBookBiz {
// 购书
public boolean buy(String userName, String bookName, Double price);
// 发表书评
public void comment(String userName, String comments);
}
package example1;
public class BookBizImpl implements IBookBiz {
public BookBizImpl() {
super();
}
public boolean buy(String userName, String bookName, Double price) {
// 通过控制台的输出方式模拟购书
//这个if判断copy进来会报错 我们只需注释掉就行了
if (null == price || price <= 0) {
throw new PriceException("book price exception");
}
System.out.println(userName + " buy " + bookName + ", spend " + price);
return true;
}
public void comment(String userName, String comments) {
// 通过控制台的输出方式模拟发表书评
System.out.println(userName + " say:" + comments);
}
}
1.4前置通知
package com.zking.spring.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import java.util.Arrays;
public class BeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
String methodName = method.getName();//方法名
String className = o.getClass().getName();//获取类的全限定名
System.out.println("[前置通知]:"+className+"被调用方法名为:"+methodName+",方法参数为="+ Arrays.toString(objects));
}
}
1.5配置spring.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--目标-->
<bean id="bookBizImpl" class="com.zking.spring.biz.BookBizImpl">
</bean>
<!--通知-->
<!--前置通知-->
<bean id="beforeAdvice" class="com.zking.spring.advice.BeforeAdvice"></bean>
<!--代理 代理=目标+通知-->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--目标-->
<property name="target">
<ref bean="bookBizImpl"/>
</property>
<!--通知-->
<property name="interceptorNames">
<list>
<!--前置通知-->
<value>beforeAdvice</value>
</list>
</property>
<!--目标+通知-->
<property name="proxyInterfaces">
<list>
<!--目标实现的接口 代理也要实现-->
<value>com.zking.spring.biz.IBookBiz</value>
</list>
</property>
</bean>
</beans>
1.6 然后我们在输出之前的方法
package com.zking.spring.demo;
import com.zking.spring.biz.BookBizImpl;
import com.zking.spring.biz.IBookBiz;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo {
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
IBookBiz ibookBiz = (IBookBiz) ac.getBean("proxy");//在这里如果不进行强转用object来接收,拿接收的就是代理对象
ibookBiz.buy("小明","西游记",8888d);
}
}
输出结果:
可以看到我们之前配置的前置通知起效果了,可以看到打印了类的全限定名、方法名、和传入的方法参数 ,这证明了我们的前置通知成功了
当然有前置通知那肯定也有后置通知,前后置通知(环绕通知),我们就快速过一下,都跟前置通知差不多,
后置通知
package com.zking.spring.advice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("[后置通知]由于你买了书,这里给你返利3元");
}
}
环绕通知:
package com.zking.spring.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.util.Arrays;
public class InvoAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
String methodName = methodInvocation.getMethod().getName();//方法名
String className = methodInvocation.getThis().getClass().getName();//全限定名
Object[] args = methodInvocation.getArguments();
//前置部分
System.out.println("[环绕通知前置部分]:"+className+"被调用方法名为:"+methodName+",方法参数为="+ Arrays.toString(args));
//放行 执行后置部分
//后置部分
Object returnValue = methodInvocation.proceed();
System.out.println("[环绕通知后置部分]:"+className+"被调用方法名为:"+methodName+",方法返回值="+ returnValue);
return returnValue;
}
}
spring.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--目标-->
<bean id="bookBizImpl" class="com.zking.spring.biz.BookBizImpl">
</bean>
<!--通知-->
<bean id="beforeAdvice" class="com.zking.spring.advice.BeforeAdvice"></bean>
<bean id="afterAdvice" class="com.zking.spring.advice.AfterAdvice"></bean>
<bean id="invoAdvice" class="com.zking.spring.advice.InvoAdvice"></bean>
<!--代理 代理=目标+通知-->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--目标-->
<property name="target">
<ref bean="bookBizImpl"/>
</property>
<!--通知-->
<property name="interceptorNames">
<list>
<!--前置通知-->
<value>beforeAdvice</value>
<!-- 后置通知-->
<value>afterAdvice</value>
<!--环绕通知-->
<value>invoAdvice</value>
</list>
</property>
<!--目标+通知-->
<property name="proxyInterfaces">
<list>
<value>com.zking.spring.biz.IBookBiz</value>
</list>
</property>
</bean>
</beans>
我们看看输出结果:
可以看到都是可以显示在控制台的
1.7 异常通知
听名字来看就知道是程序遇到异常后的通知
1.7.1 我们先来自定义一个异常类
package com.zking.spring.ex;
/**
* 自定义异常
*/
public class PriceException extends RuntimeException{
public PriceException() {
super();
}
public PriceException(String message) {
super(message);
}
public PriceException(String message, Throwable cause) {
super(message, cause);
}
public PriceException(Throwable cause) {
super(cause);
}
protected PriceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
1.7.2 定义异常通知
package com.zking.spring.advice;
import com.zking.spring.ex.PriceException;
import org.springframework.aop.ThrowsAdvice;
public class ExAdvice implements ThrowsAdvice {
//需要多个异常提示的话多写几个一样的就行(注:参数的数据类型改一下就好)
public void afterThrowing( PriceException ex ) {
System.out.println("价格异常,请重新输入购买价格");
}
}
1.7.3 异常通知spring.xml配置(跟之前的通知一致)
1.7.4 让程序出现异常
我们把之前在BookBizImpl中的判断注释解开
在把我们的价格设置为负数就可以形成我们的异常通知
1.8 适配器
通过之前的通知我们发现有一个bug 就是好评返现3元,应该在评价后才返现,而不是调用买书的方法时也返现,这个时候我们就能用适配器来定义我们的切入点
适配器我们只需要在spring.xml中配置就行了
步骤1.定义适配器
<!--适配器 适配器=通知加切入点-->
<bean id="regexp" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--通知-->
<property name="advice">
<ref bean="afterAdvice"/>
</property>
<!--切入点 很多连接点的一个集合-->
<property name="pattern">
<!--以comment结尾一个方法-->
<value>.*comment</value>
</property>
</bean>
步骤2:将适配器id加入通知
<!--代理 代理=目标+通知-->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--目标-->
<property name="target">
<ref bean="bookBizImpl"/>
</property>
<!--通知-->
<property name="interceptorNames">
<list>
<!--前置通知-->
<!-- <value>beforeAdvice</value>-->
<!-- 后置通知-->
<!-- <value>afterAdvice</value>-->
<!--环绕通知-->
<!-- <value>invoAdvice</value>-->
<!--异常通知-->
<!-- <value>exAdvice</value>-->
<!--适配器-->
<value>regexp</value>
</list>
</property>
<!--目标+通知-->
<property name="proxyInterfaces">
<list>
<value>com.zking.spring.biz.IBookBiz</value>
</list>
</property>
</bean>
我们在来看看运行结果
可以看到只有评价才会返现了,这样我们的适配器也配置好了
2.SpringAop的应用场景
日志记录
权限验证(SpringSecurity有使用)
事务控制(调用方法前开启事务, 调用方法后提交关闭事务 )
效率检查(检测方法运行时间)
数据源代理(seata里面,获取到数据源连接执行的sql)
缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
3.SpringBean的生命周期
我们先来看图
我们在来一一解释
Spring的IOC和AOP:
//初始化Spring上下文容器(IOC)
ApplicationContext ac=
new ClassXmlPathApplicationContext("spring.xml");
Spring Bean的生命周期:
1)通过XML、Java annotation(注解)以及Java Configuration(配置类)
等方式加载Spring Bean
2)BeanDefinitionReader:解析Bean的定义。在Spring容器启动过程中,
会将Bean解析成Spring内部的BeanDefinition结构;
理解为:将spring.xml中的<bean>标签转换成BeanDefinition结构
有点类似于XML解析
3)BeanDefinition:包含了很多属性和方法。例如:id、class(类名)、
scope、ref(依赖的bean)等等。其实就是将bean(例如<bean>)的定义信息
存储到这个对应BeanDefinition相应的属性中
4)BeanFactoryPostProcessor:是Spring容器功能的扩展接口。
注意:
1)BeanFactoryPostProcessor在spring容器加载完BeanDefinition之后,
在bean实例化之前执行的
2)对bean元数据(BeanDefinition)进行加工处理,也就是BeanDefinition
属性填充、修改等操作
5)BeanFactory:bean工厂。它按照我们的要求生产我们需要的各种各样的bean。
6)Aware感知接口:在实际开发中,经常需要用到Spring容器本身的功能资源
例如:BeanNameAware、ApplicationContextAware等等
BeanDefinition 实现了 BeanNameAware、ApplicationContextAware
7)BeanPostProcessor:后置处理器。在Bean对象实例化和引入注入完毕后,
在显示调用初始化方法的前后添加自定义的逻辑。(类似于AOP的绕环通知)
前提条件:如果检测到Bean对象实现了BeanPostProcessor后置处理器才会执行
Before和After方法
BeanPostProcessor
1)Before
2)调用初始化Bean(InitializingBean和init-method,Bean的初始化才算完成)
3)After
完成了Bean的创建工作
8)destory:销毁
好啦这期就到这里结束啦,下期我们来了解Mybatis