Spring笔记(2):IOC、AOP

目录

0、写在前面

1、Spring框架概述

2、IOC容器

2.1、IOC概念和原理:

2.2、IOC操作Bean管理(基于xml)

IOC操作Bean管理(FactoryBean)

Bean的作用域:

Bean的生命周期:

xml自动装配(很少用,一般用注解):

通过外部属性文件来操作bean:

2.3. IOC操作Bean管理(基于注解)

(1)基于注解创建对象:

(2)基于注解进行属性注入:

(3)完全注解开发:

3、AOP

3.1. 底层原理

3.2. 基于AspectJ实现AOP操作

(1)基于注解方式

(2)基于xml方式


0、写在前面

本篇文章是学习完b站尚硅谷Spring5所做的笔记,希望对大家有所帮助!

1、Spring框架概述

  • Spring 是轻量级的开源的 JavaEE 框架
  • Spring 可以解决企业应用开发的复杂
  • Spring 有两个核心部分:IOC 和 Aop
    • (1)IOC:控制反转,把创建对象和对象间的调用过程交给 Spring 进行管理
    • (2)Aop:面向切面,不修改源代码进行功能增强
  • Spring 特点 :
    • 方便解耦,简化开发
    • Aop 编程支持
    • 方便程序测试
    • 方便和其他框架进行整合
    • 方便进行事务操作
    • 降低 API 开发难度

2、IOC容器

2.1、IOC概念和原理:

1、概念:控制反转,把对象创建和对象的调用过程交给spring进行管理

2、目的:降低耦合度

3、底层原理:xml解析,反射,工厂模式。

修改工厂类即可

修改配置文件即可:

4、Spring提供IOC容器两种实现方式(两个接口)

  • BeanFactory:Spring内部使用的接口,不提倡开发人员使用。
    • 特点:加载配置文件时不会创建对象,获取对象时才会创建对象。
  • **ApplicationContext:**BeanFactory的子接口,提供了更多更强大的功能,一般由开发人员使用。
    • 特点:加载配置文件时会把配置文件里的对象进行创建。一般在耗时过程放在系统启动时完成,减少web服务的耗时。日常使用。

5、ApplicationContext两个常用实现类:

  • FileSystemXmlApplicationContext:绝对路径,从盘符开始算起
  • ClassPathXmlApplicationContext:相对路径,从src开始算起

image-20210720000922536

 6、什么是Bean管理?

  • Bean管理是指两个操作:Spring创建对象 和 Spring注入属性
  • Bean管理有两种操作方式:基于xml配置文件方式实现 和 基于注解方式实现

2.2、IOC操作Bean管理(基于xml)

xml实现Bean管理:
(1)基于xml方式创建对象:

image-20210719101725911

  • 在Spring配置文件中使用bean标签来创建对象
  • bean标签有很多属性,常用属性:
    • id:唯一标识
    • class:类路径
  • 创建对象时,默认执行无参构造函数

(2)基于xml方式注入属性: 

第一种方法:使用set方法进行注入:

首先先为类的属性提供set方法:

public class User {

    private String userName;
    private String userAge;

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setUserAge(String userAge) {
        this.userAge = userAge;
    }

    public String getUserName() {
        return userName;
    }

    public String getUserAge() {
        return userAge;
    }
}

然后在xml配置文件中通过property标签进行属性注入

    <!--配置User对象-->
    <bean id="user" class="com.oymn.spring5.User">
        <property name="userName" value="haha"></property>
        <property name="userAge" value="18"></property>
    </bean>

这样就完成了

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml");
    User user = applicationContext.getBean("user", User.class);
    System.out.println(user.getUserName() + "     " + user.getUserAge());

第二种方法:使用有参构造函数进行注入

首先提供有参构造方法

public class User {

    private String userName;
    private String userAge;

    public User(String userName, String userAge){
        this.userName = userName;
        this.userAge = userAge;
    }
}

