Spring框架的基本使用

  • Post author:
  • Post category:其他



目录


简要说明:


实例说明:ioc


实例说明:基于注解的IOC实现技术DI


拓展知识


在三层架构的项目中使用注解


拓展知识


AOP 面向切面编程


AspectJ框架


AspectJ 中常用的通知有四种类型:


前置通知实现@Before :


后置通知@AfterReturning


环绕通知@Around


最终通知@After


自定义切入点别名@Pointcut(了解)


常用通知的集合执行顺序说明



简要说明:


Spring框架:它是一个容器.它是

整合其它框架

的框架。


Spring框架特点:



轻量级 :

由20多个模块构成并且体积很小



面向接口编程:



使用接口,就是面向灵活,项目的可扩展性,可维护性都极高.接口不关心实现类的类型.使用时接口指向实现类,切换实现类即可切换整个功能.



(核心)IOC(Inversion of Control)控制反转:



是把传统上由

程序代码直接操控的对象(自己创建new的对象)

的调用权交给

容器

,通过容器实现对象的创建,属性赋值, 依赖的管理。



(核心)面向切面编程(AOP):



就是将

公共的,通用的,重复的

代码

单独开发

,在需要的时候反织回去.底层的原理是

动态代理

.


实例说明:ioc


1.创建一个maven项目


在pom.xml文件中添加spring依赖

<!--添加spring的依赖-->
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.19</version>
</dependency>


2.在/com/dy/s1包下定义一个实体类School

public class School {
    private String name;
    private String address;
    //为了给成员变量注入值,必须提供无参构造方法和setXXX()方法
    public void setAddress(String address) {
        this.address = address;
    }
    public void setName(String name) {
        this.name = name;
    }
    public School() {
        System.out.println("学校的无参的构造方法被调用..........");
    }
    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}


在/com/dy/s1包下定义一个实体类Student

