Spring详解(控制反转(IOC)与面向切面编程(AOP))
Spring 概念
1、spring于2002年出现,为解决企业开发的难度,减轻对项目模块之间的管理,类和类之间的管理,帮助开发人员创建对象,管理对象之间的关系。
2、spring的核心技术
(1)IOC:控制反转(Inversion Of Control)
(2)AOP:面向切面编程(Aspect Oriented Programming)
作用
:能够实现模块之间、类之间的解耦合
3、依赖
classA 中 使用classB中的属性或方法,叫做classA依赖classB,spring可以对依赖进行管理。
4、spring的优点
(1)轻量:spring使用的jar包较小,spring的核心功能在3M左右。
(2)针对接口编程、解耦合:spring提供了IOC控制反转,由容器管理对象,对象之间的依赖关系,原来在程序代码中的对象的创建方式,现由容器完成对象之间的解耦合。
5、spring框架的体系结构
控制反转(Inversion Of Control,简称:IOC)
1、IOC是什么?
(1)IOC是一种概念、一种理论思想。描述的是把对象的创建、赋值、管理工作交给代码之外的容器实现的,也就是对象的创建由其他外部资源完成的。
控制
:创建对象,对象的属性赋值,对象之间的关系管理。
正转
:由开发人员使用new关键字创建对象,由开发人员主动管理。Person p = new Person();
反转
:把原来开发人员管理、创建的对象的权利转移给代码之外的容器实现。由容器代替开发人员管理对象、创建对象、给属性赋值。
容器
:是一个服务器软件,一个框架。
(2)为什么要使用IOC?
目的就是减少对代码的改动,也能够实现不同的功能,实现解耦合
。
(3)Java中创建对象的方式有哪些?
a、构造方法:new关键字
b、反射机制
c、序列化
d、克隆
e、IOC:容器创建对象
f、动态代理
2、IOC思想的体现(Tomcat容器对Servlet对象的管理)
Servlet
:
(1)创建类继承HttpServlet
(2)在web.xml注册Servlet,使用如下代码<servlet-name>myServlet</servlet-name> <servlet-class>com.packagename.controller.MyServlet</servlet-class>
(3)没有创建对象,没有使用new关键字
MyServlet ms = new MyServlet()
;
(4)Servlet是Tomcat服务器创建的,Tomcat也成为容器。Tomcat作为容器,里面存放的有Servlet对象、listener对象、Filter对象
3、IOC的技术实现
(1)DI(依赖注入,Dependency Injection)是IOC的技术实现
(2)DI:依赖注入,只需要在程序中提供使用的对象名称就可以拿到对象就是DI,至于对象如何在容器中创建、管理、赋值、查找都有容器内部实现
(3)spring使用DI实现了IOC的功能,spring底层创建对象使用的是反射机制
(4)spring是一个容器,管理对象,给属性赋值,底层是反射创建对象
4、由spring的实现对象的创建、管理
(1)创建一个maven项目
(2)在pom.xml文件中引入spring的jar包
<!--spring的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
(3)在service(业务层)中创建SomeService及其实现类SomeServiceImpl
public interface SomeService {
void doSome();
}
public class SomeServiceImpl implements SomeService {
@Override
public void doSome() {
System.out.println("执行了SomeServiceImpl的doSome()方法");
}
}
(4)在resources目录下创建spring的beans.xml(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">
<!--告诉spring创建对象
声明bean。就是告诉spring要创建某个类的对象
id:对象的自定义名称,唯一值。spring通过这个名称找到对象
class:类的全限定名称(不能是接口,因为spring底层是通过反射机制创建对象,必须使用类)
-->
<!--
1、spring就完成SomeService someService = new SomeServiceImpl();
2、spring是把创建好的对象放入map中,spring框架有一个存放对象的map。
2.1、springMap.put(key,value);其中:key是id的值,value是对象
2.2、例如:springMap.put("someService",new SomeServiceImpl());
3、一个bean标签声明一个对象。
-->
<bean id="someService" class="com.spring.service.SomeServiceImpl"/>
</beans>
<!--
spring 的配置文件
1、beans:是根标签,spring把java对象当做bean
2、spring-beans.xsd 是约束文件,和mybatis中的dtd是一样的,xsd约束能力较强
-->
(5)在单元测试类中,可以使用如下代码进行测试
/*spring获取beans.xml对象的方法测试*/
/**
* 1、spring 默认创建对象的时间:在创建spring容器时,会创建配置文件中所有的对象
* 2、spring 创建对象默认调用的是无参构造方法
* 3、spring 可以创建自定义类或者非自定义类的对象
*/
@Test
public void test02() {
//使用spring容器创建对象
//1、指定spring配置文件的名称
String config = "beans.xml";
//2、创建表示spring容器的对象,ApplicationContext
//ApplicationContext就是表示Spring容器,通过容器就可以获取对象了
//ClassPathXMLApplicationContext:表示从类路径中加载spring的配置文件
//调用容器
ApplicationContext context = new ClassPathXmlApplicationContext(config);
//从容器中获取对象,调用对象的方法
//getBean("配置文件中的bean的id的值")
//获取对象
SomeService someService = (SomeService) context.getBean("someService");
//使用spring创建好的对象
someService.doSome();
}
依赖注入(Dependency Injection,简称:DI)
1、在spring的配置文件中,给Java对象的属性赋值。
2、依赖注入(DI):表示创建对象,给属性赋值。
3、DI的实现语法有两种(注入就是赋值的意思):
(1)在spring配置文件中,使用标签和属性来完成,叫做基于XML的DI实现。
(2)使用spring中的注解,完成属性赋值,叫做基于注解的DI实现。
4、DI的语法分类:
(1)set注入(设值注入):spring调用类的set方法,在set方法可以实现属性的赋值。(80%左右都是使用set注入)
(2)构造注入:spring调用类的构造方法,创建对象。在构造方法中完成赋值。
5、spring容器中存放对象的容器使用的是Map
基于XML配置文件的DI(依赖注入,Dependency Injection)
1、在spring配置文件中,使用标签和属性来完成,叫做基于XML的DI实现。
2、set注入(设值注入):spring调用类的set方法,在set方法可以实现属性的赋值。
set注入(设值注入)
1、简单类型的属性的set注入
<bean id="xx" class="yyy"> <property name="属性名字" value="此属性的值"/> //一个property只能给一个属性赋值 <property....> </bean>
2、引用类型的属性的set注入
<bean id="xxx" class="yyy"> <property name="属性名称" ref="bean的id(对象的名称)"/> </bean>
1、创建项目
2、引入spring依赖
<!--spring的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
3、创建Student类
public class Student {
private String name;
private int age;
private School school;
public void setName(String name) {
System.out.println("setName:" + name);
this.name = name;
}
public void setAge(int age) {
System.out.println("setAge:" + age);
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
public class School {
private String name;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
4、DI注入:applicationContext.xml配置文件
注:spring在调用默认的无参构造方法后,开始通过set注入,即对象的属性赋值,必须有set方法。
<?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">
<!--声明student对象
注入:就是赋值的意思
简单类型:spring中规定基本数据类型和字符串都是简单类型
DI:给属性赋值
1、set注入(设置注入):spring调用类的set方法,可以在set方法中完成属性赋值
(1)简单类型的set注入
<bean id="xx" class="yyy">
<property name="属性名字" value="此属性的值"/>
//一个property只能给一个属性赋值
<property....>
</bean>
(2)引用类型的set注入
<bean id="xxx" class="yyy">
<property name="属性名称" ref="bean的id(对象的名称)"/>
</bean>
-->
<!--如果是简单类简单类型的属性,value可以直接赋值-->
<bean id="myStudent" class="com.spring.ba01.Student">
<property name="name" value="李四"/> <!--setName("李四")-->
<property name="age" value="20"/> <!--setAge(20)-->
<!--引用类型属性的set注入-->
<property name="school" ref="mySchool"/>
</bean>
<!--声明school对象-->
<bean id="mySchool" class="com.spring.ba01.School">
<property name="name" value="厦门大学"/>
<property name="address" value="厦门"/>
</bean>
</beans>
构造注入
1、构造方法注入 ,spring调用类的s构造方法,可以在构造方法中完成属性赋值
2、 构造注入使用标签
3、标签:一个表示构造方法的一个参数
4、标签属性:
(1) name:表示构造方法的形参名
(2)index:表示构造方法的参数的位置,参数从左往右位置是0,1,2的顺序
(3)value:表示构造方法的形参类型是简单类型,使用value
(4)ref:构造方法的形参类型是引用类型,使用ref
例
:<!--使用构造注入 name的名称要和构造方法的形参名称一致 --> <bean id="myStudent" class="com.spring.ba01.Student"> <constructor-arg name="name" value="张三"/> <constructor-arg name="age" value="20"/> <constructor-arg name="school" ref="mySchool"/> </bean> <!--声明school对象--> <bean id="mySchool" class="com.spring.ba01.School"> <property name="name" value="厦门大学"/> <property name="address" value="厦门"/> </bean>
spring的自动注入(引用类型的自动注入,简化引用类型的赋值操作)
1、概念:spring的自动注入只针对于一用类型的自动注入。
2、引用类型的自动注入:spring框架根据某些规则给引用类型赋值。不用手动给引用类型赋值,即不需要使用一下标签对引用类型的属性赋值:<property name="school" ref="mySchool"/>
3、常用的规则有:byName
、byType
(1)byName(按名称注入):Java类中引用类型和spring容器(配置文件)标签的id的名称一样,且数据类型是一致的,这样的容器的bean,spring是能够赋值给引用类型的。语法如下:<bean id="xx" class="yyy" autowire="byName"> 简单类型的属性赋值 </bean>
(2)byType(按类型注入):Java类中引用类型的数据类型和spring容器(配置文件)中的class属性是同源关系的,这样的bean能够赋值给引用类型。语法如下:
同源关系
:
同源就是一类的意思:
1、java类中的引用类型的属性的数据类型的bean的class的值是一样的
2、java类中的引用类型的属性的数据类型和bean的class的值是父子类关系的
3、java类中的引用类型的属性的数据类型和bean的class的值是接口和实现类关系的
注意
:在byType中,在xml配置文件中声明bean只能有一个符合条件的。<bean id="xx" class="yyy" autowire="byType"> 简单类型的属性赋值 </bean>
spring的配置文件
注
:建议使用多个配置文件来完成spring的配置
1、多个配置文件的优势
(1)每个配置文件小,配置文件读写或加载速度快
(2)避免多人竞争带来的冲突(大型项目)
2、多个配置文件的分配方式
(1)按功能模块分配:一个模块一个配置文件
(2)按类的功能分配:数据库相关配置、事务功能配置、service功能配置
3、将分散的配置文件引入一个汇总配置文件,使用如下的标签:<import resource="配置文件(通常在类路径下:[classpath:spring.xml)"/>
基于注解(annotation)的DI(依赖注入,Dependency Injection)
1、通过spring的注解完成java对象的创建和属性的赋值。代替xml文件
2、实现步骤
(1)加入依赖
(2)创建类,在类中加入注解
(3)创建spring的配置文件:声明组件扫描器的标签,指明注解在你的项目中位置
(4)使用注解创建对象,创建容器ApplicationContext
3、学习的注解:
(1)@Component
(2)@Repository
(3)@Service
(4)@Controller
(5)@Value
(6)@Autowired
(7)@Resource
@Component
1、@Component的作用
/**
* 1、@Component 创建对象的,等同于<bean></bean>的功能
* 2、属性:value 就是指定对象的名称,也就是bean的id的值value的值是唯一的,
* 创建spring的对象在整个spring的容器中就一个;如果没有指定value属性,
* 默认对象名称为类名 的首字母小写,并按照驼峰命名法命名。
* 3、位置:在类的上面
* 4、@Component(value = "myStudent"),也可以省略value,即@Component("myStudent")等同于
* <bean id="myStudent" class="com.spring.anno.ba01.Student">...</bean>
* 5、spring中和@Component功能一致,创建对象的注解还有:
* (1)@Repository(应用在持久层类上面的注解) 放在dao层的实现类上面,表示创建dao对象,dao对象是能够访问数据库的
* (2)@Service(应用在业务层类的上面的注解) 放在service的实现类上面,创建service对象,service对象是做业务处理,可以有事务处理等功能的
* (3)@Controller(应用在控制器类上面的注解) 放在控制器(处理器)类上面,创建控制器对象的,控制器对象能够接受用户提交的参数,显示请求的处理结果。
* 注:以上三个注解的语法和@Component一样的,都能够创建对象,但是这三个注解他还有额外的功能。
* @Repository、@Service、@Controller是给项目的对象分层的。
*/
2、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"
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">
<!--声明组件扫描器(component-scan),组件就是Java对象
base-package:指定在你的项目中的包名
component-scan的工作方式:spring会扫描遍历base-package指定的包,
把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,
或者给属性赋值。
-->
<context:component-scan base-package="com.spring.ba01"/>
<!--扫描指定多个包的三种方式-->
<!--第一种方式:使用多次组件扫描器component-scan,指定不同的包-->
<context:component-scan base-package="com.spring.ba01"/>
<context:component-scan base-package="com.spring.ba02"/>
<!--第二种方式:使用分隔符(;或,)分隔多个包名-->
<context:component-scan base-package="com.spring.ba01;com.spring.ba02"/>
<!--第三种方式:指定父包-->
<context:component-scan base-package="com.spring"/>
</beans>
@Repository
@Repository(应用在持久层类上面的注解) 放在dao层的实现类上面,表示创建dao对象,dao对象是能够访问数据库的
@Service
@Service(应用在业务层类的上面的注解) 放在service的实现类上面,创建service对象,service对象是做业务处理,可以有事务处理等功能的
@Controller
@Controller(应用在控制器类上面的注解) 放在控制器(处理器)类上面,创建控制器对象的,控制器对象能够接受用户提交的参数,显示请求的处理结果。
@Value
1、@value:简单类型的属性赋值
(1)属性:value 是String类型的,表示简单类型的属性值
(2)位置:1、在属性定义的上面,无需set方法,推荐使用。
@Value(value = "值")
@Value(value = "张三") private String name;
2、在set方法上面
@Value(value = "值")
public void setName(String name) { this.name = name; }
(3)@Value注解的书写格式:
//带属性value @Value(value = "值") //不带属性value @Value("值")
(4)在@Value注解中使用
$
符号引入值(简单类型)1、首先创建
test.properties
属性配置文件student.name=张三 student.age=20
2、在spring配置文件中加载属性配置文件如下:
<context:property-placeholder location="classpath:test.properties"/>
3、在@Value中使用
@Value(value = "${student.name}") private String name;
@Autowired
1、引用类型属性赋值
(1)@Autowired:spring框架提供的注解实现引用类型的属性赋值
(2)spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName
、byType
(3)@Autowired:默认使用的是byType自动注入
(4)属性:1、required,是一个boolean类型的,默认为true。
(1)required=true:表示引用类型赋值失败,程序报错,并终止执行
(2)required=false:引用类型如果赋值失败,程序正常执行,引用类型是null(5)位置:
1、在属性定义的上面,无需set方法,推荐使用
@Autowired private School school;
2、在set方法上面
(6)使用ByName的方式,需要做的是:
1、在属性上面加@Autowired
2、在属性上面加@Qualifier(value = “bean的id”):表示使用指定bean名称完成赋值@Autowired @Qualifier(value = "mySchool") private School school;
@Resource
1、引用类型属性赋值
@Resource:来自JDK中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值,使用的也是自动注入原理,支持byName、byType;默认使用的是byName
2、位置:
(1)在属性定义的上面,无需set方法,推荐使用
(2)在set方法上面
3、默认使用的是byName:先使用byName自动注入,如果byName赋值失败,在使用byType
4、@Resource只使用byName方式注入,需要添加一个属性 name:name的值是bean的id(名称)
,如下:@Resource(name = "mySchool")
什么时候使用注解或XML配置文件
1、不经常修改的时候使用注解
2、经常需要修改的时候使用XMl配置文件
面向切面编程(Aspect oriented programming,简写:AOP)
AOP的概念和作用
AOP:(面向切面编程)
:AOP就是动态代理的规范化,把动态代理的实现步骤、方式都定义好了,让开发人员统一的方式实现动态代理。AOP底层,就是采用动态代理的模式实现的,采用了两种代理:JDK动态代理
、CGLIB动态代理
(1)JDK动态代理:使用JDK中的Proxy、Method、InvocationHandler创建代理对象。JDK动态代理要求目标类必须实现接口。
(2)CGLIB动态代理:第三方工具库,创建代理对象,原理是继承。通过继承目标类,创建子类。子类就是代理对象,要求目标类不能是final定义的,方法也不能是final定义的。
动态代理的作用
:
(1)在目标类源代码中不改变源代码的情况下,增加功能
(2)减少代码冗余、或减少重复代码
(3)专注业务逻辑代码
(4)解耦合,让日志、事务功能等非业务代码与业务代码分离
Q1:如何理解 Aspect oriented programming 字面意思?
Aspect
:
(1)切面,给你的目标类增加的功能,就是切面,像日志、事务等。
(2)切面的特点:一般都是非业务方法,可以独立使用,(非业务逻辑的)
Orient
:面向,对着
Programming
:编程
Q2:如何理解面向切面编程?
(1)需要在项目分析功能时,找出切面
(2)合理安排切面执行时间(在目标方法前,还是目标方法后)
(3)合理安排切面执行的位置,在哪个类,哪个方法增加、增强功能
术语:
(1)Aspect:切面
,表示增强的功能的代码,完成某一个功能。非业务功能常见的切面功能有:
日志
、事务
、统计信息
、参数信息
、权限验证
(2)
JoinPoint:连接点
,连接业务方法和切面的位置。就某个类中的业务方法。
(3)Pointcut:切入点
,指多个连接点方法的集合。多个方法
(4)目标对象
:给哪个类的方法增加功能,这个类就是目标对象
(5)Advice:通知
,表示切面功能执行的时间
Q3:一个切面的三个关键要素
:
(1)切面的功能代码,切面能干什么
(2)切面的执行位置,使用Pointcut表示且切面执行的位置
(3)切面执行的时间,使用Advice表示时间,在目标类的方法执行之前,还是在目标类的方法执行之后
动态代理
概念:
动态代理
:可以在程序执行过程中创建代理对象。通过代理对象执行方法,给目标类的方法增加额外的功能(功能增强)。实现功能的分离,解耦合。
分类:
一、JDK动态代理(例子)
JDK动态代理:使用JDK中的Proxy、Method、InvocationHandler创建代理对象。JDK动态代理要求目标类必须实现接口。
实现步骤:
(1)创建目标类
//SomeService接口
public interface SomeService {
void doSome();
void doOther();
}
/**
* ServiceUtils:工具类
* doLog()方法,输出当前时间
* doTransaction()方法,业务方法执行完成之后的事务处理
*/
public class SomeServiceImpl implements SomeService {
@Override
public void doSome() {
// ServiceUtils.doLog();
System.out.println("执行业务方法doSome");
// ServiceUtils.doTransaction();
}
@Override
public void doOther() {
// ServiceUtils.doLog();
System.out.println("执行业务方法doOther");
// ServiceUtils.doTransaction();
}
}
(2)创建InvocationHandler接口实现类,在这个类实现给目标方法增加功能
package com.spring.hander;
import com.spring.util.ServiceUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
//目标对象
private Object target; //SomeServiceImpl
public MyInvocationHandler(Object target) {
this.target = target;
}
/**
* ServiceUtils:工具类
* doLog()方法,输出当前时间
* doTransaction()方法,业务方法执行完成之后的事务处理
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过代理对象执行方法时,会调用这个invoke( )
System.out.println("执行了MyInvocationHandler中的invoke方法");
Object res = null;
ServiceUtils.doLog();
//执行目标类的方法,通过Method类invoke方法实现
res = method.invoke(target, args); //SomeServiceImpl.doOther(),doSome()
ServiceUtils.doTransaction();
//目标方法执行结果
return res;
}
}
(3)使用JDK中类Proxy,创建代理对象,实现创建对象的能力
public class MyAPP {
public static void main(String[] args) {
/*使用jdk的Proxy类创建代理对象*/
//创建目标对象
SomeService target= new SomeServiceImpl();
//创建InvocationHandler对象
InvocationHandler handler = new MyInvocationHandler(target);
//使用Proxy创建代理对象
SomeService proxy = (SomeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),handler);
//通过代理执行方法,会调用handler中的invoke方法
proxy.doSome();
}
}
二、CGLIB动态代理
CGLIB动态代理:第三方工具库,创建代理对象,原理是继承。通过继承目标类,创建子类。子类就是代理对象,要求目标类不能是final定义的,方法也不能是final定义的。
1、Java 中使用,参考:CGLIB 动态代理机制。
2、spring中,目标类没有实现接口时,spring框架会自动应用CGLIB动态代理。如果目标类实现接口时,使用CGLIB动态代理,可以在spring配置文件中加入如下配置:
<!--如果期望目标类有接口,实现CGLIB动态代理
<aop:aspectj-autoproxy proxy-target-class="true"/>
其中:proxy-target-class="true" 告诉spring框架,要使用CGLIB动态代理
-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
Spring的AOP的实现
1、AOP是一个规范,是一个动态的规范化,一个标准
2、AOP的技术实现框架:
(1)spring:spring在内部实现了AOP规范,能做AOP工作。spring主要在事务处理时使用了AOP。我们在项目开发中很少使用AOP实现,因为spring的AOP比较比较笨重。
(2)aspectj:一个开源的专门做AOP的框架,spring框架中继承了aspectj 框架,通过spring就能使用 aspectj 的功能1、使用xml的配置文件
2、使用注解,我们在项目中做AOP功能,一般都使用注解,aspectj 有5个注解
AspectJ框架
1、切面的执行时间,这个执行时间在规范中叫做Advice(通知,增强)
在aspectj框架中使用注解表示的。也可以使用xml配置文件中的标签
(1)@Before:前置通知,在目标方法之前执行切面功能
package com.aop.ba01;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @Aspect: 是aspect就框架中的注解
* 作用:表示当前类是切面类
* 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的的上面
*/
@Aspect
public class MyAspect {
/**
* 定义方法,方法实现切面功能
* 方法定义的要求:
* 1、公共方法 public
* 2、方法没有返回值
* 3、方法名称自定义
* 方法可以有参数,也可以没有参数。
* 如果有参数,参数不是自定义的。有几个参数类型可以使用。
*/
/**
* @Before 前置通知注解
* 属性:value 是切入点表达式,表示切面的功能执行位置
* 位置:方法的上面
* 特点:
* 1、在目标方法之前先执行的
* 2、不会改变目标方法的执行结果
* 3、不会影响目标方法的执行
*/
/*@Before(value = "execution(public void com.aop.ba01.SomeService.doSome(String , Integer ))")
public void myBefore() {
//就是你切面要执行的功能代码
System.out.println("前置通知,切面功能:在目标方法之前输出执行时间:" + new Date());
}*/
/**
* 指定通知方法中的参数:JoinPoint
* JoinPoint:业务方法,要加入漆面功能的业务方法
* 作用是:可以在通知方法中获取执行时的信息,例如方法名称,方法的参数
* 如果切面功能中需要用到方法的信息,就加入JoinPoint。
* 这个JoinPoint参数的值由框架赋予,必须是第一个位置的参数
*/
@Before(value = "execution(public void com.aop.ba01.SomeService.doSome(String , Integer ))")
public void myBefore(JoinPoint joinPoint) {
//获取方法的完整定义
System.out.println("方法的签名(定义):" + joinPoint.getSignature());
System.out.println("方法的名称:" + joinPoint.getSignature().getName());
//获取方法的实参
Object args[] = joinPoint.getArgs();
for (Object o : args) {
System.out.println("参数:" + o);
}
//就是你切面要执行的功能代码
System.out.println("前置通知,切面功能:在目标方法之前输出执行时间:" + new Date());
}
}
(2)@AfterReturning:后置通知,目标方法之后执行,可以获取目标方法执行后的返回值
package com.aop.ba02;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @Aspect: 是aspect就框架中的注解
* 作用:表示当前类是切面类
* 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的的上面
*/
@Aspect
public class MyAspect {
/**
* 定义方法,方法实现切面功能
* 方法定义的要求:
* 1、公共方法 public
* 2、方法没有返回值
* 3、方法名称自定义
* 4、方法有参数,推荐使用Object,参数名自定义
*/
/**
* @param res
* @AfterReturning: 后置通知
* 属性:
* 1、value 切入点表达式
* 2、returning 自定义变量:表示目标方法的返回值。
* 自定义变量名称和方法的形参名一样
* 位置: 在方法定义的上面
* 特点:
* 1、在目标方法之后执行
* 2、能够获取目标方法的返回值,可以根据这个返回制作不同的处理功能
* 3、可以操作这个返回值
*/
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
returning = "res")
public void myAfterReturning(JoinPoint joinPoint, Object res) {
//获取方法的完整定义
System.out.println("方法的签名(定义):" + joinPoint.getSignature());
// Object res:目标方法执行后的返回值,根据返回值做切面功能
System.out.println("后置通知,在目标方法之后执行的,获取的返回值是:" + res);
//修改res,修改简单类型返回值,目标方法执行结果没有影响;对引用类型的修改会影响目标方法的返回值
res = "hello World";//简单类型
}
}
(3)@Around:环绕通知
package com.aop.ba03;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
/**
* @Aspect: 是aspect就框架中的注解
* 作用:表示当前类是切面类
* 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的的上面
*/
@Aspect
public class MyAspect {
/**
* 环绕通知方法的定义格式
* 1、public
* 2、必须有一个返回值,推荐使用Object
* 3、方法名称自定义
* 4、方法有参数,固定的参数 ProceedingJoinPoint
*/
/**
* @param proceedingJoinPoint
* @return
* @Around 环绕通知
* 属性:value 切入点表达式
* 位置:在方法定义上面
* 特点:
* 1、他是功能最强的通知
* 2、在目标方法的前和后都能增强功能
* 3、能够控制目标方法是否能够被调用执行
* 4、修改原来的目标方法的执行结果。影响调用结果
*
* 环绕通知:等同于jdk动态代理,InvocationHandler接口
* 参数: ProceedingJoinPoint 就等同于Method
* 作用:执行目标方法
* 返回值:就是目标方法的执行结果,可以被修改
* 执行目标方法时,相当于执行myAround
* 环绕通知:经常做事务,在目标方法之前开启,执行目标方法,在目标方法之后提交事务
*
*/
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//获取第一个参数
Object[] args = proceedingJoinPoint.getArgs();
String name = "";
if (args != null && args.length > 1) {
Object arg = args[0];
name = (String) arg;
}
//实现环绕通知的功能
Object result = null;
System.out.println("环绕通知:在目标方法之前,输出时间:" + new Date());
//1、目标方法的调用
//控制是否执行目标方法
if (name.equals("张三")) {
result = proceedingJoinPoint.proceed();//method.invoke(); Object result = doFirst();
}
System.out.println("环绕通知:在目标方法之后,提交事务");
//2、在目标方法的前和后加入功能
//改变目标方法执行结果
if (result != null) {
result = "Hello AspectJ AOP";
}
//返回环绕通知的执行结果
return result;
}
}
(4)@AfterThrowing:异常通知
package com.aop.ba04;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
/**
* @Aspect: 是aspect就框架中的注解
* 作用:表示当前类是切面类
* 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的的上面
*/
@Aspect
public class MyAspect {
/**
* 异常通知方法的定义格式
* 1、public
* 2、没有返回值
* 3、方法名称自定义
* 4、方法有一个是Exception,如果还有是JoinPoint
*/
/**
* @param ex
* @AfterThrowing 异常通知
* 属性:
* 1、value 切入点表达式
* 2、throwing 自定义变量。表示目标方法抛出的异常对象。变量名必须和方法参数名一样
* 特点:
* 1、在目标方法抛出异常时执行
* 2、可以做异常监控的程序,监控目标方法执行时是否有异常。如果有异常,可以发送邮件或短信通知。
* try{
* SomeServiceImpl.doSecond(..)
* }catch(Exception e){
* myAfterThrowing(Exception ex)
* }
*/
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",
throwing = "ex")
public void myAfterThrowing(Exception ex) {
System.out.println("异常通知:方法发生异常时,执行:" + ex.getMessage());
//发送短信或邮件通知开发人员
}
}
(5)@After:最终通知
package com.aop.ba05;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
/**
* @Aspect: 是aspect就框架中的注解
* 作用:表示当前类是切面类
* 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的的上面
*/
@Aspect
public class MyAspect {
/**
* 最终通知方法的定义格式
* 1、public
* 2、没有返回值
* 3、方法名称自定义
* 4、没有参数,如果还有是JoinPoint
*/
/**
* @After 最终通知
* 属性:value 切入点表达是
* 位置:在方法的上面
* 特点:
* 1、总会执行
* 2、在目标方法之后执行
*
*
* try{
* SomeServiceImpl.doThird(..)
* }catch(Exception e){
*
* }finally{
* myAfter()
* }
*/
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("执行最终通知,总会被执行的代码");
//一般做资源清除工作
}
}
(6)@Pointcut:定义和管理切入点
package com.aop.ba06;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* @Aspect: 是aspect就框架中的注解
* 作用:表示当前类是切面类
* 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的的上面
*/
@Aspect
public class MyAspect {
@After(value = "myPointCut()")
public void myAfter() {
System.out.println("执行最终通知,总会被执行的代码");
//一般做资源清除工作
}
@Before(value = "myPointCut()")
public void myBefore() {
System.out.println("执行前置通知,目标代码执行之前执行");
//一般做资源清除工作
}
/**
* @pointCut 定义和管理切入点,如果项目中有多个切入点和表达式是重复的、可复用的。可以使用PointCut
* 属性:value 切入点表达式
* 位置:在自定义方法上面
* 特点:
* 当使用@PointCut定义在一个方法上面,此时这个方法的名称就是切入点表达式的别名
* 其他的通知中,value属性就可以使用这个方法名称,代替切入点表达式了
*/
@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
private void myPointCut(){
//无需代码
}
}
2、表示切面执行的位置(Pointcut),使用的是 aspectj切入点表达式。
3、使用aspectj框架实现aop
目的
:是给已经存在的一些类和方法,增加额外的功能,前提是不改变原来的类的代码。
使用aspectj实现aop的基本步骤(案例:以@Before
为例):
(1)新建maven项目
(2)加入依赖:spring依赖、aspectj依赖
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring-aop-aspectj-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
(3)创建目标类:接口及其实现类;要做的是给类中的方法增加功能
a、接口
package com.aop.ba01;
public interface SomeService {
void doSome(String name, Integer age);
}
b、实现类
package com.aop.ba01;
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name, Integer age) {
//给doSome方法增加一个功能,在doSome方法执行之前输出执行时间
System.out.println("======目标方法doSome()======");
}
}
(4)创建切面类:普通类
1、在类的上面加入
@Aspect
注解
2、在类中定义方法,方法就是切面要执行的功能代码;在方法上面加入aspectj中的通知注解,例如:@Before。有需要指定切入点表达式execution().
package com.aop.ba01;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @Aspect: 是aspect就框架中的注解
* 作用:表示当前类是切面类
* 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的的上面
*/
@Aspect
public class MyAspect {
/**
* 定义方法,方法实现切面共拿给你
* 方法定义的要求:
* 1、公共方法 public
* 2、方法没有返回值
* 3、方法名称自定义
* 方法可以有参数,也可以没有参数。
* 如果有参数,参数不是自定义的。有几个参数类型可以使用。
*/
/**
* @Before 前置通知注解
* 属性:value 是切入点表达式,表示切面的功能执行位置
* 位置:方法的上面
* 特点:
* 1、在目标方法之前先执行的
* 2、不会改变目标方法的执行结果
* 3、不会影响目标方法的执行
*/
/*@Before(value = "execution(public void com.aop.ba01.SomeService.doSome(String , Integer ))")
public void myBefore() {
//就是你切面要执行的功能代码
System.out.println("前置通知,切面功能:在目标方法之前输出执行时间:" + new Date());
}*/
/**
* 指定通知方法中的参数:JoinPoint
* JoinPoint:业务方法,要加入漆面功能的业务方法
* 作用是:可以在通知方法中获取执行时的信息,例如方法名称,方法的参数
* 如果切面功能中需要用到方法的信息,就加入JoinPoint。
* 这个JoinPoint参数的值由框架赋予,必须是第一个位置的参数
*/
@Before(value = "execution(public void com.aop.ba01.SomeService.doSome(String , Integer ))")
public void myBefore(JoinPoint joinPoint) {
//获取方法的完整定义
System.out.println("方法的签名(定义):" + joinPoint.getSignature());
System.out.println("方法的名称:" + joinPoint.getSignature().getName());
//获取方法的实参
Object args[] = joinPoint.getArgs();
for (Object o : args) {
System.out.println("参数:" + o);
}
//就是你切面要执行的功能代码
System.out.println("前置通知,切面功能:在目标方法之前输出执行时间:" + new Date());
}
}
(5)创建spring的配置文件:声明对象,把对象交给容器统一管理。使用注解或者xml配置文件的<bean>
标签
1、声明目标对象
2、声明切面类的对象
3、声明 aspectj 框架中的自动代理生成标签。自动代理生成器:用来完成代理对象的自动创建功能的。
<!-- applicationContext.xml(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"
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/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--把对象交给spring容器、由spring容器同意创建、管理对象-->
<!--声明目标对象-->
<bean id="someService" class="com.aop.ba07.SomeServiceImpl"/>
<!--声明切面对象-->
<bean id="myAspect" class="com.aop.ba07.MyAspect"/>
<!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。
创建对励对象是在内存中实现的,修改目标对象的内存中的结构。创建为代理对象,
所以目标对象就是被修改后的代理对象.
aspectj-autoproxy:会把spring容器中所有的目标对象,一次性都声称代理对象。
-->
<!--<aop:aspectj-autoproxy/>-->
<!--如果期望目标类有接口,实现CGLIB动态代理
<aop:aspectj-autoproxy proxy-target-class="true"/>
其中:proxy-target-class="true" 告诉spring框架,要使用CGLIB动态代理
-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
(6)创建测试类,从spring容器中获取目标对象(实际就是代理对象)。通过代理执行方法,实现aop的功能增强
public class AppTest {
@Test
public void test01() {
String config = "classpath:applicationContext.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
SomeService proxy = (SomeService) context.getBean("someService");
//通过代理的对象执行方法,实现目标方法执行时,增强了功能
proxy.doSome("张三",20);
}
}
(7)效果
》》》》》至此教程结束《《《《《