1、Spring的IOC注解开发
注解配置和 xml 配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样。
IOC目的就是创建对象
1.1 注解开发入门
1)创建Maven工程,添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version> 1.3.1</version>
</dependency>
</dependencies>
2)使用@Component注解配置管理的资源
/**
* 注解IOC:
* 1. 注解写在哪? 要进行IOC的那个类上
* 2. 用什么注解? Component(用在除了三层结构之外的其他类上)、Controller(用在表现层、控制层的类上)、Service(用在业务层的实现类上)、Repository(用在Dao层的实现类上)
* 3. 注解有哪些属性? value属性表示对象的id,如果不配置,那么对象的id就默认是类名首字母小写 userServiceImpl
* 特别容易忘记的点: 一定要在配置文件中配置包扫描
* 其他注解:
* 1. @Scope("prototype"),取值如果是singleton就是单例,如果是prototype就是多例
*/
@Component("userService")// 等同于 在xml里面,<bean id="accountService" class="具体的类">
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("添加用户...");
}
}
3)引入context的命名空间,配置包扫描
- applicationContext.xml中需要引入context的命名空间,
<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 http://www.springframework.org/schema/context/spring-context.xsd">
<--配置包扫描 -->
<context:component-scan base-package="com.it"></context:component-scan>
</beans>
测试:
/**
* Spring的IOC注解的使用步骤:
* 1. 引入依赖
* 2. 编写配置文件配置包扫描
* 3. 在要使用对象的地方,创建核心容器指定要加载的配置文件,然后调用核心容器的getBean(id)获取对象
*/
public class TestSpring {
@Test
public void test01(){
//创建UserService的实现类对象,执行addUser方法
//1. 创建spring的核心容器对象,指定要加载的配置文件
ApplicationContext act = new ClassPathXmlApplicationContext("spring.xml");
//2. 从核心容器中根据id获取对象
UserService userService = (UserService) act.getBean("userService");
userService.addUser();
}
}
1.2 注解开发进阶(常用注解)
3.1用于创建对象的
相对于相当于: <bean id="" class="">
3.1.1 @Component
作用:
把资源让 spring 来管理。相当于在 xml 中配置一个 bean。
属性:
value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。
3.1.2 @Controller @Service @Repository
web里面的三层结构中的所有类,在spring里面都称之为 Component (组件) , 但是它也提供了更详细的注解来针对不同的层级声明 。
三个衍生注解如下:
@Controller :修饰WEB层类 --->web | SpringMVC
@Service :修饰业务层类 --->service
@Repository :修饰DAO层类 --->dao
在需要spring创建对象的类上面使用注解 @Component(“us”) 即可.Spring看类上是否有该注解,如果有该注解,生成这个类的实例。如果不指定value值, 那么默认的id值就是类名的名字, 第一个字母小写.
3.2 用于改变作用范围的@Scope
@Scope
singleton: 单例(默认)
prototype:多例
@Scope注解用来描述类的作用范围的,默认值singleton。如同xml中bean标签的属性scope <bean scope=""/>
.如果配置成多例的使用prototype。
@Scope("prototype")
@Component("accountService")
public class AccountServiceImpl implements AccountService {}
3.3 和生命周期相关的
-
初始化和销毁回调方法对应的注解
@PostConstrut:如同xml中bean标签的属性init-method
<bean init-method=""/>
,用来设置spring框架初始化此类实例时调用的初始化方法,标注在此类的初始化方法上 @PreDestroy:如同xml中bean标签的属性destroy-method
<bean destroy-method=""/>
,用来设置spring框架销毁此类实例时调用的销毁方法,标注在此类的销毁方法上注意:这两个注解都是配在方法上的
1.3 使用注解注入属性(依赖注入)
3.1@Value
-
作用:
注入基本数据类型和 String 类型数据的
-
属性:
value:用于指定值 , 可以通过表达式动态获得内容再赋值
3.2@Autowired
作用:
注入对象类型
根据类型自动注入。当使用注解注入属性时, set 方法可以省略。它只能注入其他 bean 类型。
1.如果只有一个实现类, 可以自动注入成功
2.如果有两个或者两个以上的实现类, 找到变量名一致的id对象给注入进去, 如果找不到,就报错
required属性
@Autowired(required=true):当使用@Autowired注解的时候,其实默认就是required=true,表示注入的时候,该bean必须存在,否则就会注入失败。
@Autowired(required=false):表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错。
3.3@Qualifier
-
作用
在自动按照类型注入(@Autowired)的基础之上,再按照Bean的id注入。
它在给字段注入时不能独立使用,必须和@Autowire一起使用;
但是给方法参数注入时,可以独立使用。
-
属性
value:指定bean的id。
3.4@Resource
如果上面一个接口有多种实现,那么现在需要指定找具体的某一个实现,那么可以使用@Resource
属性
@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的id,而type属性则解析为bean的类型。
@Resource装配顺序:
①如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
②如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
③如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
④如果既没有指定name,又没有指定type,则自动按照byName方式根据属性名进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 注解方式的依赖注入:
* 1. 注解用在哪? 用在要进行依赖注入的成员变量上
* 2. 使用哪个注解?
* 2.1 注入简单类型的数据,使用Value注解
* 2.2 注入对象类型的数据,使用Autowired注解,它会根据类型进行注入
* 如果spring的核心容器中,只有一个该类型的对象,那么就不用指定id,会直接将那个对象注入进来
* 如果spring的核心容器中,有多个同类型的对象,那么此时只根据类型就无法自动注入了,就需要根据变量名
* 进行匹配,匹配对象的id;如果属性名和对象的id无法进行匹配,那么就得自己手动指定要匹配哪个对象的id,
* 就用Qualifier注解
* 2.3 注入对象类型的数据,使用Resource注解,它既可以根据类型注入,也可以根据id注入
* 如果spring的核心容器中,只有一个该类型的对象,那么就不用指定id,会直接将那个对象注入进来
* 如果spring的核心容器中,有多个同类型的对象,那么此时只根据类型就无法自动注入了,就需要根据变量名
* 进行匹配,匹配对象的id;
* 如果属性名和对象的id无法进行匹配,那么就得自己手动指定要匹配哪个对象的id
*/
@Component("userService")
public class UserServiceImpl implements UserService{
//@Autowired
//@Qualifier("anotherUserDaoImpl")
@Resource(name="userDaoImpl")
//@Resource(type = UserDaoImpl.class)
private UserDao userDao;
@Value("abc")
//@Value("${user.name}") //user.name在.yml配置文件里
private String name;
@Override
public void addUser() {
System.out.println(name);
userDao.addUser();
}
}
Spring注解@Resource和@Autowired区别对比详解
@Resource和@Autowired两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。
1.4 混合开发
1.4.1 注解和XML比较
xml和注解一起使用
- xml
- 优点: 方便维护, 改xml文件
- 缺点: 相对注解而言, 麻烦一点
- 注解
- 优点: 开发简洁方便
- 缺点: 维护没有xml那么方便, 需要改源码
1.4.2混合开发特点
IOC: 自己写的类使用注解进行IOC,非自己写的类使用配置文件进行IOC
DI: 如果一个类是使用注解进行IOC的,那么它的属性使用注解进行注入;如果一个类是使用配置文件进行IOC,那么它的属性使用配置文件进行注入
1.5 spring整合mybatis
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!--spring-->
<spring.version>5.0.2.RELEASE</spring.version>
<!--日志打印框架-->
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.12</log4j.version>
<!--mysql-->
<mysql.version>5.1.6</mysql.version>
<!--mybatis-->
<mybatis.version>3.5.3</mybatis.version>
</properties>
<dependencies>
<!--引入依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- log start -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- log end -->
<!--mybatis的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!--
mybatis整合spring的依赖
-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
spring.xml文件配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!--1. 包扫描-->
<context:component-scan base-package="com.itheima"/>
<!--
2. spring整合mybatis
-->
<!--
1. 配置jdbc的环境:
1.1 配置dataSource(既可以使用spring内置的DataSource,又可以使用第三方的DataSource)
1.2 配置事务管理者(现在不做),后面学习声明式事务的时候再讲
2. 将SqlSessionFactoryBean 对象进行IOC配置到spring的核心容器中,并且将dataSource注入进去
3. 扫描dao包,创建出dao的代理对象,交给spring的核心容器管理
各种dao代理对象的id,就是接口名首字母改小写
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="username" value="root"></property>
<property name="password" value="123"></property>
<property name="url" value="jdbc:mysql:///day20?characterEncoding=utf8"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!--加载mybatis的主配置文件-->
<!--<property name="configLocation" value="classpath:SqlMapConfig.xml"></property>-->
<!--别名配置的包扫描-->
<property name="typeAliasesPackage" value="com.it.pojo"></property>
</bean>
<bean id="scannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.it.dao"></property>
</bean>
</beans>
2、AOP相关的概念
2.1AOP概述
AOP:全称是AspectOriented Programming, 即面向切面编程。在不修改源码的基础上,对我们的已有方法进行增强。
说白了就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,进行增强
3.2 AOP的作用和优势
作用:
在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码
提高开发效率
维护方便
3.3 AOP实现原理
使用动态代理技术
- JDK动态代理: 必须需要接口的
- Cglib动态代理: 不需要接口的,只需要类就好了
2.2 AOP的具体应用
1.需求
在所有的service层的save()方法逻辑调用之前,进行权限的校验
2.分析
在AOP 这种思想还没有出现的时候,我们解决 切面的问题思路无非有以下两种:
-
方式一:通过静态方法实现(缺点:需要修改源码,后期不好维护)
把需要添加的代码抽取到一个地方,然后在需要添加那些方法中引用
-
方式二:通过继承方案来解决(缺点:需要修改源码,继承关系复杂,后期不好维护)
抽取共性代码到父类, 子类在需要的位置,调用父类方法。
上述两种方式的缺点
都会打破原来代码的平静(也就是必须要修改代码 , 或者就是必须事先固定好。) 如果我们想在原有代码基础上扩展。 并且不改动原来的代码, 就可以使用AOP了。
其实AOP 字面直译过来是面向切面编程,其实通俗一点它就是在不修改类的源码的基础之上对我们的具体某个方法进行了增强而已。在之前我们对某个方法进行增强无非是两种手段 ,一种是装饰者模式 、 一种是代理模式 (静态代理 & 动态代理) 。 AOP 的底层使用的是动态代理方式 。
3.实现
AOP 的底层动态代理实现有两种方案。
一种是使用JDK的动态代理。 这一种主要是针对有接口实现的情况。 它的底层是创建接口的实现代理类, 实现扩展功能。也就是我们要增强的这个类,实现了某个接口,那么我就可以使用这种方式了. 而另一种方式是使用了cglib 的动态代理,这种主要是针对没有接口的方式,那么它的底层是创建被代理类的子类,实现扩展功能.
3.1JDK方式
要求: 必须有接口
pojo:
import lombok.Data;
@Data
public class Account {
private Integer id;
private String name;
private Double money;
}
AccountDao:
import java.util.List;
/**
* 目标: 获取AccountDao的代理对象,执行查询所有的sql语句
*/
public interface AccountDao {
List<Account> findAll();
}
实现:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class JDKProxyTest {
public static void main(String[] args) {
//使用jdk的动态代理,来代理AccountDao接口
AccountDao accountDao = (AccountDao) Proxy.newProxyInstance(AccountDao.class.getClassLoader(), new Class[]{AccountDao.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy就是代理对象
//method就是需要被代理的方法
//args就是方法的参数
if (method.getName().equals("findAll")) {
//执行查询所有的sql语句
String sql = "select * from account";
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///day29?characterEncoding=utf8", "root", "123");
PreparedStatement pstm = conn.prepareStatement(sql);
ResultSet rst = pstm.executeQuery();
List<Account> accountList = new ArrayList<>();
while (rst.next()) {
int id = rst.getInt("id");
String name = rst.getString("name");
double money = rst.getDouble("money");
Account account = new Account();
account.setId(id);
account.setName(name);
account.setMoney(money);
accountList.add(account);
}
return accountList;
}
return null;
}
});
System.out.println(accountDao.findAll());
}
}
框架的几个方面:
1.配置文件解析
2.
3.反射
4.动态代理
5.设计模式
3.2 CgLib方式
- 添加坐标
<dependencies>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--SpringAOP相关的坐标-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--Spring整合单元测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
-
使用CgLib方式实现: 第三方的代理机制,不是jdk自带的. 没有实现接口的类产生代理,使用的是字节码的增强技术,其实就是产生这个类的子类。
不需要有接口
public class AccountService {
public void save(){
System.out.println("保存账户信息...");
}
}
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 包名:com.itheima.proxy
*
* @author Leevi
* 日期2020-11-11 15:06
*/
public class CglibProxyTest {
public static void main(String[] args) {
//使用cglib的动态代理,代理AccountService对象
AccountService accountService = new AccountService();
Enhancer enhancer = new Enhancer();
//设置enhancer的父类
enhancer.setSuperclass(AccountService.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//o 表示代理对象
// method表示方法
//objects表示参数
if (method.getName().equals("save")) {
//先进行权限校验
System.out.println("校验添加权限。。。");
//再调用save()方法
method.invoke(accountService,objects);
return null;
}
//其它方法就不增强
return method.invoke(accountService,objects);
}
});
//创建代理对象
AccountService proxyService = (AccountService) enhancer.create();
proxyService.save();
}
}
2.3 AOP术语
2.3.1 学习spring中的AOP要明确的事
-
编译期
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成切面中的通知。(开发阶段最后再做)
在配置文件中,声明切入点与通知间的关系,即切面 -
运行期(Spring框架完成的)
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
2.3.2. AOP中的术语
-
JoinPoint: 连接点(所有可以被增强的方法)
类里面哪些方法可以被增强,这些方法称为连接点. 在spring的AOP中,指的是业务层的类的所有现有的方法。
-
Pointcut: 切入点(具体项目中真正已经被增强的方法)
在类里面可以有很多方法被增强,但是实际开发中,我们只对具体的某几个方法而已,那么这些实际增强的方法就称之为切入点
-
Advice: 通知/增强 (具体用于增强方法的代码)
增强的逻辑、称为增强,比如给某个切入点(方法) 扩展了校验权限的功能,那么这个校验权限即可称之为增强 或者是通知
通知分为:
前置通知: 在被增强的方法之前执行.
后置通知: 在被增强的方法之后执行. 特点: 可以得到被增强方法的返回值
异常通知: 在被增强的方法执行出现异常的时候执行. 如果方法没有异常,不会执行. 特点:可以获得异常的信息,主要用来收集异常
最终通知: 指的是无论是否有异常,总是被执行的。
环绕通知:在方法之前和方法之后执行. 特点:可以阻止目标方法执行,还能获取切入点的返回值以及修改返回值
事务通知:
-
Aspect: 切面(所有的通知都是在切面中的)
3、Spring中的AOP
3.1 基于XML的AOP配置(重点)
需求: 在service的增删改查方法调用之前进行权限的校验
实现步骤:
- 导入坐标
<dependencies>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--SpringAOP相关的坐标-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--Spring整合单元测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
- 定义Dao和增强的逻辑, 进行注册
/**
* 目标1: 在执行service的所有方法之前加入权限校验
* 目标2: 在执行完所有的方法之后都获取方法的返回值,并且进行日志打印 "方法执行完毕...,返回值为:", 如果方法出现异常,则不会执行后置通知
* 目标3: 在执行所有方法出现异常之后,将异常信息写入到本地文件中
* 目标4: 在执行所有方法之后,无论是否出现异常,均执行 "资源回收的操作......"
* 目标5: 在执行query()方法的过程中,统计执行时间
* 环绕通知还能修改切入点方法的返回值
*/
@Service
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("执行添加...");
int number = 10/0;
}
@Override
public void deleteById(int id) {
//删除了id为
System.out.println("执行删除..."+id);
}
@Override
public void update() {
System.out.println("执行修改...");
}
@Override
public String query() {
try {
Thread.sleep(3000);
System.out.println("执行查询...");
return "张三";
} catch (InterruptedException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
package com.it.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Component
public class MyAspect {
public void checkPermission(){
System.out.println("进行权限校验....");
}
public void printResult(String returnValue){
//1. 获取被增强方法的返回值,就是returnValue
//2. 打印
System.out.println("执行完毕,方法的返回值为:"+returnValue);
}
public void printError(Throwable errorMsg){
errorMsg.printStackTrace();
//使用流将字符串(异常信息),写入到本地磁盘文件中
System.out.println("使用FileOutputStream将" + errorMsg.getStackTrace() + "写入到磁盘...");
}
public void close(){
System.out.println("执行资源回收的操作...");
}
public Object totalTime(ProceedingJoinPoint joinPoint){
try {
//1. 获取当前时间
long startTime = System.currentTimeMillis();
//2. 执行切入点
Object obj = joinPoint.proceed();
//3. 获取执行完切入点之后的时间
long endTime = System.currentTimeMillis();
System.out.println("方法的总执行时间是:" + (endTime - startTime));
return "hello:"+obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
throw new RuntimeException(throwable.getMessage());
}
}
}
- 配置AOP
- 配置切入点
- 配置切面(切入点和通知结合)
spring-aop.xml配置文件中进行aop配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--1. 包扫描-->
<!--将UserServiceImpl、MyAspect进行IOC-->
<context:component-scan base-package="com.it"/>
<!--
进行aop配置
目标1: 在执行增删改查方法之前,都加入权限校验
1. 切入点是: UserServiceImpl中的所有方法
2. 通知是: MyAspect中的checkPermission方法
3. 通知的类型是: 前置通知
4. 切面是: MyAspect类的对象
目标2: 在执行完所有的方法之后都获取方法的返回值,并且进行日志打印 "方法执行完毕...,返回值为:"
1. 切入点是: UserServiceImpl中的所有方法
2. 通知是: MyAspect中的printResult方法
3. 通知的类型是: 后置通知
4. 切面是: MyAspect类的对象
目标3: 在执行所有方法出现异常之后,将异常信息写入到本地文件中
1. 切入点是: UserServiceImpl中的所有方法
2. 通知是: MyAspect中的printError方法
3. 通知的类型是: 异常通知
4. 切面是: MyAspect类的对象
目标4: 在执行所有方法之后,无论是否出现异常,均执行 "资源回收的操作......"
1.切入点是: UserServiceImpl中的所有方法
2. 通知是: MyAspect中的close方法
3. 通知的类型是: 最终通知
4. 切面是: MyAspect类的对象
目标5: 在执行query()方法的过程中,统计执行时间
1. 切入点是: UserServiceImpl中的query方法
2. 通知是: MyAspect中的totalTime方法
3. 通知的类型是: 环绕通知
4. 切面是: MyAspect类的对象
-->
<aop:config>
<!--
expression是切入点表达式,它的作用是用一个表达式描述切入点
如:execution(* com.it.service.impl.UserServiceImpl.*(..))
* 为方法的返回值
第二个*:为方法名
(..)为参数,参数为任意类型,可有可无
(*) 参数为任意类型,但必须有参数
-->
<aop:pointcut id="pt1" expression="execution(* com.it.service.impl.UserServiceImpl.*(..))"/>
<aop:pointcut id="pt2" expression="execution(String com.it.service.impl.UserServiceImpl.query())"/>
<!--
配置一个切面
-->
<aop:aspect id="ap1" ref="myAspect">
<!--
配置通知去增强切入点
-->
<aop:before method="checkPermission" pointcut-ref="pt1"></aop:before>
<!--
配置后置通知, 后置通知有一个特殊的属性returning用于指定将切入点的返回值赋值给通知中的哪个参数
如果切入点没有返回值,而后置通知又要获取返回值,则后置通知不生效
-->
<aop:after-returning returning="returnValue" method="printResult" pointcut-ref="pt1"></aop:after-returning>
<!--
配置异常通知
-->
<aop:after-throwing throwing="errorMsg" method="printError" pointcut-ref="pt1"></aop:after-throwing>
<!--
配置最终通知
-->
<aop:after method="close" pointcut-ref="pt1"></aop:after>
<!--
配置环绕通知
-->
<aop:around method="totalTime" pointcut-ref="pt2"></aop:around>
</aop:aspect>
</aop:config>
</beans>
一般用xml做AOP
3.2 基于注解的AOP配置
- 添加坐标
<dependencies>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--SpringAOP相关的坐标-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--Spring整合单元测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
定义被增强的业务逻辑类
package com.it.service.impl;
import com.it.service.UserService;
import org.springframework.stereotype.Service;
/**
* 目标1: 在执行所有方法之前,先进行权限校验
* 切入点: UserServiceImpl类的所有方法
* 前置通知: checkPermission()方法
* 切面: MyAspect
* 目标2: 在执行完所有方法之后,进行日志打印 "方法执行完毕...", 如果方法出现异常,则不会执行后置通知
* 切入点:UserServiceImpl类的所有方法
* 后置通知: printLog()方法
* 切面: MyAspect
* 目标3: 在执行所有方法出现异常之后,输出"方法出现异常,请稍后再试"
* 切入点: UserServiceImpl类的所有方法
* 异常通知: printException()
* 切面: MyAspect
* 目标4: 在执行所有方法之后,无论是否出现异常,均执行 "hello world...."
* 切入点: UserServiceImpl类的所有方法
* 最终通知: printHello()
* 切面: MyAspect
* 目标5: 在执行query()方法的过程中,统计执行时间
* 切入点: query()方法
* 环绕通知: total()
* 切面: MyAspect
*/
@Service
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("执行添加...");
int num = 10/0;
}
@Override
public void delete() {
System.out.println("执行删除...");
}
@Override
public void update() {
System.out.println("执行修改...");
}
@Override
public void query() {
try {
Thread.sleep(3000);
System.out.println("执行查询...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
定义切面类,在切面增强类上面使用注解 @Aspect并且在切面类中定义切入点方法
package com.it.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 1. 在spring的配置文件中开启AOP的自动代理 <aop:aspectj-autoproxy />
* 2. 在作为切面的类上添加Aspect注解
* 3. 在切面中声明切入点: 创建一个public void方法,方法名就是切入点的id,方法上添加Pointcut注解用于编写切入点表达式
* 4. 配置通知,在要作为通知的方法上添加注解:
* 1. Before 前置通知
* 2. AfterReturning 后置通知
* 3. After 最终通知
* 4. AfterThrowing 异常通知
* 5. Around 环绕通知
*/
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.it.service.impl.UserServiceImpl.*(..))")
public void pt1(){
}
@Pointcut("execution(String com.it.service.impl.UserServiceImpl.query())")
public void pt2(){
}
@Before("pt1()")
public void checkPermission(){
System.out.println("进行权限校验....");
}
@AfterReturning(value = "pt2()",returning = "returnValue")
public void printResult(String returnValue){
//1. 获取被增强方法的返回值,就是returnValue
//2. 打印
System.out.println("执行完毕,方法的返回值为:"+returnValue);
}
@AfterThrowing(value = "pt1()",throwing = "errorMsg")
public void printError(Throwable errorMsg){
errorMsg.printStackTrace();
//使用流将字符串(异常信息),写入到本地磁盘文件中
System.out.println("使用FileOutputStream将" + errorMsg.getStackTrace() + "写入到磁盘...");
}
@After("pt1()")
public void close(){
System.out.println("执行资源回收的操作...");
}
@Around("pt2()")
public Object totalTime(ProceedingJoinPoint joinPoint){
try {
//1. 获取当前时间
long startTime = System.currentTimeMillis();
//2. 执行切入点
Object obj = joinPoint.proceed();
//3. 获取执行完切入点之后的时间
long endTime = System.currentTimeMillis();
System.out.println("方法的总执行时间是:" + (endTime - startTime));
return "hello:"+obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
throw new RuntimeException(throwable.getMessage());
}
}
}
创建配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
1. 包扫描
-->
<context:component-scan base-package="com.it"/>
<!--
要使用注解方式配置AOP:开启注解AOP的驱动
-->
<aop:aspectj-autoproxy />
</beans>
3.3 切入点表达式
Declaring a Pointcut
详解Spring 框架中切入点 pointcut 表达式的常用写法
切入点表达式详解
* / … / +
切入点表达式的写法:
关键字:execution(表达式)
表达式:访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
标准的表达式写法:public void com.huge.service.impl.AccountServiceImpl.save()
<aop:before method="printLog"
pointcut="execution(public void com.huge.service.impl.AccountServiceImpl.save())">
</aop:before>
1)访问修饰符可以省略
void com.huge.service.impl.AccountServiceImpl.save()
2)返回值可以使用通配符 “*”,表示任意返回值
* com.huge.service.impl.AccountServiceImpl.save()
3)包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
* *.*.*.*.AccountServiceImpl.save()
4)包名可以使用…表示当前包及其子包
* *..AccountServiceImpl.save()
5)定义在service 包里,的任意类的任意类的任意方法。
* com.huge.service.*.*()
6)类名和方法名都可以使用*来实现通配
* *..*.*()
7)指定只有一级包下的service中方法
* *.service..*()
8)所有包下的service,及其子包
* *..service..*()
9)IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意,方法;若为类,则为该类及其子类中的任意方法。
* com.huge.service.IAccountService+.*()
10)以s开头的任意方法
* com.huge.service.*.s*()
参数列表:
数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
如果方法中的参数类型是 j ava.lang 包下类,可以直接使用类名,否则必须使用全限定类名
1)方法不带参数的
* com.huge.service.*.*()
2)可以使用…表示有无参数均可,有参数可以是任意类型
* com.huge.service.*.*(..)
3)两个参数,第一个是String,第二个任意,三个参数不行
execution(* joke(String,*)))
4)两个参数,第一个String,第二个个数和类型不限
execution(* joke(String,..)))
5)Object类,子类不算像String,User
execution(* joke(Object))
6)Object类,或者子类
execution(* joke(Object+)))
附录:切入点表达式示例
-
执行任何公有方法
execution(public * *(..))
-
执行任何以
set
开头的方法execution(* set*(..))
-
执行com.xyz.service.AccountService类中的任何方法__(执行某个类的任何方法)__
execution(* com.xyz.service.AccountService.*(..))
-
执行当前包下类的任意方法(不含子包)
execution(* com.xyz.service.*.*(..))
-
执行当前包及其子包下类的任意方法
execution(* com.xyz.service..*.*(..))
-
执行当前包下类的任意方法(不含子包)
within(com.xyz.service.*)
-
执行当前包及其子包下类的任意方方法
within(com.xyz.service..*)
-
实现当前接口的类的任何方法
this(com.xyz.service.AccountService)
-
实现当前接口的类的任何方法
target(com.xyz.service.AccountService)
-
只有一个参数且实现了Serializable的任何方法
args(java.io.Serializable)
-
有Transactional注解标签的方法(针对特定注解标签)
@target(org.springframework.transaction.annotation.Transactional)
-
有Transactional注解标签的方法(针对特定注解标签)
@within(org.springframework.transaction.annotation.Transactional)
-
有Transactional注解标签的方法(针对特定注解标签)
@annotation(org.springframework.transaction.annotation.Transactional)
-
只有一个参数且参数有Classified注解的任何方法
@args(com.xyz.security.Classified)
-
指定名称的bean下任意方法
bean(tradeService)
-
满足通配符命名的bean下任意方法
bean(*Service)