IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> spring学习——02(IOC、AOP) -> 正文阅读

[Java知识库]spring学习——02(IOC、AOP)

一、控制反转

控制反转说白了就是将java对象注册到spring容器中,让spring 容器管理我们的对象。
对应的操作就是:

  • 在xml中配置bean,将对象(bean)注册到spring 容器
  • 使用注解@Component(同类@Controller,@Mapping、@Repository、@Service)、@Configuration(配合@Bean)同时配合包扫面@ComponentScan(),就能将对象(bean)注册到spring 容器

二、依赖注入

依赖输入就是将我们放到spring 容器的bean(对象)拿出来使用。注入到一个类中。这样我们就不需要通过new 来创建类的对象。
对应的操作就是:

1、在xml中注入:

<bean id="people" class="com.lihua.pojo.People" autowire="byName">
  <property name="name" value="lihua"/>  <!-- 将基础类型String的初始值注入到people类中 -->
  <property name="cat" ref="cat"/>   <!-- 从容器中拿出cat,并将它注入到people类中 -->
  <property name="dog" ref="dog"/>
</bean>

People 类

public class People {
    private Cat cat;
    private Dog dog;
    private String name;

2、在java中注入

2.1、@Autowired和@Qualifier(“Cat1”)

@Autowired ,这种方式是根据byType进行注入,也就是根据类名(接口名)进行加载
比如:

@Autowired 
private Cat cat;
//在这里Type就是Cat,注入时会从容器中找到Cat这个类型的bean,将他(注入)复制cat这个属性

注意:使用 @Autowired可能会报错,特别是注入一个接口类型时,因为接口会有很多实现类,在使用 @Autowired注入时,spring 容器不知道注入哪个实现类

解决:我们可以结合@Qualifier(“Cat1”)注解,给接口指定一个实现类。这样@Autowired和@Qualifier(“Cat1”)配置使用我们就是通过byName来进行匹配

比如:

  1. 接口:
package com.lihua.pojo;

import org.springframework.stereotype.Component;
//注意:接口这里可以不用注解,只需要将实现类注册到spring容器即可
public interface CatInterface {
    void show();
}
  1. 实现类Cat1
package com.lihua.pojo;

import org.springframework.stereotype.Component;

@Component("Cat1")
public class Cat1 implements CatInterface {
    public void show() {
        System.out.println("cat1");
    }
}
  1. 实现类Cat2
package com.lihua.pojo;


import org.springframework.stereotype.Component;

@Component("Cat2")
public class Cat2 implements CatInterface {
    public void show() {
        System.out.println("cat2");
    }
}
  1. 测试类:
package com.lihua.pojo;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class Test {

    @Autowired
    //必须指定注入哪个实现类,否则报错
    @Qualifier("Cat2")
    private CatInterface catInterface;
    @org.junit.Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Test test = (Test)context.getBean("test");
        test.catInterface.show();
    }
}

2.2、@Resource(name = “Cat1”)

@Resource(name = “Cat1”) 等价于 @Autowired和@Qualifier(“Cat1”)

@Resource(name = “Cat1”) ,注解有两个属性分别是type和name

指定type属性,会根据byType进行匹配bean(即根据类名)
指定name属性,会根据byName进行匹配bean(根据id @Component(“Cat1”) 也就是里面的Cat1 )
默认是byName匹配

3、属性注入的方式(spring容器是如何将值注入属性的)

Spring支持两种依赖注入方式,分别是属性注入和构造方法注入。用户不但可以将String,int等类型参数注入到Bean中,还可以将集合、Map类型注入到Bean中。

1、set方法注入

也就是在实体类里必须提供set方法,这样spring 容器才能通过set方法将值注入到属性中。

属性注入要求Bean提供一个默认的构造方法,并为需要注入的属性提供对应的set方法。Spring调Bean的默认构造方法创建对象,然后通过反射的方式调用set方法注入属性。

//创建类
public class User {
     private String username;
     public void setUsername(String username){
         this.username=username;
     }
     public void speak(){
         System.out.println("my name is"+username);
     }
 }

//  配置文件
 <bean id="user" class="com.cad.domain.User"> 
     <!--<property>对应一个属性,name是属性的名称,value是属性的值,ref用来引用其他bean-->
     <property name="username" value="张三"></property>
 </bean>

** 注意:set方法必须发和命名规范,否则 spring 容器无法匹配属性和set方法。**

2、有参构造注入

通过有参构照器注入,使用构造方法注入前提是Bean必须提供带参的构造方法。


//创建User类
public class User {
    private String username;
    public User(String username){
        this.username=username;
    }
    public void speak(){
        System.out.println("my name is"+username);
    }
}

//配置文件
<bean id="user" class="com.cad.domain.User">
    <!--<constructor-arg>标签对应构造方法的参数,name是参数名称,value是参数值-->
    <constructor-arg name="username" value="jack"></constructor-arg>