然后再xml配置文件中通过constructor-arg标签进行属性注入

    <!--配置User对象-->
    <bean id="user" class="com.oymn.spring5.User">
        <constructor-arg name="userName" value="haha"></constructor-arg>
        <constructor-arg name="userAge" value="18"></constructor-arg>
    </bean>

第三种方法:p名称空间注入(了解即可)

首先在xml配置文件中添加p名称空间,并且在bean标签中进行操作

image-20210719104230761

 然后提供set方法

public class User {

    private String userName;
    private String userAge;

    public User() {
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setUserAge(String userAge) {
        this.userAge = userAge;
    }
}

(3)xml注入其他属性

1、null值

    <!--配置User对象-->
    <bean id="user" class="com.oymn.spring5.User">
        <property name="userName"> <null/> </property>
    </bean>

2、属性值包含特殊符号

假设现在userName属性需要赋值为 < haha >

如果像上面那样直接在value中声明的话会报错,因为包含特殊符号 <>

image-20210720003501206

 需要通过 <![CDATA[值]]> 来表示

image-20210720003720138

 3、注入属性——外部bean

有两个类:UserService和UserDaoImpl,其中UserDaoImpl实现UserDao接口

public class UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao){
        this.userDao = userDao;
    }

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

通过 ref 来指定创建userDaoImpl

<bean id="userDaoImpl" class="com.oymn.spring5.UserDaoImpl"></bean>

<bean id="userService" class="com.oymn.spring5.UserService">
    <property name="userDao" ref="userDaoImpl"></property>
</bean>

4、注入属性——内部bean

不通过ref属性,而是通过嵌套一个bean标签实现

<!--内部 bean-->
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
     <!--设置两个普通属性-->
     <property name="ename" value="lucy"></property>
     <property name="gender" value="女"></property>
     <!--设置对象类型属性-->
     <property name="dept">
         <bean id="dept" class="com.atguigu.spring5.bean.Dept">
        	 <property name="dname" value="安保部"></property>
         </bean>
     </property>
</bean>

5、注入属性——级联赋值

写法一:也就是上面所说的外部bean,通过ref属性来获取外部bean

写法二:emp类中有ename和dept两个属性,其中dept有dname属性,写法二需要emp提供dept属性的get方法。

<!--级联赋值-->
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
    <!--设置两个普通属性-->
    <property name="ename" value="lucy"></property> <property name="gender" value="女"></property>
    <!--写法一-->
	<property name="dept" ref="dept"></property>
    <!--写法二-->
    <property name="dept.dname" value="技术部"></property>
</bean>
<bean id="dept" class="com.atguigu.spring5.bean.Dept">
    <property name="dname" value="财务部"></property>
</bean>

6、注入集合属性(数组,List,Map)

假设有一个Stu类

public class Stu {

    private String[] courses;
    private List<String> list;
    private Map<String,String> map;
    private Set<String> set;

