Spring AOP(面向切面编程)

一、面向切面编程概念

将切面提取出来单独开发,在需要调用的方法中通过动态代理的方式进行植入

切面:公共的 通用的 重复的功能

二、手写AOP框架

业务:图书购买业务 ,切面:事务

1.业务和切面紧耦合在一起,没有拆分

/**
 * 图书购买业务和事务的切面
 */
public class BookServiceImpl {

    public void buy(){
        try{
            System.out.println("事务开启.....");
            System.out.println("图书购买业务功能实现.....");
            System.out.println("事务提交....");
        }catch (Exception e){
            System.out.println("事务回滚....");
        }
    }
}

2.使用子类代理的方式拆分业务和切面

/**
 * 使用子类代理的方式进行图书业务和事务切面的拆分
 */
public class BookServiceImpl {
    //只有业务
    public void buy(){
        System.out.println("图书购买功能实现....");
    }
}

/**
 * 子类是代理类,将父类的图书购买功能添加事务切面
 */
public class SubBookServiceImpl extends BookServiceImpl{

    @Override
    public void buy() {
        try{
            //事务切面
            System.out.println("事务开启...");
            //主业务实现
            super.buy();
            //事务切面
            System.out.println("事务提交....");
        }catch (Exception e){
            System.out.println("事务回滚....");
        }
    }
}

3.使用静态代理拆分业务和切面

业务和业务已经拆分,但是切面紧耦合在业务中

public interface Service {
    //规定业务功能
    void buy();
}

public class ProductServiceImpl implements Service{
    @Override
    public void buy() {
        System.out.println("商品购买业务实现....");
    }
}

public class BookServiceImpl implements Service{
    @Override
    public void buy() {
        System.out.println("图书购买业务功能实现....");
    }
}

/**
 * 静态代理已经实现了目标对象的灵活切换
 */
public class Agent implements Service{
    //设计成员变量的类型为接口,为了灵活切换目标对象
    public Service target;
    //使用构造方法传入目标对象
    public Agent(Service target){
        this.target=target;
    }
    @Override
    public void buy() {
        try{
            //切面功能
            System.out.println("事务开启....");
            //业务功能
            target.buy();
            //切面功能
            System.out.println("事务提交....");
        }catch (Exception e){
            System.out.println("事务回滚....");
        }
    }
}

4.使用静态代理拆分业务和业务接口,切面和切面接口

public interface AOP {
    default void before(){}
    default void after(){}
    default void exception(){}
}

public class LogAop implements AOP{
    @Override
    public void before() {
        System.out.println("前置日志输出.....");
    }
}

public class TransAop implements AOP{
    @Override
    public void before() {
        System.out.println("事务开启......");
    }
    @Override
    public void after() {
        System.out.println("事务提交.....");
    }
    @Override
    public void exception() {
        System.out.println("事务回滚");
    }
}

public interface Service {
    //规定业务功能
    void buy();
}

public class ProductServiceImpl implements Service {
    @Override
    public void buy() {
        System.out.println("商品购买业务实现....");
    }
}

public class BookServiceImpl implements Service {
    @Override
    public void buy() {
        System.out.println("图书购买业务功能实现....");
    }
}

public class Agent implements Service{
    //传入目标业务对象,切面对象
    Service target;
    AOP aop;
    //使用构造方法初始化业务对象和切面对象
    public Agent(Service target,AOP aop){
        this.target=target;
        this.aop=aop;
    }
    @Override
    public void buy() {
        try{
            //切面 事务/日志
            aop.before();
            //业务 图书。商品
            target.buy();
            //切面 事务
            aop.after();
        }catch (Exception e){
            //切面
            aop.exception();
        }
    }
}

5.使用动态代理完成4的优化

public interface AOP {
    default void before(){}
    default void after(){}
    default void exception(){}
}

public class LogAop implements AOP{
    @Override
    public void before() {
        System.out.println("前置日志输出.....");
    }
}

public class TransAop implements AOP{
    @Override
    public void before() {
        System.out.println("事务开启......");
    }
    @Override
    public void after() {
        System.out.println("事务提交.....");
    }
    @Override
    public void exception() {
        System.out.println("事务回滚");
    }
}

public interface Service {
    //规定业务功能
    void buy();
}

public class ProductServiceImpl implements Service {
    @Override
    public void buy() {
        System.out.println("商品购买业务实现....");
    }
}

public class BookServiceImpl implements Service {
    @Override
    public void buy() {
        System.out.println("图书购买业务功能实现....");
    }
}

public class ProxyFactory {
    public static Object getAgent(Service target,AOP aop){
        /**
         * ClassLoader 第一个参数:类加载器
         * Class<?>[] interfaces 第二个参数:目标对象实现的所有接口
         * reflect.InvocationHandler 第三个参数:代理功能实现
         * 返回:生成的代理对象
         */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     *proxy 生成的代理对象
                     *method 正在被调用的目标方法
                     *args 目标方法的参数
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
                        Object obj = null;
                        try {
                            //切面 事务/业务
                            aop.before();
                            //业务
                            obj = method.invoke(target, args);
                            //切面
                            aop.after();
                        } catch (Exception e) {
                            //切面
                            aop.exception();
                        }
                        //切面
                        return obj;//目标方法的返回值
                    }
                }
        );
    }
}