</bean> 
这里有一个小问题,如果类中有两个带参构造方法,我们应该怎样注入? < constructor-arg>标签有两个属性 index和type。 type是指定要注入的属性的类型,index指定注入的属性的顺序,从0开始。

三、IOC容器与spring容器

一般称BeanFactory为IoC容器ApplicationContext为应用上下文,也称为Spring容器
BeanFactory是Spring框架的基础,面向Spring本身,ApplicationContext面向使用Spring框架的开发者,几乎所有的应用我们都直接使用ApplicationContext而非底层的BeanFactory。

四、BeanFactory、ApplicationContext、WebApplicationContext

1、BeanFactory

BeanFactory是一个类工厂,和传统的类工厂不同,传统的类工厂仅负责构造一个类或几个类的实例。而BeanFactory可以创建并管理各种类的对象,Spring称这些被创建和管理的Java对象为Bean。

BeanFactory是一个接口,Spring为BeanFactory提供了多种实现,最常用的就是实现类XmlBeanFactory
BeanFactory接口最主要的方法就是getBean(String beanName),该方法从容器中返回指定名称的Bean。
BeanFactory接口的功能通过其他实现它的接口不断扩展。

注意: XmlBeanFactory装在Spring配置文件并启动IoC容器。通过BeanFactory 启动IoC容器时,并不会初始化配置文件中定义的Bean,初始化创建动作在第一个调用时

package com.lihua.IOC;

import com.lihua.IOC.pojo.User;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

/**
 * 使用BeanFactory创建IOC容器
 *
 * @author 15594
 */
public class MyBeanFactory {
    public static void main(String[] args) {
        //获取spring配置文件
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource resource = resolver.getResource("classpath:bean.xml");

        //加载spring 配置文件创建IOC容器
        BeanFactory bf = new XmlBeanFactory(resource);
        //从容器中获取bean对象
        User user = (User)bf.getBean("user");
        user.setId(1);
        System.out.println(user.getId());

    }
}

2、ApplicationContext

ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。在BeanFactory中,很多功能需要编程方式来实现,ApplicationContext中可以通过配置的方式来实现。

ApplicationContext的主要实现类是ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,前者默认从类路径加载配置文件,后者默认从文件系统中加载配置文件。

package com.lihua.IOC;

import com.lihua.IOC.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * 使用ApplicationContext创建spring 容器 (常用)
 * @author 15594
 */
public class MyApplicationContext {
    public static void main(String[] args) {

        //从classpath目录(也就是这个目录中target\classes)中获取spring 配置文件(常用)
        ApplicationContext app =  new ClassPathXmlApplicationContext("bean.xml");

        //从文件系统中获取spring 配置文件
        ApplicationContext app1 = new FileSystemXmlApplicationContext("C:\\Users\\15594\\IdeaProjects\\spring\\IOC容器的创建方式\\target\\classes\\bean.xml");


        //通过byType方式获取Bean
        User beanByType = app.getBean(User.class);

        System.out.println("byType:"+beanByType.toString());
        //通过byName方式获取Bean
        User beanByName = (User) app.getBean("user");
        System.out.println("byName:"+beanByName.toString());
    }
}

3、WebApplicationContext

WebApplicationContext是专门为Web应用准备的,它允许以相对于Web根目录的路径中加载配置文件完成初始化工作。从WebApplicationContext中可以获取ServletContext的引用,整个WebApplicationContext对象作为属性放置到ServletContext中,以便Web应用环境中可以访问Spring应用上下文。

ConfigurableWebApplicationContext扩展了WebApplicationContext,允许通过配置方式实例化WebApplicationContext,定义了两个重要方法。

setServletContext(ServletContext servletcontext):为Spring设置ServletContext

setConfigLocation(String[] configLocations):设置Spring配置文件地址。

WebApplicationContext的初始化
在ServletContext对象创建的时候,借助监听器监听ServletContext对象的创建,然后初始化WebApplicationContext对象。Spring提供了这个监听器。ContextLoaderListener。我们只需要在web.xml配置监听器即可。

五、AOP

1、动态代理

动代理的核心是,代理对象不仅仅需要完成代理的任务,在完成这个任务的过程中代理对象也可以去做一些额外的事。
比如:
1、代理购票,代理者(代理对象)在代理购票的过程中,代理者还可以在买票前喝杯奶茶,买完票后去看一部电影。
2、代码中的例子:比如userMapper对象需要调用queryUserById(int id)这个方法查询一个用户。突然我们想要在查询到这个user后插入一条日志,在不改变原有的代码的前提下怎么做?我们已近知道动态代理的核心就是在完成主线任务的前后,可以完成一些支线任务。所有我们就可以通过动态代理来实现。

1.1、jdk动态代理

jdk动态代理需要代理的目标对象的类必须实现一个接口。这个接口里面存放的方法就是主线任务(主逻辑)

package com.lihua.AOP.JDKProxy;

/**
 * 接口,主线任务(主逻辑)
 * @author 15594
 */
public interface Animal {
    public void run();
    public void eat();
}