    public void setCourses(String[] courses) {
        this.courses = courses;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public void setSet(Set<String> set) {
        this.set = set;
    }
}

在xml配置文件中对这些集合属性进行注入

<bean id="stu" class="com.oymn.spring5.Stu">
    <!--数组类型属性注入-->
    <property name="courses">
        <array>
            <value>java课程</value>
            <value>数据库课程</value>
        </array>
    </property>
    <!--List类型属性注入-->
    <property name="list">
        <list>
            <value>张三</value>
            <value>李四</value>
        </list>
    </property>
    <!--Map类型属性注入-->
    <property name="map">
        <map>
            <entry key="JAVA" value="java"></entry>
            <entry key="PHP" value="php"></entry>
        </map>
    </property>
    <!--Set类型属性注入-->
    <property name="set">
        <set>
            <value>Mysql</value>
            <value>Redis</value>
        </set>
    </property>
</bean>

7、上面的集合值都是字符串,如果是对象的话,如下:

写法: 集合+外部bean

<!--创建多个 course 对象-->
<bean id="course1" class="com.atguigu.spring5.collectiontype.Course">
	<property name="cname" value="Spring5 框架"></property>
</bean>
<bean id="course2" class="com.atguigu.spring5.collectiontype.Course">
	<property name="cname" value="MyBatis 框架"></property>
</bean>

<!--注入 list 集合类型,值是对象-->
<property name="courseList">
    <list>
        <ref bean="course1"></ref>
        <ref bean="course2"></ref>
    </list>
</property>

8、把集合注入部分提取出来

使用 util 标签,这样不同的bean都可以使用相同的集合注入部分了。

<!--将集合注入部分提取出来-->
<util:list id="booklist">
    <value>易筋经</value>
    <value>九阳神功</value>
</util:list>

<bean id="book" class="com.oymn.spring5.Book">
    <property name="list" ref="booklist"></property>
</bean>

IOC操作Bean管理(FactoryBean)

Spring有两种Bean

  • 一种是普通Bean:在配置文件中定义bean类型就是返回类型
@Data
public class Course {

    private String cname;
}
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

   <bean id="myBean" class="factorybean.MyBean">

   </bean>

</beans>

public class TestSpring5Demo {

    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        MyBean myBean = context.getBean("myBean", MyBean.class);
        System.out.println(myBean);//factorybean.MyBean@68ceda24
    }
}
  • 另一种是工厂Bean(FactoryBean):在配置文件定义bean类型可以和返回类型不一样。
    • 第一步:创建类,让这个类作为工厂bean,实现接口FactoryBean
    • 第二步:实现接口里面的方法,在实现的方法中定义返回的bean类型
@Data
public class Course {

    private String cname;
}

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

   <bean id="myBean" class="factorybean.MyBean">

   </bean>

</beans>


public class MyBean implements FactoryBean<Course> {


    //定义返回bean,此时返回定义的范型Course,而不是xml中定义的MyBean
    @Override
    public Course getObject() throws Exception {
        Course course = new Course();
        course.setCname("abc");
        return course;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

public class TestSpring5Demo {
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        Course course = context.getBean("myBean", Course.class);
        System.out.println(course);//Course(cname=abc)
    }

}

Bean的作用域:

  • 在Spring中,默认情况下bean是单实例对象

image-20210719113035226

执行结果是相同的:

image-20210719113122345

通过 bean标签的scope属性 来设置单实例还是多实例。
Scope属性值:

  • **singleton:**默认值,表示单实例对象。加载配置文件时就会创建单实例对象
  • prototype:表示多实例对象。不是在加载配置文件时创建对象,在调用getBean方法时创建多实例对象。

image-20210719113500730

 执行结果不同了:

image-20210719113518353

Bean的生命周期:

Spring完整生命周期从 创建Spring容器开始,直到最终Spring容器销毁bean:

(1)通过构造器创建 bean 实例(执行无参数构造)

(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)

(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization

(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)

(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization

(6)bean 可以使用了(对象获取到了)

(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

演示bean的生命周期

public class Orders {
    private String orderName;

    public Orders() {
        System.out.println("第一步:执行无参构造方法创建bean实例");
    }

    public void setOrderName(String orderName) {
        this.orderName = orderName;
        System.out.println("第二步:调用set方法设置属性值");
    }

    //初始化方法
    public void initMethod(){
        System.out.println("第四步:执行初始化方法");
    }

    //销毁方法
    public void destroyMethod(){
        System.out.println("第七步:执行销毁方法");
    }
}
//实现后置处理器,需要实现BeanPostProcessor接口
public class MyBeanPost implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第三步:将bean实例传递给bean后置处理器的postProcessBeforeInitialization方法");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第五步:将bean实例传递给bean后置处理器的postProcessAfterInitialization方法");
        return bean;
    }
}

<bean id="orders" class="com.oymn.spring5.Orders" init-method="initMethod" destroy-method="destroyMethod">
    <property name="orderName" value="hahah"></property>
</bean>

<!--配置bean后置处理器,这样配置后整个xml里面的bean用的都是这个后置处理器-->
<bean id="myBeanPost" class="com.oymn.spring5.MyBeanPost"></bean>
@Test
public void testOrders(){

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");

    Orders orders = context.getBean("orders", Orders.class);

    System.out.println("第六步:获取bean实例对象");
    System.out.println(orders);

    //手动让bean实例销毁
    context.close();
}

执行结果:

image-20210720122628081

xml自动装配(很少用,一般用注解):

根据指定的装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入