public class Student {
    private String name;
    private int age;
    private School school;//引用类型的成员变量,学生所在的学校
    public Student() {
        System.out.println("学生的无参的构造方法..........");
    }
  //交给Spring容器注入值,必须提供setXXX()方法
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setSchool(School school) {
        this.school = school;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}


3.在

resources

包下创建Spring的配置文件applicationContext.xml

bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。初始化是由容器自动完成的,称为注入。根据注入方式的不同,常用的有两类:set 注入、构造注入。


方法一:

使用setter注入

(注入分为简单类型注入和引用类型注入)




简单类型

注入值:使用value属性



引用类型

注入值:使用ref属性 (当指定

bean

(Student


)的某属性值(school)为另一

bean

的实例(school)时,通过 ref 指定它们间的引用关系。ref 的值必须为某 bean 的 id 值。)


必须要注意:使用setter注入必须提供无参的构造方法,必须提供setXXX()方法.

<?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"
       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">
<!--<bean/>:用于定义一个实例对象
    id:就是创建的对象的名称  等同于 Student stu = new Student(); 中的stu
    class:指定该Bean创建的对象的类型,注意这里只能是类,不能是接口。-->
   <!--创建学校对象-->
    <bean id="sch" class="com.dy.s1.School">
        <property name="name" value="清华大学"/>   <!--简单类型注入-->
        <property name="address" value="海淀区"/>  <!--简单类型注入-->
    </bean>
    <!--创建学生对象-->
    <bean id="stu" class="com.dy.s1.Student" >
     <property name="name" value="李四"/>  <!--简单类型注入-->
        <property name="age" value="22"/>   <!--简单类型注入-->
        <property name="school" ref="school"/>  <!--引用类型注入-->
        <!--给创建的对象赋值:使用setter注入
    </bean>
</beans>


创建方法一的测试类

public class MyTest{
@Test
public void testStudent(){
  //创建容器对象
  //当容器启动时,会自动调用对象的无参构造创建对象
  ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
  //取出学生对象包含学校对象
  Student stu = (Student) ac.getBean("stu");
  System.out.println(stu);
    }
}
测试结果:
学校的无参的构造方法被调用..........
学生的无参的构造方法..........
Student{name='李四', age=22, school=School{name='清华大学', address='海淀区'}}


方法二:使用

构造方法

注入(上面的实体类Student



School中,分别加入代参构造方法)在实体类中必须提供相应参数的构造方法。


配置文件applicationContext.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">
<!--<constructor-arg />标签中用于指定参数的属性有:
name:指定参数名称。
index:指明该参数对应着构造器的第几个参数,从0开始。不过,
       该属性不要也行,但要注意,若参数类型相同,或之间有
       包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。-->
    <!--创建学校的对象,使用构造方法中带参的名称注入值-->
    <bean id="school" class="com.dy.s1.School">
        <constructor-arg name="address" value="海淀区"/>
        <constructor-arg name="name" value="清华大学"/>
    </bean>
    <!--创建学生对象,使用构造方法的参数的下标注入值(通过下标方式可以随意调换位置)-->
    <bean id="stu" class="com.dy.s1.Student">
        <constructor-arg index="0" value="钱七"/>
        <constructor-arg index="2" ref="school"/>
        <constructor-arg index="1" value="22"/>
    </bean>
    <!--创建学生对象,使用默认的构造方法的参数顺(必须按照有参构造的顺序)-->
    <bean id="stuSequence" class="com.dy.s1.Student">
        <constructor-arg value="陈十"/>
        <constructor-arg value="22"/>
        <constructor-arg ref="school"/>
    </bean>
</beans>


创建方法二的测试类

public class Mytest2 {
@Test   //通过有参构造的名字入值
public void testSchool(){
  ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
  School school = (School) ac.getBean("school");
  System.out.println(school);
 }
//测试结果:School{name='清华大学', address='海淀区'}
@Test  //通过有参构造的下标入值
public void testStudent(){
  ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
  Student stu = (Student) ac.getBean("stu");
  System.out.println(stu);
 }
//测试结果:Student{name='钱七', age=22, school=School{name='清华大学', address='海淀区'}}
@Test  //使用默认的构造方法的参数顺序
public void testStudentSequence(){
  ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
  Student stu = (Student) ac.getBean("stuSequence");
  System.out.println(stu);
 }
}
//测试结果:Student{name='陈十', age=22, school=School{name='清华大学', address='海淀区'}}


实例说明:基于注解的IOC实现技术DI



DI (Dependency Injection)依赖注入



对于 DI 使用注解,将不再需要在 Spring 配置文件中声明bean实例。Spring 中使用注解, 需要在原有 Spring 运行环境基础上再做一些改变。需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。


关于对象中注解的说明:创建对象的常用注解




@Component:


可以

创建任意对象

.创建的对象的默认名称是类名的

驼峰命名法

.也可以指定对象的名称@Component(”

指定名称

“).



@Controller:


专门用来创建

控制器的对象

(Servlet),这种对象可以接收用户的请求,可以返回处理结果给客户端.



@Service:


专门用来创建

业务逻辑层的对象

,负责向下访问数据访问层,处理完毕后的结果返回给界面层.



@Repository:


专门用来创建

数据访问层

的对象,负责数据库中的增删改查所有操作.


1.创建一个maven项目(并在pom.xml文件中添加Spring依赖)


2.在/com/dy/s1包下创建实体类Student

//@Component:可以创建任意对象.创建的对象的默认名称是类名的驼峰命名法.
//也可以指定对象的名称@Component("指定名称") ,指定名称后,就只能用这个名称
//@Value:用来给简单类型注入值(8种基本类型+String)
@Component
//@Component("stu") 指定名称后,就只能用这个名称
public class Student {
    @Value("张三")   //使用Value注入时,不管后面是什么类型都必须带""号
    private String name;
    @Value("20")
    private int age;

  public Student() {
        System.out.println("学生对象的无参构造方法.........");
    }