接口实现类,目标类(需要代理的类)

package com.lihua.AOP.JDKProxy;

public class Dog implements Animal {
    public void run() {
        System.out.println("跑");
    }

    public void eat() {
        System.out.println("吃");
    }
}

我们想在动物吃饭前后添加一些业务逻辑(支线任务,也就是一些方法、事务),我们为了方便调用将这些方法放到一个类中,当然也可以在不同类。

package com.lihua.AOP.JDKProxy;

/**
 * 向目标对象中插入一些方法。当然这些方法不一定要放到一个类中。
 * @author 15594
 */
public class MyAspect {

    //在动物吃饭前需要主人发出命令。
    public void before(){
        System.out.println("主人发出命令");
    }
    //在动物吃饭完后主人给动物一些奖励。
    public void after(){
        System.out.println("主人给予奖励");
    }
}

在不改变原来代码的基础上,我们增加一个动态代理类,给目标类(方法)增加一些业务逻辑

package com.lihua.AOP.JDKProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 *
 * 使用动态代理生成一个插入了一些方法(事务、增强逻辑)的代理对象。
 * 代理对象里面有原来的方法,同时也插入了一些我们指定的方法。
 * @author 15594
 */
public class MyProxyFactory {
    public  static Animal createProxy(){
         final Animal dog=new Dog();
         final MyAspect aspect=new MyAspect();
        //生成的代理对象也实现了目标对象的接口。
        Animal dogproxy=(Animal) Proxy.newProxyInstance(
                //指定一个类加载器,(不管是哪个类调用,只要返回类加载器就行)
                MyAspect.class.getClassLoader(),
                //指定目标对象实现的接口
                dog.getClass().getInterfaces(),
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //在执行需要代理的方法前,可以添加一些业务逻辑
                        aspect.before();

                        //执行要代理的方法
                        Object invoke = method.invoke(dog, args);

                        //在执行需要代理的方法后,可以添加一些业务逻辑
                        aspect.after();

                        return invoke;
                    }
                }
        );
        return dogproxy;
    }
}

使用动态代理生成的代理对象,通过调用代理对象来完成主业务逻辑和一些附加的业务逻辑

/**
 * @author 15594
 */
public class Test {
    public static void main(String[] args) {
        //没有代理的对象
        Animal animal1 = new Dog();
        animal1.eat();

        System.out.println("=======");
        //代理后的对象
        Animal animal2=MyProxyFactory.createProxy();
        animal2.eat();
    }
}

在这里插入图片描述

1.2、CGLib动态代理

使用JDK动态代理有一个很大的限制,就是只能为接口创建代理实例
对于没有通过接口定义的类,如何动态创建代理实例呢?CGLib填补了这么空缺。

CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并织入横切逻辑。但是由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的private、final等方法进行代理

CGLib使用需要导入cglib.jar核心包和asm.jar依赖包,但是Spring 的Core.jar核心包中已经包含了这两个jar包,所以我们直接使用即可。

CGLib与jdk最大的区别就是CGLib目标对象不需要实现任何接口。
CGLib通过子类(这个子类不用写CGLib自己生成)的方式来实现代理,子类继承父类后就会根父类有一样的方法。

目标类

package com.lihua.AOP.CGLib;
/**
 *
 * CGLib与jdk最大的区别就是CGLib目标对象不需要实现任何接口。
 * CGLib通过子类(这个子类不用写CGLib自己生成)的方式来实现代理,子类继承父类后就会根父类有一样的方法
 * @author 15594
 */
public class Dog {
    public void run() {
        System.out.println("跑");
    }

    public void eat(int i) {
        System.out.println("吃");
    }
}

需要增加的业务逻辑

package com.lihua.AOP.CGLib;

/**
 * 向目标对象中插入一些方法。当然这些方法不一定要放到一个类中。
 * @author 15594
 */
public class MyAspect {
    public void before(){
        System.out.println("主人发出命令");
    }

    public void after(){
        System.out.println("主人给予奖励");
    }
}

动态代理类生成,代理对象

package com.lihua.AOP.CGLib;



import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 *
 * 使用动态代理生成一个插入了一些方法(事务、增强逻辑)的代理对象。
 * 代理对象里面有原来的方法,同时也插入了一些我们指定的方法。
 * @author 15594
 */
public class MyProxyFactory {

    public  static Dog createProxy(){
        //CGLib核心类
        Enhancer enhancer=new Enhancer();
        final Dog dog=new Dog();
        final MyAspect aspect=new MyAspect();

        //设置父类,因为CGLib底层是生成需要被代理类的子类
        enhancer.setSuperclass(dog.getClass());
        //设置回调方法,和InvocationHandler起到的效果一样
        enhancer.setCallback(new MethodInterceptor() {

            //intercept方法会拦截所有目标类方法的调用
            //o表示目标类的实例,method为目标类方法的反射对象,objects是方法的参数,methodProxy是代理类实例
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                aspect.before();
                Object obj=method.invoke(dog, objects);
                aspect.after();
                return obj;
            }
        });
        //创建代理对象
        Dog dogproxy=(Dog) enhancer.create();
        return dogproxy;
    }
}

