1.什么是面向切面编程
AspectOrientedProgramming(AOP),面向切面编程,主要面对的处理过程中的某个步骤或阶段,已获得逻辑过程中各部分之间低耦合性的隔离效果。
(即是把某个事物在某个方面的功能提取出来与一批对象进行隔离,这样就与一批对象之间的耦合性降低了,可以就某个功能进行编程)
2.AOP中的面向切面编程
(1)Join point(连接点):程序执行期间的某一个点,例如执行方法或处理异常时候的点,连接点便是方法的执行。
(2)Adivice(通知):指一个切面在特定时间要做的事情。
(3)Aspect(切面):它是一个跨越多个类的模块化的关注点,它是通知(Advice)和切点(Pointcut)合起来的抽象,它定义了一个切点(Pointcut)用来匹配连接点(Join point),也就是需要对需要拦截的那些方法进行定义;它定义了一系列的通知(Advice)用来对拦截到的方法进行增强;(是切入点的集合)
(4)Target object(目标对象):被一个或者多个切面(Aspect)通知的对象,也就是需要被 AOP 进行拦截对方法进行增强(使用通知)的对象,也称为被通知的对象。
(5)AOP Proxy(AOP代理):为了实现切面(Aspect)功能使用 AOP 框架创建一个对象,在 Spring 框架里面一个 AOP 代理要么指 JDK 动态代理,要么指 CgLIB 代理。
(6)Weaving(织入):是将切面应用到目标对象的过程,这个过程可以是在编译时(例如使用 AspectJ 编译器),类加载时,运行时完成。
(7)Joinpoint(切入点):描述拦截的术语。
(8)Introduction(引介):动态添加一些方法,放在拦截目标前后执行。
()
示例
下面定义一个接口,定义了几个假设的功能(因为动态代理是根据接口生成的。所以功能类一定要定义一个接口)
package edu.xalead.AOP;
public interface PersonInterface {
void lvyou();
void shangban();
void tanqin();
}
实现接口
package edu.xalead.AOP;
public class Person implements PersonInterface {
public void lvyou(){
System.out.println("旅游");
}
public void shangban(){
System.out.println("上班");
}
public void tanqin(){
System.out.println("探亲");
}
}
假设一个服务类要调用上面的功能
package edu.xalead.AOP;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component//将依赖注入到一起
public class PersonService1 {
@Resource//从容器中自动获取bean
private Person1 person1;
public void service(){
person1.lvyou();
person1.shangban();
person1.tanqin();
}
}
测试类
package edu.xalead.AOP;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test1 {
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
PersonService1 personService1 = factory.getBean(PersonService1.class);
personService1.service();
}
}
输出结果:
我们在旅游、上班、探亲途中都需要做一些共同的事情,比如:穿衣服、吃早饭等,在此我们就可以用面向切面的方式将穿衣服、迟早饭等通用功能抽取出来
下面创建一个通用功能的类
package edu.xalead.AOP;
import org.springframework.stereotype.Component;
@Component("www")
public class Server {
public void chuanyifu(){
System.out.println("穿衣服");
}
public void chizaofan(){
System.out.println("吃早饭");
}
}
输出结果:
3.Spring_AOP配置
实例
(1)添加maven包
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
(2)定义一个接口
package edu.xalead;
public interface MyThing {
public void lvyou();
}
(3)写一个实现类,实现刚才的接口
(就是我们所要拦截的方法)
package edu.xalead;
import org.springframework.stereotype.Component;
//target
@Component("myThing")
public class MyThingImpl implements MyThing {
//JoinPoint(切入点 要拦截的方法)
public void lvyou() {
System.out.println("旅游");
}
}
(4)写一个引介(就是我们在在旅游前后要做的事情)
package edu.xalead;
import org.springframework.stereotype.Component;
/**
* 引介(要做的事情)
*/
@Component
public class CommonThing {
public void qichuang(){
System.out.println("起床");
}
public void chifan(){
System.out.println("吃饭");
}
public void chuanyi(){
System.out.println("穿衣");
}
public void tuoyi(){
System.out.println("脱衣");
}
public void shuijiao(){
System.out.println("睡觉");
}
}
(5)织入(将引介与目标对象织入到一起)
切面配置
注意:一定要将引介和目标对象利用@Component注解将它们放进bean工厂里面,才能织入
<aop:config>
<!--要拦截的类-->
<aop:aspect ref="commonThing">
<!--配置切入点(以旅游为切入点)-->
<!--第一个* 表示任意返回值类型
第二个* 表示以任意名字开头的package. 如 com.xx.
第三个* 表示以任意名字开头的class的类名 如TestService
第四个* 表示 通配 *service下的任意class
最后二个.. 表示通配 方法可以有0个或多个参数-->
<aop:pointcut id="aa" expression="execution(* edu.xalead.MyThingImpl.*())"/>
<!--配置前置通知(在切入点之前拦截-->
<aop:before method="qichuang" pointcut-ref="aa"/>
<aop:before method="chuanyi" pointcut-ref="aa"/>
<aop:before method="chifan" pointcut-ref="aa"/>
<!--配置后置通知(在切入点之后拦截)-->
<!--after-returning在after后面执行-->
<aop:after-returning method="shuijiao" pointcut-ref="aa"/>
<aop:after method="tuoyi" pointcut-ref="aa"/>
</aop:aspect>
</aop:config>
(6)测试
package edu.xalead;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
MyThing myThing = (MyThing) factory.getBean("myThing");
myThing.lvyou();
}
}
4.Spring通知类型
<aop:pointcut id="aa" expression="execution(* edu.xalead.MyThingImpl.*())"/>
(1)前置通知(before :在目标方法执行前的通知)
<aop:before method="qichuang" pointcut-ref="aa"/>
(2)后置通知(after-returning:在目标方法执行后通知)
<aop:after-returning method="shuijiao" pointcut-ref="aa"/>
(3)最终通知(after:在目标方法执行后执行的通知)
注意:最终通=通知与后置通知的不同之处在于,后置通知是在方法正常返回后执行的通知,如果方法没有正常返回(例如抛出异常),则后置通知不会执行
<aop:after method="tuoyi" pointcut-ref="aa"/>
(4)异常通知
<aop:after-throwing method="yiwai" pointcut-ref="aa" throwing = "e" />
引介方法
public void yiwai(Exception e){
System.out.println("出现意外" + e.getMessage());
}
拦截的方法(代码中加入抛出异常时候的代码,异常抛出的时候执行)
if(1 == 1){
throw new RuntimeException("意外");
}
(5)环绕通知(在切入点前后都要执行的方法)
<aop:around method="around" pointcut-ref="aa"/>
引介方法
public void around(ProceedingJoinPoint joinPoint){
//调用方法之前要做的事
heshui();
try {
joinPoint.proceed();//真正调用的方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//调用方法之后要做的事
shuaya();
}
五种通知执行的顺序
(1)在目标方法没有抛出异常的情况下
前置通知
环绕通知的调用目标方法之前的代码
目标方法
环绕通知的调用目标方法之后的代码
后置通知
最终通知
(2)在目标方法抛出异常的情况下
前置通知
环绕通知的调用目标方法之前的代码
目标方法 抛出异常 异常通知
最终通知
5.基于注解配置AOP
直接在引介的方法的类中去配
配置方法:
(1)首先要在applicationContext.xml里面加aop:aspectj-autoproxy/配置 启用aspectj自动代理
<aop:aspectj-autoproxy/>
(2)用@Aspect注解来配置切面
(3)利用 @Pointcut注解配置切入点
@Pointcut("execution(* edu.xalead.MyThingImpl.*())")
private void aaa(){
}
完整代码
package edu.xalead;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 引介(要做的事情)
*/
@Component
@Aspect
public class CommonThing {
@Pointcut("execution(* edu.xalead.MyThingImpl.*())")
private void aaa(){
}
@Before("aaa()")
public void qichuang(){
System.out.println("起床");
}
@Before("aaa()")
public void chifan(){
System.out.println("吃饭");
}
@Before("aaa()")
public void chuanyi(){
System.out.println("穿衣");
}
@After("aaa()")
public void shuijiao(){
System.out.println("睡觉");
}
@AfterReturning("aaa()")
public void tuoyi(){
System.out.println("脱衣");
}
public void heshui(){
System.out.println("喝水");
}
public void shuaya(){
System.out.println("刷牙");
}
@AfterThrowing(value = "aaa()",throwing = "e")
public void yiwai(Exception e){
System.out.println("出现意外" + e.getMessage());
}
@Around("aaa()")
public void around(ProceedingJoinPoint joinPoint){
//调用方法之前要做的事
heshui();
try {
joinPoint.proceed();//真正调用的方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//调用方法之后要做的事
shuaya();
}
}
6.用配置类代替xml文件(applicationContext.xml)
利用@Configuration注解 声明配置类(创建工厂的时候指定的配置类可以省略这个注解)
创建配置类
package edu.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.*;
import javax.sql.DataSource;
import java.nio.channels.Channel;
import java.sql.Connection;
//@Configuration
public class SpringConfig {
private Connection conn = null;
private String driverClass = "com.mysql.jdbc.Driver";
private String url = "jdbc:mysql://localhost:3306/cms";
private String username = "root";
private String password = "root";
@Bean("druidDataSource")//把 druidDataSource()方法返回的对象放入bean工厂
@Scope("prototype")
public DruidDataSource druidDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setUrl(url);
ds.setDriverClassName(driverClass);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
测试类
import edu.config.SpringConfig;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import javax.sql.DataSource;
public class Test1 {
@Test
public void test1(){
BeanFactory factory = new AnnotationConfigApplicationContext(SpringConfig.class);
DataSource ds = (DataSource) factory.getBean("druidDataSource");
}
}
几个注解
(1)@ComponetScan注解 它可以代替xml配置中的
<context:component-scan base-package="edu.xalead"/>
(2)@EnableAspectJAutoProxy当配置了组件扫描之后,当扫描到aspectj的注解的时候,自动创建aspectj代理,来启动AOP
代替xml配置中的aop:aspectj-autoproxy/>
注意,如果有多个配置类,其它的配置类要标注@Configuration,并保证组件扫描时要可以扫描到这些配置类,这些配置类才可以起作用
package edu.config;
import org.springframework.context.annotation.*;
/**
* 创建配置类
*/
//@Configuration
//组件扫描 配置包扫描(将有component的注解实例化出来放进工厂)
@ComponentScan(basePackages = "edu")
//开启Aop
@EnableAspectJAutoProxy
//之间将配置类导入进来
//@Import(OtherConfig.class)
public class SpringConfig {
}
(3)@Import注解
如果其它的配置类也想省略@Configuration注解,则必须在工厂默认使用的配置类中使用@Import注解把配置类引用进来
package edu.config;
import org.springframework.context.annotation.*;
/**
* 创建配置类
*/
//@Configuration
//组件扫描 配置包扫描(将有component的注解实例化出来放进工厂)
@ComponentScan(basePackages = "edu")
//开启Aop
@EnableAspectJAutoProxy
//之间将配置类导入进来
@Import(OtherConfig.class)
public class SpringConfig {
}