  @Override
  public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


3.在配置文件

resources

包下创建Spring的配置文件applicationContext.xml


基于注解的IOC,必须要在Spring的核心配置文件中添加包扫描.


<context:component-scan base-package=”xxx.xxx.包名”></context:component-scan>

<?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"
       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">
    <!--添加包扫描-->
    <!--启动时当扫描到xxx.xxx.包名下有注解,就会自动创建对象-->
    <context:component-scan base-package="com.dy.s1"/>
</beans>


4.创建测试类

public class MyTest01 {
  @Test
  public void testStudent(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = (Student) ac.getBean("student");
//Student student = (Student) ac.getBean("stu"); 注解如果使用了指定名称,这里也必须是指定的名称
    System.out.println(student);
    }
}
测试结果结果:
学生对象的无参构造方法.........
Student{name='张三', age=20}


拓展知识


1.引用类型的注入



@Autowired:


使用类型注入值,从整个Bean工厂中搜索同源类型的对象进行注入.



@Autowired

@Qualifier(“名称”):


使用名称注入值,从整个Bean工厂中搜索相同名称的对象进行注入.


例:


创建父类School

//@Component  未规定名称 此时School对象的名称就是school
@Component("schoolFu")  //规定了名称
public class School {  
    @Value("清华大学")
    private String name;
    @Value("海淀区")
    private String address;

    public School() {
        System.out.println("School的无参的构造方法.........");
    }
    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}


创建子类SubSchool

//@Component 此时School对象的名称就是school
@Component("schoolZi")  //规定了名称
public class SubSchool extends School {
    @Value("清华附小")
    private String name;
    @Value("海淀小区")
    private String address;

    public SubSchool() {
        System.out.println("这是SubSchool子类的构造方法......");
    }