测试

package com.lihua.AOP.CGLib;

import com.lihua.AOP.JDKProxy.Animal;

/**
 * @author 15594
 */
public class Test {
    public static void main(String[] args) {
        //没有代理的对象
        Dog dog = new Dog();
        dog.eat(1);

        System.out.println("=======");
        //代理后的对象
        Dog dog1 = MyProxyFactory.createProxy();
        dog1.eat(1);
    }
}

2、动态代理出现的一些问题

  1. 目标类的所有方法都添加了横切逻辑,而有时,我们只想对某些方法添加横切逻辑
  2. 我们通过硬编码的方式指定了织入横切逻辑的连接点,显然不利于修改和维护。
  3. 我们纯手工的编写创建代理的过程,过程繁琐为不同类创建代理时,需要分别编写不同的代码,无法做到通用

spring AOP为我们完全解决了上述问题

3、spring AOP的一些概念

3.1、AOP术语(不用记、看完就理解了)

  • 连接点(Joinpoint): 程序执行的某个特定位置:如类初始化前,类初始化后,某个方法调用前,方法调用后,方法抛出异常后。一个类或一段代码拥有一些具有边界性质的特定点,就被称为 “连接点”。Spring仅支持方法的连接点,仅能在方法调用前后、方法抛出异常时这些连接点织入增强。
    举例:
package com.lihua.AOP.CGLib;
/**
 * @author 15594
 */
public class MyAspect {

	//前置增强方法
    public void before(){
        System.out.println("在查询前插入一条日志");
    }
    //这个方法上下就是连接点(上连接点),此外还有抛出异常连接点
    public void queryUser(){
        System.out.println("查询用户业务");
    }
    //(下连接点)
    
    //后置增强方法
    public void after(){
        System.out.println("在查询前插入一条日志");
    }
}
  • 切点(Pointcut):每个程序都拥有很多连接点,如有一个两个方法的类,这两个方法都是连接点。如何定位到某个连接点上呢?AOP通过“切点”定位特定的连接点。例如:数据库记录是连接点,切点就是查询条件,用来定位特定的连接点。在Spring中,切点通过Pointcut接口描述,使用类和方法作为连接点的查询条件,Spring AOP的解析引擎负责解析切点设定的条件,找到对应的连接点。连接点是方法执行前后等具体程序执行点,而切点只定位到某个方法上,所以如果定位到具体的连接点,还需要提供方位信息,即方法执行前还是执行后。
    举例:
    比如用户登录模块,会有一些业务逻辑(方法)注册、修改密码、找回密码、登录…现在我们只需要对登录、修改密码这个两个方法添加一个日志增强(日志功能,记录用户登录、修改)。但是用户登录模块里面有很多方法,我们怎么只能给登录、修改方法织入日志增强?切点:可以理解为找到这些方法的位置。我们一般通过切点算法、切点表达式就行过滤方法
    切点算法
package com.lihua.AOP.AOP.切面和切点;

import com.lihua.AOP.AOP.Dog;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;

import java.lang.reflect.Method;

/**
 * 切点算法,用来筛选方法
 * <b>如果不设置切点,那么目标对象所有的方法都会被增强</b><br
 * 获取切点(编写一个匹配算法,筛选出切点)
 * @author 15594
 */
public class MyPointcut extends StaticMethodMatcherPointcutAdvisor {
    /**
     * 匹配方法名称为eat的方法
     * */
    public boolean matches(Method method, Class<?> aClass) {
        return method.getName().equals("eat");
    }

    /**
     * 但是默认情况,该切面匹配所有类,我们通过覆盖getClassFilter方法,仅匹配Dog及其子类
     *
     * 我们通过覆盖getClassFilter方法,仅匹配Dog及其子类.
     * */
    @Override
    public ClassFilter getClassFilter() {
        return new ClassFilter(){
            public boolean matches(Class<?> clazz) {
                return Dog.class.isAssignableFrom(clazz);
            }
        };
    }
}

