spring的AOP实现(未使用注解方式)
- 1.引入:
通过上一篇文章,我们已经知道了关于代理设计模式来实现AOP的方式,其实昨天所说的东西在开发中并不会这样去使用,所以今天我们来学习怎么通过spring来实现aop,但是spring的底层也是基于上一篇文章里所说的方式来实现的
- 2.spring AOP中的基本概念(这里就用通俗的话来解释):
连接点:
比如web层调用service层里的方法,这一个过程就叫连接点。
切入规则和切入点:
通过切入规则,筛选出来的连接点,就叫切入点。也就是筛选出哪些方法需要被代理。
切面:
就是一个类,里面有一些特殊的方法用来处理spring拦截下来的切入点,对切入点进行处理。
通知:
spring拦截下来的切入点会交由切入面的特殊方法(通知)来进行处理。
目标对象:
真正被调用完成操作的对象,可以理解为被代理对象,至于它到底是不是被代理对象,就看它符合不符合切入规则,是不是切入点。
关于spring的aop,spring很聪明的给大家准备了自动的代理对象,这个代理对象就不需要我们自己去创建,之前我们的做法是自己写一个代理类,类中再去声明被代理者,而这一次,代理对象已经由spring来完成,我们需要做的,仅仅是筛选切入点,写切入面即可。如下图:
**
- 3.通过xml配置文件实现Spring AOP
**
a.首先自己写一个切面类
b.在切面类中定义通知
c.在xml文件里配置切面类如下图:
(关于切入点表达式和通知类型,后面做讲解)
- 4.切入点表达式
a. within()表达式:该表达式是粗粒度的,只能筛选到within(包名.类名),类中的所有连接点(也就是方法),都会变成切入点。
b. execution()表达式:细粒度的切入点表达式,可以以方法为单位定义切入点规则,execution(返回值类型 包名.类名.方法名(参数类型,参数类型…))。
切入点详细的规则这里我不想做阐述了,想看自己去找,有很多的。
- 5.Spring的五大通知类型
前置通知:
在执行目标方法之前执行的通知。前置通知方法,可以没有参数,也可以额外接收一个JoinPoint,Spring会自动将该对象传入,代表当前的连接点,通过该对象可以获取目标对象 和 目标方法相关的信息。
(注意:如果接收JoinPoint,必须保证其为方法的第一个参数,否则报错)
环绕通知:
执行目标方法之前,以及目标方法执行完成之后。需要注意的是,环绕通知中的目标方法需要手动执行,通过ProceedingJoinPoint来实现的,可以在环绕通知中接收一个此类型的形参,spring容器会自动将该对象传入,这个参数必须处在环绕通知的第一个形参位置。
后置通知:
在目标方法执行之后成功执行的通知。在后置通知中也可以选择性的接收一个JoinPoint来获取连接点的额外信息,但是这个参数必须处在参数列表的第一个。
后置通知中,还可以通过配置获取目标方法的返回值。
异常通知:
在目标方法抛出异常时执行的通知
最终通知:
是在目标方法执行之后执行的通知。和后置通知不同之处在于,后置通知是在方法正常返回后执行的通知,如果方法没有正常返-例如抛出异常,则后置通知不会执行。而最终通知无论如何都会在目标方法调用过后执行,即使目标方法没有正常的执行完成。另外,后置通知可以通过配置得到返回值,而最终通知无法得到。最终通知也可以额外接收一个JoinPoint参数,来获取目标对象和目标方法相关信息,但一定要保证必须是第一个参数。
当存在多个切面时:
采用了责任链设计模式,如下图。
如果目标方法抛出异常:
Spring AOP的原理过程
在初始化spring容器的时候,通过注解或者配置文件,把需要交给spring管理的类全部加载进去,然后创建对应的对象,这个时候根据AOP的配置,来判断哪些对象是需要代理对象的,这个时候把它对应的代理对象也创建出来,当目标对象调用到那些切入点的时候,其实是代理对象来进行调用的,而没有被注解或者配置文件特殊说明过的方法,则就正常调用。
Spring自动为目标对象生成代理对象,默认情况下,如果目标对象实现过接口,则采用java的动态代理机制,如果目标对象没有实现过接口,则采用cglib动态代理。
如果希望使用cglib的方式来生成代理对象,则在配置文件中<aop:config proxy-target-class=“true”>即可。
Spring实现AOP和综合案例(注解的方式)
直接结合综合案例来阐述如何使用
下面是项目结构:
首先需要在配置文件中开启AOP注解的功能:
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--包扫描-->
<context:component-scan base-package="cn.tdu"></context:component-scan>
<!--注解声明-->
<context:annotation-config></context:annotation-config>
<!--AOP注解配置-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
要使用AOP,就需要写一个切面类和里面的通知,在切面类上用@Component和@Aspect来注解这个类,表明这是一个切面类。这里我使用@Around来注解这个通知,表示它是一个环绕通知。
其他几种通知的方式:
前置通知 @Before
环绕通知 @Around
后置通知 @AfterReturning
异常通知 @AfterThrowing
最终通知 @After
如果一个切面中多个通知 重复使用同一个切入点表达式,则可以将该切入点表达式使用@pointcut来单独定义,后续引用,注意,在当前切面中通过注解定义的切入点只在当前切面中起作用,其他切面看不到。
@Component
@org.aspectj.lang.annotation.Aspect
public class Aspect {
//切入点表达式
@Pointcut("execution(cn.tdu.bean.User cn.tdu.Service.UserService.Create())")
public void mx(){};
//该注解表示环绕增强mx()表达式筛出的方法
@Around("mx()")
public Object around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("正准备调用");
Object obj=jp.proceed();
System.out.println("调用完成");
return obj;
}
}
另外说几句,在后置通知的注解中,也可以额外配置一个returning属性,来指定一个参数名接受目标方法执行后的返回值。
在异常通知的注解中,也可以额外配置一个throwing属性,来指定一个参数名接受目标方法抛出的异常对象
上面就说明了如何使用注解来实现spring AOP 的使用,切面类,通知和切入点我们都已经写好了,再来看看其他类的代码。
首先来看DAO层:
@Component("MySql")
public class MySql implements dao {
//使用工厂注入user
@Autowired
@Qualifier("getInstance")
User user=null;
@Override
public User insert() {
System.out.println("创建成功");
return user;
}
}
该层实现了一个dao接口,之前说过这样做的目的是用来解耦的,user是一个对象,通过工厂模式生产出来的,用来返回出去到上一层。
工厂:
@Component("UserFactory")
public class UserFactory {
//通过user类来注入到工厂中,工厂返回
@Autowired
@Qualifier("User")
User user=null;
@Bean("getInstance")
public User getInstance(){
return this.user;
}
}
User类:
@Component("User")
public class User {
@Value("lxh")
private String name;
@Value("23")
private int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
service层:
同理也是实现了一个接口来进行解耦合,这里和之前的案例没有多大区别就不做说明了。
@Component("UserService")
public class UserService implements service {
//注入Mysql的对象
@Autowired
@Qualifier("MySql")
private dao Dao=null;
@Override
public User Create() {
//调用mysql中的方法
return Dao.insert();
}
public void test1(){
System.out.println("不是代理对象调用的");
}
}
web层:
这里和之前也没有多大区别也不做解释
@Component("control")
public class control {
@Autowired
@Qualifier("UserService")
private service service=null;
public User create(){
return service.Create();
}
public void test1(){
service.test1();
}
}
调用测试:
public class test01 {
@Test
public void test(){
ApplicationContext context=new ClassPathXmlApplicationContext("Application.xml");
control con= (control) context.getBean("control");
System.out.println(con.create());
con.test1();
((ClassPathXmlApplicationContext)(context)).close();
}
}
整个过程:
con.create()调用了service层中的create()方法,因为之前在切面类中已经把调用create()方法声明成了一个切入点(@Pointcut(“execution(cn.tdu.bean.User cn.tdu.Service.UserService.Create())”)),所以其实是UserService的代理对象调用的create方法,进入了环绕通知,执行环绕通知的前通知,然后手动调用方法(Object obj=jp.proceed();),于是进入到service层中的create方法,service层中的create方法再调用Dao层中的create方法,Dao层中的user通过注解获取到工厂生产的对象,然后把该对象返回给service,再执行环绕通知的后通知,然后再把对象返回给web层。