三、Spring原生AOP

四、AOP常见术语

(1)切面:那些重复的、公共的、通用的功能,例如:日志 事务 权限
(2)连接点:就是目标方法,因为在目标方法中要实现目标方法的功能和切面功能
(3)切入点(Pointcut):多个连接点构成切入点,切入点可以是一个目标方法,可以是一个类中的所有方法,可以是一个包下的所有方法
(4)目标对象:操作谁谁就是目标对象
(5)通知(Advice):来指定切入的时机,是在目标方法执行前 还是执行后 还是出错时 还是环绕目标方法切入面功能

五、AspectJ框架

1.什么是AspectJ框架

是一个优秀的面向切面的框架,它扩展了java语言,提供了强大的切面实现,是由java实现的

2.AspectJ常见的通知类型

(1)前置通知 @Before
(2)后置通知 @AfterReturing
(3)环绕通知 @Around 常用于事务,功能最强大
(4)最终通知 @After
(5)定义切入点(了解) @Pointcut

3.AspectJ的切入点表达式(掌握)

(1)公式和符号

(1)
规范公式:
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
简化:
execution(方法返回值 方法声明(参数))

(2)用到的符号
* 代表人一个任意的字符(通配符)
.. 如果出现在方法的参数中则代表任意参数,如果出现在路径中则代表本路径及其所有的子路径

(2)示例

(1)execution(public * *(..))
公共访问权限下的 任意返回值的 任意路径下的 任意参数的方法 ---> 公共访问权限下的所有方法
(2)execution(* set*(..))
任意返回值类型的 任意路径下的 以set打头的所有方法  ---> 任意一个以"set"开始的方法
(3)execution(* com.xyz.service.impl.*.*(..))
任意返回值类型的,在com.xyz.service.impl包下的任意类的任意方法的任意参数
(3)execution(* com.xyz.service..*.*(..))
任意返回值类型的,在 com.xyz.service及其子包下的任意类的任意方法的任意参数
(4)execution(* *..service.*.*(..))
任意返回值类型的,(service之前可以有任意的包),指定所有包下的service子包下所有类中所有的方法作为切入点

4.前置通知 @Before

在目标方法执行前切入切面功能,在切面方法中不可以获得目标方法的返回值,只能得到目标方法的签名。

签名:访问权限 方法返回值 方法名 参数列表

(1)基于applicationContext.xml的方式

1)创建业务接口
public interface SomeService {
    String doSome (String name,int age);
    void show();
}2)创建业务实现
public class SomeServiceImpl implements SomeService{
    @Override
    public String doSome(String name, int age) {
        System.out.println("doSome的业务功能实现....");
        return "1";
    }
    @Override
    public void show() {
        System.out.println("show()的业务方法被执行.....");
    }
}3)创建切面类,实现切面方法
@Aspect //交给AspectJ的框架去识别切面类
public class MyAspect {
    /**
     * 所有切面的功能都是由切面方法来实现的
     * 可以将各种切面都在此类中进行开发
     *
     * 前置通知的切面方法的规范:
     *   访问权限 public
     *   方法的返回值:void
     *   方法名称:自定义
     *   方法没有参数
     *   必须使用@Before注解来声明切入的时机和切入点
     *     参数:@value 指定切入点表达式
     * 业务方法:public String doSome(String name, int age)
     */
    @Before(value = "execution(public * com.aspectj.s01.SomeServiceImpl.*(..))")
    public void myBefore(){
        System.out.println("切面方法中的前置通知功能实现");
    }
}4)在applicationContext.xml文件中进行切面绑定
    <!--创建业务对象-->
    <bean id="someService" class="com.aspectj.s01.SomeServiceImpl"/>
    <!--创建切面对象-->
    <bean id="myAspect" class="com.aspectj.s01.MyAspect"/>
    <!--绑定-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

(2)注解方式实现

1)创建业务接口
public interface SomeService {
    String doSome (String name,int age);
    void show();
}2)创建业务实现
@Service("someServiceImpl")
public class SomeServiceImpl implements SomeService{
    @Override
    public String doSome(String name, int age) {
        System.out.println("doSome的业务功能实现....");
        return "1";
    }
    @Override
    public void show() {
        System.out.println("show()的业务方法被执行.....");
    }
}3)创建切面类,实现切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component("myAspect")
public class MyAspect {
    @Before(value = "execution(public * com.aspectj.s01.SomeServiceImpl.*(..))")
    public void myBefore(){
        System.out.println("切面方法中的前置通知功能实现");
    }
}4)在applicationContext.xml文件中进行切面绑定
    <!--基于注解的访问要添加包扫描-->
    <context:component-scan base-package="com.aspectj.s01"/>
    <!--绑定-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

5.后置通知@AfterReturning