切点表达式

        <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.lihua.spring事务_声明式02.service.impl.BankServiceImpl.*(..))" />
  • 增强/通知(Advice):增强是织入到目标类连接点上的一段程序代码。在Spring中,增强除了描述一段程序代码外,还拥有另一个和连接点相关的信息,就是执行点的方位。结合方位信息和切点信息,就可以找到特定的连接点。Spring提供的增强接口都是带方位名的:BeforeAdvice,AfterRetuningAdvice,ThrowsAdvice等。所以只有结合切点和增强,才能确定特定的连接点并织入增强代码。
    增强也就是在原有的业务逻辑上增加一些额外是业务逻辑(比如日志),我们使用AOP织入增强可以做到在不改变原有的代码的前提下,增加业务逻辑

  • 目标对象(Target):需要织入增强代码的目标类。

  • 引介(Introduction):**引介是一种特殊的增强,**它为类添加一些属性和方法。这样,即使某个类没有实现某个接口,通过AOP的引介功能,我们可以动态的为该类添加接口的实现逻辑,让该类成为这个接口的实现类。

  • 织入(Weaving):织入是将增强添加到目标类上具体连接点的过程。(将增强插入原有的业务逻辑的过程就叫织入

  • 代理(Proxy):一个类被AOP织入增强后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类

  • 切面(Aspect):切面由切点和增强组成,既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面,将定义的横切逻辑织入到切面指定的连接点中。切面一般由增强和切点组成

<!--增强类-->
    <bean id="advices" class="com.lihua.AOP.AOP.AspectJ.MyAdvice"></bean>
 <!--配置aop,proxy-target-class="true"表示使用cglib动态代理-->
    <aop:config proxy-target-class="true">
        <!--切点-->
        <aop:pointcut id="pointcut1" expression="execution(* *(..))"/>
        <!--切面-->
        <aop:aspect ref="advices">
            <!--method="before" 是增强类中的方法-->
            <aop:before  method="before" pointcut-ref="pointcut1"></aop:before>
            <!--后置增强-->
            <aop:after-returning  method="after" pointcut-ref="pointcut1"></aop:after-returning>
        </aop:aspect>
    </aop:config>

3.2、增强类型

AOP联盟为增强定义了Advice接口,Spring支持五种类型的增强

  1. 前置增强: org.springframework.aop.BeforeAdvice 代表前置增强,因为Spring目前只支持方法级的增强,所以MethodBeforeAdvice是目前可用的前置增强,表示在目标方法执行前实施置增强,而BeforeAdvice是为了将来扩展而定义的。

  2. 后置增强: org.springframework.aop.AfterReturningAdvice 代表后置增强,表示在目标方法执行后实施增强。

  3. 环绕增强: org.aopalliance.intercept.MethodInterceptor 代表环绕增强,表示在目标方法的执行前后实施增强。

  4. 异常抛出增强: org.springframework.aop.ThrowsAdvice 代表抛出异常增强,表示在目标方法抛出异常后实施增强。

  5. 引介增强: org.springframework.aop.IntroductionInterceptor 代表引介增强,表示在目标类中添加一些新的方法和属性。

4、spring AOP的使用

spring AOP的使用大体分为两种:

  1. ProxyFactoryBean:
    通过spring 为我们提供的org.springframework.aop.framework.ProxyFactoryBean bean来实现。(不常用,但是能了解AOP原理
  2. AspectJ切面框架:
    通过AspectJ切面框架实现,spring官方也提倡使用常用

4.1、 ProxyFactoryBean(不常用)

  1. 目标对象:
//定义动物接口 
public interface Animal {
    public void run();
    public void eat();

}
 //狗的实现类 
public class Dog implements Animal{

    public void run() {
        System.out.println("跑");
    }

    public void eat() {
        System.out.println("吃");
    }

}
  1. 增强:
  • 前置增强
//我们使实现方法前置增强接口,并写入我们的横切逻辑代码
public class MyBeforeAdvice implements MethodBeforeAdvice {

    //method为目标类的方法,args为目标类方法的所需参数,obj为目标类实例
    public void before(Method method, Object[] args, Object obj) throws Throwable {
        System.out.println("主人发出命令");

    }

}
  • 后置增强
/**
 * 后置增强
 * @author 15594
 */
public class MyAfterAdvice implements AfterReturningAdvice {

    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("主人给予奖励");
    }
}
  • 环绕增强
/**
 * 环绕增强
 * @author 15594
 */
public class MyMethodInterceptor implements MethodInterceptor  {
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {

        System.out.println("主人发出命令");
        //使用proceed()方法反射调用目标类相应的方法
        Object proceed = methodInvocation.proceed();
        System.out.println("主人给予奖励");
        return proceed;
    }
}
  • 异常增强
/**
 * 异常增强:<br>
 * ThrowsAdvice异常抛出增强接口没有定义任何方法,它是一个标识接口,在运行期Spring使用反射机制自行判断,
 * 我们必须使用以下签名形式定义异常抛出的增强方法。
 * @author 15594
 */
public class MyThrowsAdvice implements ThrowsAdvice {

    /**
     *
     * 方法名必须为afterThrowing,前三个参数是可选的,最后一个参数是Throwable或者其子类。
     * 可以在同一个增强中定义多个afterThrowing()方法,当目标类方法抛出异常时,
     * Spring会自动选用最匹配的增强方法,主要是根据异常类的匹配程度。
     * */
    public void afterThrowing(Method method, Object[] args, Object target, Exception e)throws Throwable{
        System.out.println(method.getName());
        System.out.println(e.getMessage());
        System.out.println("回滚事务");
    }
}


/**
 * 会发生异常的user查询
 * @author 15594
 */
public class ThrowsUserMapper {

    /**
     * 模拟查询异常
     * */
    public void queryUsers(){
        throw  new RuntimeException("查询users发生异常");
    }
}
  1. 配置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="target1" class="com.lihua.AOP.AOP.Dog"/>
    <bean id="advice1" class="com.lihua.AOP.AOP.MyBeforeAdvice"/>
    <bean id="beforeProxyService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--指定代理的接口(目标对象实现的接口),有多个的话可以使用数组、list等标签
        注意:这一步只有jdk代理需要-->
        <!--单个接口     <property name="proxyInterfaces" value="com.lihua.AOP.AOP.Animal"/> -->
        <property name="proxyInterfaces">
            <list value-type="com.lihua.AOP.AOP.Animal"/>
        </property>
        <!--前置增强-->
        <property name="interceptorNames" value="advice1"/>
        <!--目标对象-->
        <property name="target" ref="target1"/>
    </bean>

    <!--后置增强与前置一样的配置-->



    <!--===============环绕增强==============-->
    <bean id="target2" class="com.lihua.AOP.AOP.Dog"></bean>
    <bean id="advice2" class="com.lihua.AOP.AOP.MyMethodInterceptor"></bean>

    <!--生成环绕代理对象-->
    <bean id="surroundAdvice" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces" value="com.lihua.AOP.AOP.Animal"></property>
        <property name="interceptorNames" value="advice2"></property>
        <property name="target" ref="target2"></property>
    </bean>



    <!-- ============异常增强=============-->
    <!--目标对象-->
    <bean id="target" class="com.lihua.AOP.AOP.ThrowsUserMapper"></bean>
    <!--异常增强-->
    <bean id="advice" class="com.lihua.AOP.AOP.MyThrowsAdvice"></bean>
    <!--经过异常增强的代理对象-->
    <bean id="proxyServiceThrows" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="interceptorNames" value="advice"/>
        <property name="target" ref="target"/>
        <!--代理类型,因为我们的业务类(ThrowsUserMapper)没有实现接口,因此使用CGLib代理-->
        <property name="proxyTargetClass" value="true"/>
    </bean>
</beans>
  1. 测试
/**
 * @author 15594
 */
public class Test {
    /**
     * 前置增强测试
     * */
    @org.junit.Test
    public void beforeAdvice() {
        Animal dog=new Dog();
        //获得前置增强
        BeforeAdvice advice=new MyBeforeAdvice();
        //Spring提供的代理工厂
        ProxyFactory pf=new ProxyFactory();
        //设置代理目标
        pf.setTarget(dog);
        //为代理目标添加增强
        pf.addAdvice(advice);
        //生成代理对象
        Animal proxy=(Animal)pf.getProxy();
        proxy.eat();
        proxy.run();

        System.out.println("当然我们也可以通过配置spring.xml文件来生成代理对象");

        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        Animal animalProxy = (Animal)context.getBean("beforeProxyService");
        animalProxy.eat();
    }

    /**
     * 后置增强
     * */
    @org.junit.Test
    public void afterAdvice(){
        //后置增强
        AfterReturningAdvice afterAdvice  = new MyAfterAdvice();
        //目标对象
        Animal dog= new Dog();
        //spring提供的代理工厂,生成代理操作对象
        ProxyFactory pf = new ProxyFactory();
        //设置目标对象
        pf.setTarget(dog);
        //设置增强
        pf.addAdvice(afterAdvice);
        //获取代理对象
        Dog proxyDog = (Dog)pf.getProxy();
        proxyDog.eat();

    }

    /**
     * 环绕增强
     *
     * */
    @org.junit.Test
    public  void surroundAdvice(){

        //环绕增强
        MethodInterceptor surroundAdvice =  new MyMethodInterceptor();
        //目标对象
        Animal dog= new Dog();
        //spring提供的代理工厂,生成代理操作对象
        ProxyFactory pf = new ProxyFactory();
        //设置目标对象
        pf.setTarget(dog);

        //设置增强
        pf.addAdvice(surroundAdvice);

        //获取代理对象
        Dog proxyDog = (Dog)pf.getProxy();
        proxyDog.eat();
        System.out.println("当然我们也可以通过配置spring.xml文件来生成代理对象");

        ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
        Animal animalProxy = (Animal)context.getBean("surroundAdvice");
        animalProxy.eat();
    }



    /**
     * 异常增强
     * */
    @org.junit.Test
    public void throwsAdvice(){

        //代理的过程需要配合spring.xml完成
        ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");

        //获取代理对象
        ThrowsUserMapper userMapper = (ThrowsUserMapper) ac.getBean("proxyServiceThrows");
        try {
            userMapper.queryUsers();
        }catch (RuntimeException e){
            System.out.println("处理抛出的异常");
        }

    }
}

增加切点筛选需要增强的方法:

切点算法

/**
 * 切点算法,用来筛选方法
 * <b>如果不设置切点,那么目标对象所有的方法都会被增强</b><br
 * 获取切点(编写一个匹配算法,筛选出切点)
 * @author 15594
 */
public class MyPointcut extends StaticMethodMatcherPointcutAdvisor {
    /**
     * 匹配方法名称为eat的方法
     * */
    public boolean matches(Method method, Class<?> aClass) {
        return method.getName().equals("eat");
    }

    /**
     * 但是默认情况,该切面匹配所有类,我们通过覆盖getClassFilter方法,仅匹配Dog及其子类
     *
     * 我们通过覆盖getClassFilter方法,仅匹配Dog及其子类.
     * */
    @Override
    public ClassFilter getClassFilter() {
        return new ClassFilter(){
            public boolean matches(Class<?> clazz) {
                return Dog.class.isAssignableFrom(clazz);
            }
        };
    }
}

配置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">
    <!--Dog目标类-->
    <bean id="targetDog" class="com.lihua.AOP.AOP.Dog"/>
    <!--增强类-->
    <bean id="advice" class="com.lihua.AOP.AOP.切面和切点.MyBeforeAdvice"/>
    <!--切面,里面包含增强,还有一个classFilter属性,
    可以指定类匹配过滤器,
    但我们在类里面已经通过覆盖来实现类匹配过滤器,所以不用配置-->
    <bean id="myPointcut" class="com.lihua.AOP.AOP.切面和切点.MyPointcut">
        <property name="advice" ref="advice"/>
    </bean>

    <!--生成代理对象-->
    <bean id="dogProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--配置切面-->
        <property name="interceptorNames" value="myPointcut"/>
        <!--使用cglib代理-->
        <property name="proxyTargetClass" value="true"/>
        <!--目标对象-->
        <property name="target" ref="targetDog"/>
    </bean>
</beans>

总的来说:使用 ProxyFactoryBean方式实现AOP需要三步:

  1. 编写增强类(方法)
  2. 编写切点算法(当然如果不需要筛选,可以不写)
  3. 将增强、目标对象、切点配置到ProxyFactoryBean中
  4. 生成代理对象

此外:我们发现上面的做法都是给一个个目标对象织入增强。当我们想要给很多目标对象织入一些增强时怎么办?要一个个织入吗?当然不用,我们可以通过以下配置让spring 帮我们筛选目标对象自动织入增强。配置如下:

<?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="targetDog" class="com.lihua.AOP.AOP.AutoProxy.Dog"></bean>
    <bean id="targetCat" class="com.lihua.AOP.AOP.AutoProxy.Cat"></bean>
    <!--增强类-->
    <bean id="advice" class="com.lihua.AOP.AOP.AutoProxy.MyAfterAdvice"></bean>

    <!--配置BeanNameAutoProxyCreator实现类-->
    <bean  class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <!--beanNames属性允许用户指定需要自动代理的Bean名称,可以使用*通配符,例如*t,就会匹配cat-->
        <property name="beanNames" value="*"></property>
        <property name="interceptorNames" value="advice"></property>
        <!--指定使用CGLib-->
        <property name="optimize" value="true"></property>
    </bean>

</beans>

4.2、 AspectJ切面框架(常用)

AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
Spring2.0之后增加了对AspectJ切点表达式的支持。@AspectJ是AspectJ1.5新增的功能,通过JDK的注解技术,允许开发者在Bean上直接通过注解定义切面。Spring使用和@AspectJ相同风格的注解,并通过AspectJ提供的注解库和解析库来处理切点。

1) 切点表达式

