目录
简要说明:
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