  • 根据属性名称自动装配:要求 emp类中属性的名称dept 和 bean标签的id值dept 一样,才能识别:

<!--指定autowire属性值为byName-->
<bean id="emp" class="com.oymn.spring5.Emp" autowire="byName"></bean>

<bean id="dept" class="com.oymn.spring5.Dept"></bean>
  • 根据属性类型自动装配:要求同一个xml文件中不能有两个相同类型的bean,否则无法识别是哪一个:

<!--指定autowire属性值为byType-->
<bean id="emp" class="com.oymn.spring5.Emp" autowire="byType"></bean>

<bean id="dept" class="com.oymn.spring5.Dept"></bean>
  • 普通装配实现:
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

   <bean id="emp" class="autowired.Emp">
      <property name="dept" ref="dept"></property>
   </bean>
   <bean id="dept" class="autowired.Dept">

</beans>

public class Dept {

}
@ToString
@Data
public class Emp {

    private Dept dept;

    public void test(){
        System.out.println(dept);
    }
}

    @Test
    public void testEmp(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
        Emp emp = context.getBean("emp", Emp.class);
        System.out.println(emp);
    }
  • 自动装配实现:
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

   <!-- 实现自动装配
         bean标签属性autowired,配置自动装配
         autowired属性常用两个值:
            byName:根据属性名称注入,注入值bean的id值 和 类属性名称一样
            byType:根据属性类型注入
      -->
   <bean id="emp" class="autowired.Emp" autowire="byName">
   </bean>
   <bean id="dept" class="autowired.Dept"></bean>

</beans>

public class Dept {

}

@ToString
@Data
public class Emp {

    private Dept dept;

    public void test(){
        System.out.println(dept);
    }
}

    @Test
    public void testEmp(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
        Emp emp = context.getBean("emp", Emp.class);
        System.out.println(emp);
    }

通过外部属性文件来操作bean:

例如配置数据库信息:

  1. 导入德鲁伊连接池jar包

  2. 创建外部属性文件,properties格式文件,写数据库信息

image-20210731004522456

      3.引入context名称空间,通过context标签引入外部属性文件,使用“${}”获取文件中对应的值

image-20210731010320233







2.3. IOC操作Bean管理(基于注解)

  • 格式:@注解名称(属性名=属性值,属性名=属性值,……)
  • 注解可以作用在类,属性,方法。
  • 使用注解的目的:简化xml配置

(1)基于注解创建对象:

spring提供了四种创建对象的注解:

  • @Component
  • @Service:一般用于Service层
  • @Controller:一般用于web层
  • @ Repository:一般用于Dao层

流程:

1. 引入依赖:

2. 开启组件扫描:扫描base-package包下所有有注解的类并为其创建对象

<context:component-scan base-package="com.oymn"></context:component-scan>

3. com.oymn.spring5.Service有一个stuService类

//这里通过@Component注解来创建对象,括号中value的值等同于之前xml创建对象使用的id,为了后面使用时通过id来获取对象
//括号中的内容也可以省略,默认是类名并且首字母小写
//可以用其他三个注解
@Component(value="stuService")
public class StuService {
    public void add(){
        System.out.println("addService");
    }
}

4. 这样就可以通过getBean方法来获取stuService对象了

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
StuService stuService = context.getBean("stuService", StuService.class);
System.out.println(stuService);
stuService.add();

开启组件扫描的细节配置:

  1. use-default-fileters设置为false表示不使用默认过滤器,通过include-filter来设置只扫描com.oymn包下的所有@Controller修饰的类。
<context:component-scan base-package="com.oymn" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

 2. exclude-filter设置哪些注解不被扫描,例子中为@Controller修饰的类不被扫描

<context:component-scan base-package="com.oymn">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

(2)基于注解进行属性注入:

  • @Autowired根据属性类型自动装配

    创建StuDao接口和StuDaoImpl实现类,为StuDaoImpl添加创建对象注解

public interface StuDao {
    public void add();
}
@Repository
public class StuDaoImpl implements StuDao {
    @Override
    public void add() {
        System.out.println("StuDaoImpl");
    }
}

StuService类中添加StuDao属性,为其添加@Autowire注解,spring会自动为stuDao属性创建StuDaoImpl对象

@Component(value="stuService")
public class StuService {
    
    @Autowired
    public StuDao stuDao;

    public void add(){
        System.out.println("addService");
        stuDao.add();
    }
}
@Test
public void test1(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
    StuService stuService = context.getBean("stuService", StuService.class);
    System.out.println(stuService);
    stuService.add();
}
  • 测试结果:

    image-20210731164052536

  • @Qualifier根据属性名称自动装配

    当遇到一个接口有很多实现类时,只通过@Autowire是无法完成自动装配的,所以需要再使用@Qualifier通过名称来锁定某个类

    @Component(value="stuService")
    public class StuService {
    
        @Autowired
        @Qualifier(value="stuDaoImpl")  //这样就能显式指定stuDaoImpl这个实现类
        public StuDao stuDao;
    
        public void add(){
            System.out.println("addService");
            stuDao.add();
        }
    }
    

     @Resource可以根据类型注入,也可以根据名称注入

  • @Resources不是spring中的,是java扩展包import javax.annotation.Resource;中,spring不建议使用。
@Component(value="stuService")
public class StuService {
    
    //@Resource   //根据类型进行注入
    @Resource(name="stuDaoImpl")  //根据名称进行注入
    public StuDao stuDao;

    public void add(){
        System.out.println("addService");
        stuDao.add();
    }
}

@Value注入普通类型属性

@Value(value = "abc")
private String name;

(3)完全注解开发:

创建配置类,替代xml配置文件

@Configuration    //表明为一个配置类
@ComponentScan(basePackages = "com.oymn")   //开启组件扫描
public class SpringConfig {
}

测试类:

@Test
public void test2(){
    //创建AnnotationConfigApplicationContext对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    StuService stuService = context.getBean("stuService", StuService.class);
    System.out.println(stuService);
    stuService.add();
}

3、AOP

3.1. 底层原理

  • 面向切面编程:利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
  • 通俗来说就是:在不修改代码的情况下添加新的功能

  • AOP底层通过动态代理来实现:
    • 第一种:有接口的情况,使用JDK动态代理:创建接口实现类的代理对象,增强类的方法

  • 第二种:无接口的情况,使用CGLIB动态代理:创建当前类子类的代理对象,增强类的方法

JDK动态代理举例:

通过 java.lang.reflect.Proxy类 的 newProxyInstance方法 创建代理类。

  • newProxyInstance方法:

image-20210801004308007

  • 参数一:类加载器
  • 参数二:所增强方法所在的类,这个类实现的接口,支持多个接口
  • 参数三:实现InvocationHandle接口,重写invoke方法来添加新的功能

代码举例:

public interface UserDao {
    public int add(int a, int b);
    public int multi(int a, int b);
}
public class UserDaoImpl implements UserDao {
    @Override
    public int add(int a, int b) {
        System.out.println("add方法执行了...");
        return a+b;
    }