AspectJ中最重要的是切点表达式。

  • 通配符
* 匹配任意字符,只匹配一个元素
.. 匹配任意字符,可以匹配多个元素 ,在表示类时,必须和*联合使用
+ 表示按照类型匹配指定类的所有类,必须跟在类名后面,如com.cad.Car+,表示继承该类的所有子类包括本身
  • 逻辑运算符
切点表达式由切点函数组成,切点函数之间还可以进行逻辑运算,组成复合切点。
- && :与操作符。相当于切点的交集运算。xml配置文件中使用切点表达式,&是特殊字符,所以需要转义字符&;来表示。
- || :或操作符。相当于切点的并集运算。
- !:非操作符,相当于切点的反集运算。
  • 切点表达式
    语法:execution(<修饰符><返回类型><包.类.方法(参数)><异常>)
使用例子 :
- execution(public * *(..)) : 匹配目标类的所有public方法,第一个*代表返回类型,第二个*代表方法名,..代表方法的参数。
- execution(**User(..)) :匹配目标类所有以User为后缀的方法。第一个*代表返回类型,*User代表以User为后缀的方法
- execution(* com.cad.demo.User.*(..)) :匹配User类里的所有方法
- execution(* com.cad.demo.User+.*(..)) :匹配该类的子类包括该类的所有方法
- execution(* com.cad.*.*(..)) :匹配com.cad包下的所有类的所有方法
- execution(* com.cad..*.*(..)) :匹配com.cad包下、子孙包下所有类的所有方法
- execution(* addUser(Spring,int)) :匹配addUser方法,且第一个参数类型是String,第二个是int