1)业务接口
public interface SomeService {
    String doSome (String name,int age);
    Student change();
}2)业务实现类
@Service("someServiceImpl")
public class SomeServiceImpl implements SomeService {
    @Override
    public String doSome(String name, int age) {
        System.out.println("doSome的业务功能实现....");
        return "abcd";
    }
    @Override
    public Student change() {
        System.out.println("change()方法被执行....");
        return new Student("张三");
    }
}3)切面类实现切面方法
@Aspect
@Component
public class MyAspect {
    /**
     * 后置通知方法的规范:
     *    访问权限是public
     *    方法没有返回值
     *    方法名称自定义
     *    方法有参数(如果目标方法没有返回值,则可以写无参的方法,一般写有参,切面方法的参数,就是目标方法的返回值)
     *    使用@AfterReturning注解标明是后置通知
     *    参数:
     *      value:指定切入点表达式
     *      returning:指定目标方法的返回值的名称,此名称必须和切面方法的参数名称一致
     */
    @AfterReturning(value = "execution(* com.aspectj.s02.*.*(..))",returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置通知功能的实现....");
        if (obj != null) {
            if (obj instanceof String){
                obj=obj.toString().toUpperCase();
                System.out.println("在切面方法中目标方法的返回值:"+obj);
            }
            if (obj instanceof Student){
                Student student=(Student) obj;
                student.setName("李四");
                System.out.println("在切面方法中目标方法的返回值:"+student);
            }
        }
    }
}4)在applicationContext.xml文件中进行绑定
    <!--基于注解的访问要添加包扫描-->
    <context:component-scan base-package="com.aspectj.s02"/>
    <!--绑定-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>5)测试
public class test2 {
    @Test
    public void test02(){
        ApplicationContext ac=new ClassPathXmlApplicationContext("s02/applicationContext.xml");
        //取出代理对象
        SomeService someServiceImpl =(SomeService) ac.getBean("someServiceImpl");
        String s = someServiceImpl.doSome("张三", 12);
        System.out.println("测试类中目标方法的返回值:"+s);
    }
    @Test
    public void test03(){
        ApplicationContext ac=new ClassPathXmlApplicationContext("s02/applicationContext.xml");
        //取出代理对象
        SomeService someServiceImpl =(SomeService) ac.getBean("someServiceImpl");
        Student student = someServiceImpl.change();
        System.out.println("测试类中目标方法的返回值:"+student);
    }
}

6.环绕通知@Around

通过拦截目标方法的方式,在目标方法的前后增强功能的通知。功能强大,一般事务使用此通知,可以任意改变目标方法的返回值

1)业务接口
public interface SomeService {
    String doSome (String name,int age);
}2)实现业务类
@Service("someServiceImpl")
public class SomeServiceImpl implements SomeService {
    @Override
    public String doSome(String name, int age) {
        System.out.println("doSome的业务功能实现...."+name);
        return "abcd";
    }
}3)切面类实现切面方法
@Aspect
@Component
public class MyAspect {
    /**
     * 环绕通知方法的规范
     *    访问权限:public
     *    切面方法的返回值就是目标方法的返回值
     *    方法名称自定义
     *    方法有参数,此参数就是目标方法
     *    回避异常
     *    使用@Around注解声明是环绕通知
     *    参数:
     *       value:指定切入点表达式
     */
    @Around(value = "execution(* com.aspectj.s03.*.*(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        //前切功能实现
        System.out.println("环绕通知中的前置功能实现....");
        /**
         * 目标方法调用
         * proceed():获取目标方法
         * getArgs():获取目标方法的参数
         */
        Object obj=pjp.proceed(pjp.getArgs());
        //后切功能实现
        System.out.println("环绕通知中的后置功能实现....");
        return obj.toString().toUpperCase();//改变了目标方法的返回值
    }
}4)在applicationContext.xml文件中进行绑定
    <!--基于注解的访问要添加包扫描-->
    <context:component-scan base-package="com.aspectj.s03"/>
    <!--绑定-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>5)测试
public class test3 {
    @Test
    public void test03(){
        ApplicationContext ac=new ClassPathXmlApplicationContext("s03/applicationContext.xml");
        //取出代理对象
        SomeService someServiceImpl =(SomeService) ac.getBean("someServiceImpl");
        String s = someServiceImpl.doSome("张三", 12);
        System.out.println("测试类中目标方法的返回值:"+s);
    }
}

7.最终通知@After

无论目标方法是否正常执行,最终通知的代码都会被执行
切面类实现切面方法
@Aspect
@Component
public class MyAspect {
    /**
     * 最终通知方法的规范
     *    访问权限:public
     *    方法没有返回值
     *    方法名称自定义
     *    方法没有参数,如果有也只能是JoinPoint
     *    使用@After注解声明是最终通知
     *    参数:
     *       value:指定切入点表达式
     */
    @After(value = "execution(* com.aspectj.s04.*.*(..))")
    public void myAfter(){
        System.out.println("最终通知的功能....");
    }
}

8.给切入点表达式起别名@Pointcut

如果多个切面切入到同一个切入点,可以使用别名简化开发
使用@Pointcut注解,创建一个空方法,此方法的名称就是别名

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cw旧巷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值