    @Override
    public int multi(int a, int b) {
        System.out.println("add方法执行了...");
        return a*b;
    }
}

public class JDKProxy {

    public static void main(String[] args) {
        //所需代理的类实现的接口,支持多个接口
        Class[] interfaces = {UserDao.class};

        //类加载器
        UserDaoImpl userDao = new UserDaoImpl();
        //调用newProxyInstance方法来创建代理类
        UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao));
        int res = dao.add(1,2);
        System.out.println(res);
    }

//方法执行之前add传递的参数[1, 2]
//add方法执行了...
//方法之后执行com.jdkproxy.UserDaoImpl@15aeb7ab
//3
}

//创建代理对象
class UserDaoProxy implements InvocationHandler{

    //1 把创建的是谁的代理对象,把谁传递过来
    //有参构造函数
    private Object obj;
    //通过有参构造函数将所需代理的类传过来
    public UserDaoProxy(Object obj){
        this.obj = obj;
    }

    //增强逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法执行之前
        System.out.println("方法执行之前"+method.getName()+"传递的参数"+ Arrays.toString(args));

        //被增强的方法执行,//执行原有的代码
        Object res = method.invoke(obj, args);
        //方法之后
        System.out.println("方法之后执行"+obj);
        return res;
    }
}

运行结果:

方法执行之前add传递的参数[1, 2]
add方法执行了...
方法之后执行com.jdkproxy.UserDaoImpl@15aeb7ab
3

3.2. 基于AspectJ实现AOP操作

(1)AOP相关术语:

  • 连接点:类中可以被增强的方法,称为连接点。
  • 切入点:实际被增强的方法,称为切入点。例如:登陆的逻辑。
  • 通知(增强):增强的那一部分逻辑代码。例如:增加权限判断。通知有多种类型:
    • 前置通知:增强部分代码在原代码前面。
    • 后置通知:增强部分代码在原代码后面。
    • 环绕通知:增强部分代码既有在原代码前面,也有在原代码后面。
    • 异常通知:原代码发生异常后才会执行。
    • 最终通知:类似与finally那一部分
  • 切面:指把通知应用到切入点这一个动作。例如:把权限判断加入到登陆逻辑中。

Spring框架一般都是基于AspectJ实现AOP操作。

什么是AspectJ:

AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ 和 Spring框架一起使用,进行AOP操作。

(2)基于AspectJ实现AOP有两种方式:

  • 基于xml配置文件实现
  • 基于注解方式实现(使用)

(3)切入点表达式

切入点表达式作用:知道对哪个类里面的哪个方法进行增强。

  • 语法:execution([权限修饰符] [返回类型] [类全路径] [方法名称] [参数列表])
  • 举例1:对 com.crane.dao.BookDao 类里面的 add 进行增强,类全路径: com.crane.dao.BookDao

execution(* com.crane.dao.BookDao.add(..))

execution(public 省略 com.crane.dao.BookDao.add(..))

  • 举例2:对 com.crane.dao.BookDao 类里面的所有的方法进行增强

execution(* com.crane.dao.BookDao.*(..))

  • 举例 3:对 com.crane.dao 包里面所有类,类里面所有方法进行增强

execution(* com.crane.dao.*.* (..))

(1)基于注解方式

  • 1、创建类,在类里面定义方法
  • 2、创建增强类,编写增强逻辑
  • 3、进行通知的配置
    • 在spring配置文件中,开启注解扫描
    • 使用注解创建User和UserProxy 对象
    • 在增强类上添加@Aspect
    • 在spring配置文件中开启生成代理对象
  • 4、配置不同类型的通知
    • 在增强类里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置

添加AOP依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.6.1</version>
        </dependency>