除了execution外,还有以下表达式:

  • args()
    该函数接受一个类名,表示目标类方法参数是指定类时(包含子类),则匹配切点。
args(com.cad.User):匹配addUser(User user)方法等
  • within()匹配类
    语法:within(<类>)
within(com.cad.User):匹配User类下的所有方法
  • target()
    target()函数通过判断目标类是否按类型匹配指定类决定连接点是否匹配。
例如 target(com.cad.User):如果目标类类型是User没那么目标类所有方法都匹配切点。
  • this()
    this()函数判断代理对象的类是否按类型匹配指定类。

2) 增强类型

  • Before:前置增强。相当于BeforeAdvice的功能,方法执行前执行。

  • AfterReturning:后置增强。相当于AfterReturningAdvice,方法执行后执行。

  • Around:环绕增强。

  • AfterThrowing:异常抛出增强。

  • After:不管是抛出异常还是正常退出,该增强都会执行,类似于finally块

  • DeclareParents:引介增强。

3) 通过配置xml实现

增强方法

/**
 *
 * 注意:使用AspectJ,增强不需要实现增强接口
 * @author 15594
 */
public class MyAdvice {

    /**
     * 前置增强 ,方法根据逻辑自己定义
     * */
    public void before() {
        System.out.println("主人发出命令");
    }