    @Override
    public String toString() {
        return "SubSchool{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}


创建学生类 Student并引用School

@Component
public class Student {
    @Value("李四")
    private String name;
    @Value("23")
    private int age;

    //@Autowired  按引用类型按类型注入
    @Autowired
    @Qualifier("schoolZi")  //使用规定名称注入
    private School school;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}


applicationContext.xml配置包扫描

<!--添加包扫描-->
<!--启动时当扫描到xxx.xxx.包名下有注解,就会自动创建对象-->
<context:component-scan base-package="xxx.xxx.包名"/>


测试类

public class MyTest03 {
@Test
  public void testStudent(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student stu = (Student) ac.getBean("student");
    System.out.println(stu);
    }
}


情况一:在父子类中的注解@Component中

未注明名称

,在测试类中Student中的school

按引用类型注入

时  测试结果是:(默认为

父类优先

注入)

School的无参的构造方法.........
School的无参的构造方法.........
这是SubSchool子类的构造方法......
Student{name='李四', age=23, school=School{name='清华大学', address='海淀区'}}


情况二:在父子类中

父类

注解@Component中规定了名称,子类没有,在测试类中Student中的school按引用类型注入时  测试结果是:(优先父类,但如果

父类对象名称对不上

,就进行第二次筛选,


选中与



被注入对象相同名称



的对象进行注入


)

School的无参的构造方法.........
School的无参的构造方法.........
这是SubSchool子类的构造方法......
Student{name='李四', age=23, school=SubSchool{name='清华附小', address='海淀小区'}}



注意:



在有父子类的情况下,使用按



类型注入



,就意味着有多个可注入的对象.此时



按照名称



进行二次筛选,选中与



被注入对象相同名称



的对象进行注入.


情况三:在父子类中的注解@Component

分别规定了不同的名称

,在测试类中Student中的school类型也

引用了规定名称注入

时  测试结果是:

School的无参的构造方法.........
School的无参的构造方法.........
这是SubSchool子类的构造方法......
Student{name='李四', age=23, school=SubSchool{name='清华附小', address='海淀小区'}}

在三层架构的项目中使用注解


创建项目并在pom.xml文件中添加Spring依赖


1.创建实体类(com.dy.pojo包下)

public class Users {
    private int uid;
    private String uname;
    private int uage;

    public int getUid() {return uid;}
    public void setUid(int uid) { this.uid = uid; }

    public String getUname() { return uname; }
    public void setUname(String uname) { this.uname = uname; }

    public int getUage() { return uage; }
    public void setUage(int uage) { this.uage = uage;}

    public Users(int uid, String uname, int uage) {
        this.uid = uid;
        this.uname = uname;
        this.uage = uage;
    }
    public Users() {}

    @Override
    public String toString() {
        return "Users{" +
                "uid=" + uid +
                ", uname='" + uname + '\'' +
                ", uage=" + uage +
                '}';
    }
}


2.创建数据访问层 的实现类和接口(com.dy.mapper包下)

@Repository  //交给spring框架去创建数据访问层的对象
public class UsersMapperImpl implements UsersMappr {
    @Override
    public int insert(Users u) {
        System.out.println(u.getUname()+"用户增加成功!");
        return 1;
    }
}
public interface UsersMappr {
    int insert(Users u ); //增加用户
}

3.

创建业务逻辑层的接口和实现类(com.dy.service包下)

public interface UsersService {
    //增加用户
    int insert(Users users);
}
@Service  //交给spring创建业务逻辑层的对象
public class UsersServiceImpl implements UsersService {
    //切记切记:在所有的业务逻辑层中都必定有数据访问层的对象
    @Autowired
    private UsersMappr usersMappr;
    @Override
    public int insert(Users users) {
        return usersMappr.insert(users);
    }
}


4.创建界面层的实现类(com.dy.controller包下)

@Controller  //spring来创建控制器的对象(Servlet)
public class UsersController {
    //如何去访问业务逻辑层,就是创建对象
    //切记切记:所有的界面层都会有业务逻辑层的对象
    @Autowired
    public UsersService usersService ;
    //界成层的功能实现,对外提供访问的功能
    public int insert(Users users){
        return usersService.insert(users);
    }
}


5.配置

applicationContext.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">
    <!--只要是基于注解的开发,必须包扫描-->
    <context:component-scan base-package="com.dy.controller"/>
    <context:component-scan base-package="com.dy.service"/>
    <context:component-scan base-package="com.dy.mapper"/>
</beans>


6.创建测试类

public class MyTest {
@Test
  public void testInsertUsers(){
  ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
  UsersController usersController = (UsersController) ac.getBean("uController");
  //测试功能
  int num = usersController.insert(new Users(200,"王五",24));
  System.out.println(num);
    }
}
测试结果:
王五用户增加成功
1

拓展知识


1.

添加包扫描的方式

:单个包扫描(推荐使用)


当项目中有多个包需要被添加扫描时,可以单个配置包扫描

例:(单个添加)
<context:component-scan base-package="com.dy.controller"/>
<context:component-scan base-package="com.dy.service"/>
<context:component-scan base-package="com.dy.mapper"/>


也可以只添加一个包扫描,多个包之间以逗号或空格或分号分隔

例:(多个添加)
<context:component-scan base-package="com.dy.controller;com.dy.service,com.dy.mapper"/>


2.为应用指定多个 Spring 配置文件(当项目越来越大,需要多人合作开发,只有一个配置文件(或所有的都配置在一个文件中)的话就存在很大隐患.这时我们就需要对配置

进行合理的拆分

)


例:项目下实体类Users、实体类Book的三层架构xml配置拆分



按层拆分:

(按其中的架构类型进行拆分)(界面层,业务逻辑层,数据访问层)



applicationContext_controller.xml

对应

界面层

<bean id="uController" class="com.dy.controller.UsersController">
<bean id="bController" class="com.dy.controller.BookController">



applicationContext_service.xml

对应

业务逻辑层

<bean id="uService" class="com.dy.service.UsersService">
<bean id="bService" class="com.dy.service.BookService">



applicationContext_mapper.xml

对应

数据访问层

<bean id="uMapper" class="com.dy.mapper.UsersMapper">
<bean id="bMapper" class="com.dy.mapper.BookMapper">



按功能拆分:

(按实体类功能进行拆分)



applicationContext_users.xml

<bean id="uController" class="com.dy.controller.UsersController">
<bean id="uService" class="com.dy.Service.UsersService">
<bean id="uMapper" class="com.dy.mapper.UsersMapper">



applicationContext_book.xml

<bean id="bController" class="com.dy.controller.BookController">
<bean id="bService" class="com.dy.Service.BookService">
<bean id="bMapper" class="com.dy.Mapper.BookMapper">


3.当

按层拆分后

的配置文件,我们需要对这些文件单独配置一个文件(区别大的名字.xml)进行

整合





单个文件导入 :

<import resource="applicatoinContext_mapper.xml"/>
<import resource="applicatoinContext_service.xml"/>
<import resource="applicatoinContext_controller.xml"/>



批量导入:

<import resource="applicatoinContext_*.xml"/>

AOP 面向切面编程



AOP(Aspect Orient Programming),面向切面编程。


切面:

公共的,通用的,重复的功能

称为切面,面向切面编程就是将切面

提取出来,单独开发

,在需要调用的方法中

通过动态代理

的方式进行织入.


Spring支持AOP的编程,常用的有以下几种:



Before通知:

在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;


After通知:

在目标方法被调用后调用,涉及接口org.springframework.aop.AfterReturningAdvice;


Throws通知:

目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice;


Around通知:

拦截对目标对象方法调用,涉及接口org.aopalliance.intercept.MethodInterceptor;


AOP常用的术语:



切面(

Aspect)

:

就是那些重复的,公共的,通用的功能称为切面,例如:日志,事务,权限.


连接点(

JoinPoint

):

就是目标方法.因为在目标方法中要实现目标方法的功能和切面功能.


切入点(

Pointcut

):

指定切入的位置,多个连接点构成切入点.切入点可以是一个目标方法,可以是一个类中的所有方法,可以是某个包下的所有类中的方法.


目标对象(

Target

):

操作谁,谁就是目标对象.


通知(

Advice

):

来指定切入的时机.是在目标方法执行前还是执行后还是出错时,还是环绕目标方法切入切面功能.


AspectJ框架


AspectJ 的切入点表达式:(重点)


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

简化后的公式:execution( 方法返回值 方法声明(参数) )


用到的符号:


*

代表任意个任意的字符(通配符)


..

如果出现在方法的参数中,则代表任意参数


..

如果出现在路径中,则代表本路径及其所有的子路径


示例:指定切入点




execution(public * *(..)):


任意(public)公共方法。



execution(* set*(..)):


任何一个以“set”开始的方法。



execution(* com.xyz.service.impl.*.*(..)):


任意的返回值类型,在指定包下的

任意类(

这里是


.*


必须是类,因为方法不能出现在包中

)

的任意方法的任意参数。



execution(* com.xyz.service..*.*(..)):


任意的返回值类型 ,在指定的service及其子包下的任意类的任意方法的任意参数。com.xyz.service.a.b.*.*(..)  com.xyz.service.*.*(..)



execution(* *..service.*.*(..)):


指定所有包下的 serivce 子包下的类(接口)中所有方法。



execution(* *.service.*.*(..)):


指定只有一个包下的 serivce 子包下的类(接口)中所有方法。


AspectJ 中常用的通知有四种类型:

前置通知实现


@Before :



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


实现步骤:


1.添加依赖 需要添加spring和aspectj的依赖

<!--添加spring的依赖-->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>5.3.19</version>
</dependency>
<!--添加aspectj的依赖-->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-aspects</artifactId>
   <version>5.3.19</version>
</dependency>


2.创建业务接口

public interface SomeService {
    String doSome(String name,int age); //方法1
    void show(); //无类型无参数的方法2
}


3.创建业务实现类

@Service
public class SomeServiceImpl implements SomeService {
  @Override
  public String doSome(String name, int age) {
    System.out.println("doSome的业务功能实现................");
    return "abcd";
    }
  @Override
  public void show() {
    System.out.println("show的业务方法实现.............");
   }
}


4.


创建切面类,实现切面方法

类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。

@Aspect  //交给AspectJ的框架去识别切面类
@Component  
public class MyAspect {  
//切面类中包含各种的切面方法,所有切面的功能都是由切面方法来实现的,可以将各种切面都在此类中进行开发
  @Before(value = "execution( * com.dy.s1.*.*(..))")
  public void myBefore(JoinPoint jp){
//JoinPoint类型参数在目标方法执行之前执行,该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。
    System.out.println("切面方法中的前置通知功能实现............");
    System.out.println("目标方法的签名:"+jp.getSignature());
    System.out.println("目标方法的参数:"+ Arrays.toString(jp.getArgs()));
  }
}


5.在applicationContext.xml文件中进行切面绑定

<!--基于注解的访问要添加包扫描-->
<context:component-scan base-package="com.dy.s1"/>
<!--切面功能绑定-->
<aop:aspectj-autoproxy/>


6.测试类

public class MyTest {
 @Test
 public void test(){
   //创建容器并启动
   ApplicationContext ac = new ClassPathXmlApplicationContext("s1/applicationContext.xml");
   //取出代理对象
   //默认是JDK动态代理,取时必须使用接口类型,使用接口来接,永远不出错.
   SomeService someService = (SomeService) ac.getBean("someServiceImpl");
   //输出代理对象
   System.out.println(someService.getClass());
   String s = someService.doSome("张三", 20);
   System.out.println("返回值:"+s);
   someService.show();
    }
}
测试结果:
class jdk.proxy2.$Proxy16

切面方法中的前置通知功能实现............
目标方法的签名:String com.dy.s1.SomeService.doSome(String,int)
目标方法的参数:[张三, 20]
doSome的业务功能实现................
返回值:abcd   //前置通知是无法改变目标方法的返回值的

切面方法中的前置通知功能实现............
目标方法的签名:void com.dy.s1.SomeService.show()
目标方法的参数:[]
show的业务方法实现执行.............


后置通知

@AfterReturning





目标方法执行之后

执行,由于是目标方法之后执行,所以可以

获取到目标方法的返回值

,如果目标方法的返回值是

简单类型

(8种基本类型+String)则

不可改变

.如果目标方法的返回值是

引用类型则可以改变。






实现步骤:


1.添加依赖 需要添加spring和aspectj的依赖(和上方的一样)


2.创建业务接口

public interface SomeService {
  String doSome(String name,int age);
  Student change();
}


3.创建实体类

public class Student {
    private String name;
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student() {
    }
    public Student(String name) {
        this.name = name;
    }
}


4.创建业务实现类

@Service
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("张三");
    }
}


5.创建切面类,实现切面方法

@Aspect
@Component
public class MyAspect {
   //returning:指定目标方法的返回值的名称,此名称必须与切面方法的参数名称一致.
  @AfterReturning(value = "execution(* com.dy.s2.*.*(..))",returning = "obj")
  public void myAfterReturning(Object obj){
    System.out.println("后置通知功能实现..............");
    if(obj != null){ //判断返回值不为空
        if(obj instanceof String){  //判断返回值是否是String类型
            obj = obj.toString().toUpperCase();  //将返回值全部大写
            System.out.println("在切面方法中目标方法的返回值:"+obj);
            }
        if(obj instanceof Student){ //判断返回值类型是否是Student类型
            Student stu = (Student) obj;  //是就进行类型强转
            stu.setName("李四");  //修改返回值
            System.out.println("在切面方法中目标方法的返回值:"+stu);
            }
        }
    }
}


6.在applicationContext.xml文件中进行切面绑定(和上方的一样)


7.测试类

public class MyTest {
 @Test
 public void test(){
   //创建容器并启动
   ApplicationContext ac = new ClassPathXmlApplicationContext("s2/applicationContext.xml");
   //取出代理对象
   SomeService someService = (SomeService) ac.getBean("someServiceImpl");
   String s = someService.doSome("张三",22);
   System.out.println("在测试方法中目标方法的返回值:"+s);
        
   Student stu = someService.change();
   System.out.println("在测试为中目标方法的返回值是:"+stu);
  }   
}
测试结果:
doSome()业务方法被执行............
后置通知功能实现..............
在切面方法中目标方法的返回值:ABCD 
在测试方法中目标方法的返回值:abcd //基本类型修改失败

change()方法被执行............
后置通知功能实现..............
在切面方法中目标方法的返回值:Student{name='李四'}
在测试为中目标方法的返回值是:Student{name='李四'} //引用类型修改成功


环绕通知

@Around



通过拦截目标方法的方式 ,在目标方法前后增强功能的通知.它是功能最强大的通知,一般事务使用此通知.它可以轻易的改变目标方法的返回值.


实现步骤:


1.添加依赖 需要添加spring和aspectj的依赖(和上方的一样)


2.创建业务接口

public interface SomeService {
  String doSome(String name,int age);
}


3.创建业务实现类

@Service
public class SomeServiceImpl implements SomeService{
  @Override
  public String doSome(String name, int age) {
    System.out.println("doSome业务方法被执行...."+name);
    return "abcd";
  }
}


4.创建切面类,实现切面方法

@Aspect
@Component
public class MyAspect {
  @Around(value = "execution(* com.dy.s3.*.*(..))")
  //必须要回避异常Throwable
  public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
  //前切功能实现
  System.out.println("环绕通知中的前置功能实现............");
  //目标方法调用
  Object obj = pjp.proceed(pjp.getArgs());
  //后切功能实现
  System.out.println("环绕通知中的后置功能实现............");
  return obj.toString().toUpperCase();  //改变了目标方法的返回值
    }
}


5.测试类

public class MyTest03 {
  @Test
  public void test01(){
   //创建容器并启动
   ApplicationContext ac = new ClassPathXmlApplicationContext("s3/applicationContext.xml");
   //取出代理对象
   SomeService someService = (SomeService) ac.getBean("someServiceImpl");
   String s = someService.doSome("张三",22);
   System.out.println("在测试方法中目标方法的返回值:"+s);
   }
}
测试结果:
环绕通知中的前置功能实现............
doSome业务方法被执行....张三
环绕通知中的后置功能实现............
在测试方法中目标方法的返回值:ABCD


最终通知

@After



无论目标方法是否正常执行,最终通知的代码都会被执行.(可参考下面集合说明)


自定义切入点别名

@Pointcut

(了解)


如果多个切面切入到同一个切入点,可以使用别名简化开发.(可参考下面集合说明)

@Pointcut(value = "execution(* com.dy.s4.*.*(..))")
    public void mycut(){}

常用通知的集合执行顺序说明


实现步骤:


1.添加依赖 需要添加spring和aspectj的依赖(和上方的一样)


2.创建业务接口

public interface SomeService {
    String doSome(String name, int age);
}


3.创建业务实现类

@Service
public class SomeServiceImpl implements SomeService {
  @Override
  public String doSome(String name, int age) {
    System.out.println("doSome业务方法被执行...."+name);
    return "abcd";
    }
}


4.创建切面类,实现切面方法

@Aspect
@Component
public class MyAspect {
    @After(value = "mycut()")
    public void myAfter(){
        System.out.println("最终通知的功能........");
    }
    @Before(value = "mycut()")
    public void myBefore(){
        System.out.println("前置通知的功能........");
    }
    @AfterReturning(value = "mycut()",returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置通知的功能........");
    }
    @Around(value = "mycut()")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知中的前置通知的功能........");
        Object obj = pjp.proceed(pjp.getArgs());
        System.out.println("环绕通知中的后置通知的功能........");
        return obj;
    }
    @Pointcut(value = "execution(* com.dy.s4.*.*(..))")
    public void mycut(){}
}


5.测试类

public class MyTest04 {
 @Test
 public void test01(){
   ApplicationContext ac = new ClassPathXmlApplicationContext("s4/applicationContext.xml");
   SomeService someService = (SomeService) ac.getBean("someServiceImpl");
   String s = someService.doSome("张三",22);
   System.out.println("在测试方法中目标方法的返回值:"+s);
    }
}
测试结果:
环绕通知中的前置通知的功能........
前置通知的功能........
doSome业务方法被执行....张三
后置通知的功能........
最终通知的功能........
环绕通知中的后置通知的功能........
在测试方法中目标方法的返回值:abcd



版权声明:本文为molu1991原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。