//被增强的类
@Component
public class User {
    public void add(){    
        System.out.println("User.add()");
    }
}
@Component
@Aspect   //使用Aspect注解
public class UserProxy {
    //前置通知
    @Before(value="execution(* com.oymn.spring5.User.add(..))")
    public void before(){
        System.out.println("UserProxy.before()");
    }
    
    //后置通知
    @AfterReturning(value="execution(* com.oymn.spring5.User.add(..))")
    public void afterReturning(){
        System.out.println("UserProxy.afterReturning()");
    }
    
    //最终通知
    @After(value="execution(* com.oymn.spring5.User.add(..))")
    public void After(){
        System.out.println("UserProxy.After()");
    }

    //异常通知
    @AfterThrowing(value="execution(* com.oymn.spring5.User.add(..))")
    public void AfterThrowing(){
        System.out.println("UserProxy.AfterThrowing()");
    }

    //环绕通知
    @Around(value="execution(* com.oymn.spring5.User.add(..))")
    public void Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{

        System.out.println("环绕之前");

        //调用proceed方法执行原先部分的代码
        proceedingJoinPoint.proceed();

        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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       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">


   <!--开启组件扫描-->
   <context:component-scan base-package="aopanno"></context:component-scan>
   <!--开启AspectJ生成代理对象-->
   <aop:aspectj-autoproxy></aop:aspectj-autoproxy>


</beans>

测试类:

@Test
public void test2(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    User user = context.getBean("user", User.class);
    user.add();
}

运行结果:

image-20210801210024676

 运行结果中没有出现异常通知,在add方法中添加int i = 1/0;

public void add(){
    int i = 1/0;
    System.out.println("User.add()");
}

运行结果:从这里也可以看到,但出现异常时,After最终通知有执行,而AfterReturning后置通知并没有执行。

image-20210801210304774


对于上面的例子,有很多通知的切入点都是相同的方法,因此,可以将该切入点进行抽取:通过@Pointcut注解

@Pointcut(value="execution(* com.oymn.spring5.User.add(..))")
public void pointDemo(){
    
}

//前置通知
@Before(value="pointDemo()")
public void before(){
    System.out.println("UserProxy.before()");
}

设置增强类优先级:

当有多个增强类对同一方法进行增强时,可以通过**@Order(数字值)来设置增强类的优先级,数字越小优先级越高。**

@Component
@Aspect
@Order(1)
public class PersonProxy{

}

完全注解开发 :

可以通过配置类来彻底摆脱xml配置文件:

@Configuration
@ComponentScan(basePackages = "com.oymn.spring5")
//@EnableAspectJAutoProxy注解相当于上面xml文件中配置的 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@EnableAspectJAutoProxy(proxyTargetClass = true)  
public class Config {

}

(2)基于xml方式

这种方式开发中不怎么用,了解即可。

创建Book和BookProxy类

public class Book {
    public void buy(){
        System.out.println("buy()");
    }
}
public class BookProxy {
    public void before(){
        System.out.println("before()");
    }
}

配置xml文件:

<!--创建对象-->
<bean id="book" class="com.oymn.spring5.Book"></bean>
<bean id="bookProxy" class="com.oymn.spring5.BookProxy"></bean>

<aop:config>
    <!--切入点-->
    <aop:pointcut id="p" expression="execution(* com.oymn.spring5.Book.buy(..))"/>
    <!--配置切面-->
    <aop:aspect ref="bookProxy">
        <aop:before method="before" pointcut-ref="p"/>  <!--将bookProxy中的before方法配置为切入点的前置通知-->
    </aop:aspect>
</aop:config>

Spring笔记(3):JdbcTemplate、事务管理、Spring5_mingyuli的博客-CSDN博客

aop:

在SpringBoot中用SpringAOP实现日志记录功能 - 汪神 - 博客园

SpringBoot中使用AOP_AlanLee97的博客-CSDN博客_springboot使用aop

SpringBoot使用AOP_大老杨的博客-CSDN博客_springboot使用aop

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值