    /**
     * 后置增强
     * */
    public void after() {
        System.out.println("主人给予奖励");
    }
}

配置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"
       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
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--注意需要引入aop命名空间-->

    <!--实例Dog类:目标对象-->
    <bean id="dog" class="com.lihua.AOP.AOP.Dog"></bean>
    <bean id="cat" class="com.lihua.AOP.AOP.AutoProxy.Cat"></bean>

    <!--增强类-->
    <bean id="advices" class="com.lihua.AOP.AOP.AspectJ.MyAdvice"></bean>

    <!--配置aop,proxy-target-class="true"表示使用cglib动态代理-->
    <aop:config proxy-target-class="true">
        <!--切点,切点表达式-->
        <aop:pointcut id="pointcut1" expression="execution(* *(..))"/>
        <!--切面-->
        <aop:aspect ref="advices">
            <!--method="before" 是增强类中的方法-->
            <aop:before  method="before" pointcut-ref="pointcut1"></aop:before>
            <!--后置增强-->
            <aop:after-returning  method="after" pointcut-ref="pointcut1"></aop:after-returning>
        </aop:aspect>
    </aop:config>

</beans>

测试:

package com.lihua.AOP.AOP.AspectJ;

import com.lihua.AOP.AOP.AutoProxy.Cat;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 *
 * 测试基于切面框架AspectJ的AOP
 * @author 15594
 */
public class TestAspectJ {
    @Test
    public void  test(){
        ApplicationContext app = new ClassPathXmlApplicationContext("AspectJ.xml");
        Cat bean = (Cat) app.getBean("cat");
        bean.eat();
    }

}

4) 通过注解实现(推荐)

目标对象:

package com.lihua.AOP.AOP.AspectJ.注解实现AspectJ;

import org.springframework.stereotype.Component;

@Component("Dog")
public class Dog  {
    public void run() {
        System.out.println("跑");
    }

    public void eat() {
        System.out.println("吃");
    }
}

增强

package com.lihua.AOP.AOP.AspectJ.注解实现AspectJ;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 通过注解实现
 * @author 15594
 */
//等价于    <bean id="advices" class="com.lihua.AOP.AOP.AspectJ.MyAdvice"></bean>
@Component("Advice")
//等价于<aop:aspect ref="advices"></aop:aspect>
@Aspect
public class MyAdvice {
    /**
     * 前置增强 ,方法根据逻辑自己定义
     * */
    @Before("execution(* com.lihua.AOP.AOP.AspectJ.注解实现AspectJ.Dog.run(..))")
    public void before() {
        System.out.println("主人发出命令");
    }
    /**
     * 后置增强
     * */
    @AfterReturning("execution(* com.lihua.AOP.AOP.AspectJ.注解实现AspectJ.Dog.e*(..))")
    public void after() {
        System.out.println("主人给予奖励");
    }
}

配置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"
       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
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--扫描将bean注册到spring 容器-->
    <context:component-scan base-package="com.lihua.AOP.AOP.AspectJ.注解实现AspectJ"/>
    <!--开启注解驱动  proxy-target-class="true"使用cglib代理-->
    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>

测试:

package com.lihua.AOP.AOP.AspectJ.注解实现AspectJ;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 注解方式实现AspectJ
 * @author 15594
 */
public class MyTest {
    public static void main(String[] args) {
       ApplicationContext app = new ClassPathXmlApplicationContext("AspectJ1.xml");
        Dog dog = (Dog)app.getBean("Dog");
        dog.eat();
        dog.run();
    }
}

六、参考:

Spring 深入浅出核心技术 (一)

这篇文章AOP讲得很好:
Spring 深入浅出核心技术(二)

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-09-12 13:02:20  更:2021-09-12 13:03:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 16:48:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码