文章目录
- 注解开发
- bean相关
- 注解开发定义bean
- 纯注解开发
- 纯注解开发中bean的管理
- 依赖注入相关
- 依赖注入
- 第三方bean管理
- 第三方bean依赖注入
- AOP(Aspect Oriented Programming)面向切面编程
- AOP简介
- AOP核心概念
- AOP工作流程
- AOP切入点表达式
- 通知类型
- AOP通知获取数据
注解开发
bean相关
注解开发定义bean
- 使用@Component定义bean
@Component("bookDao")
public class BookDaoImpl implements BookDao {
}
@Component
public class BookServiceImpl implements BookService {
}
- 核心配置文件中通过组件扫描加载bean
<context:component-scan base-package="com.itheima"/>
- String提供@Component注解的三个衍生注解
- @Controller:用于表现层bean定义
- @Service:用于业务层bean定义
- @Repository:用于数据层bean定义
@Repository("bookDao") // 数据层的bean
public class BookDaoImpl implements BookDao {
}
@Service // 业务层的bean
public class BookServiceImpl implements BookService {
}
纯注解开发
-
在纯注解开发中,使用java类代替spring核心配置文件
- 核心配置文件如下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 核心配置文件中通过组件扫描加载bean--> <context:component-scan base-package="com.itheima"/> </beans>
- java类如下
package com.itheima.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; //声明当前类为Spring配置类 @Configuration //设置bean扫描路径,多个路径书写为字符串数组格式 @ComponentScan({"com.itheima.service","com.itheima.dao"}) public class SpringConfig { }
其中,java类中的@Configuration完全代替了原来核心配置文件的结构
- @Configuration注解用于设定当前类为配置类
- @ConponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据用数组格式
//设置bean扫描路径,多个路径书写为字符串数组格式
@ComponentScan({"com.itheima.service","com.itheima.dao"})
- 读取spring核心配置文件初始化容器对象切换为读取java配置类初始化容器对象
// 加载配置文件初始化容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 加载配置类初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
纯注解开发中bean的管理
-
bean的作用范围
- 使用@Scope定义bean作用范围
//@Scope设置bean的作用范围 @Scope("singleton") public class BookDaoImpl implements BookDao { }
-
bean的生命周期
- 使用@PostConstruct、@PreDestroy定义bean生命周期
@Repository //@Scope设置bean的作用范围 @Scope("singleton") public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } //@PostConstruct设置bean的初始化方法 @PostConstruct public void init() { System.out.println("init ..."); } //@PreDestroy设置bean的销毁方法 @PreDestroy public void destroy() { System.out.println("destroy ..."); } }
依赖注入相关
依赖注入
- 使用@Autowired注解开启自动装配模式(按类型装配)
@Service
public class BookServiceImpl implements BookService {
//@Autowired:注入引用类型,自动装配模式,默认按类型装配
@Autowired
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法
注意:自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法
- 使用@Qualifier注解开启指定名称装配bean
@Service
public class BookServiceImpl implements BookService {
//@Autowired:注入引用类型,自动装配模式,默认按类型装配
@Autowired
//@Qualifier:自动装配bean时按bean名称装配
@Qualifier("bookDao")
private BookDao bookDao;
}
注意:@Qualifier注解无法单独使用,必须配合@Autowired注解使用
- 对于简单类型注入,使用@Value进行装配
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
//@Value:注入简单类型(无需提供set方法)
@Value("${name}") // name来自于配置文件property
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
- 使用@PropertySource注解加载properties文件
@Configuration
@ComponentScan("com.itheima")
//@PropertySource加载properties配置文件
@PropertySource({"jdbc.properties"})
public class SpringConfig {
}
注意:路径仅支持单一文件配置,多文件要使用数组格式配置,不允许使用通配符*
第三方bean管理
- 使用@Bean配置第三方bean
@Configuration
public class SpringConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- 使用独立的配置类管理第三方bean
@Configuration
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
-
将独立的配置类加入核心配置
- 方式一:导入式(推荐使用)
public class JdbcConfig { @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); // 相关配置 return ds; } }
使用@Import注解手动加入配置类到核心配置,此注解只能添加一次,多个数据请用数组格式
@Configuration @Import({JdbcConfig.class}) public class SpringConfig { }
- 方式二:扫描式**(不推荐使用,隐藏性强,不知道在config中导入过哪些配置类)**
@Configuration public class JdbcConfig { @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); // 相关配置 return ds; } }
使用@ComponentScan注解扫描配置类所在的包,加载对应的配置类信息
@Configuration @ComponentScan({"com.itheima.config", "com.itheima.service", "com.itheima.dao"}) public class SpringConfig { }
第三方bean依赖注入
- 简单类型依赖注入
public class JdbcConfig {
//1.定义一个方法获得要管理的对象
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("root")
private String password;
//2.添加@Bean,表示当前方法的返回值是一个bean
//@Bean修饰的方法,形参根据类型自动装配
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- 引用类型依赖注入
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
// 属性设置
return ds;
}
引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象
AOP(Aspect Oriented Programming)面向切面编程
AOP简介
-
AOP:面向切面编程,一种编程范式,指导开发者如何组织程序结构
-
作用:在不改变原始设计的基础上为其进行功能增强
-
spring理念:无入侵式编程
AOP核心概念
- 连接点:程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
- 在SpringAOP中,理解为方法的执行
- 切入点:匹配连接点的式子
- 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 通知:在切入点处执行的操作,也就是共性功能
- 在SpringAOP中,功能最终以方法的形式呈现
- 通知类:定义通知的类
- 切面:描述通知与切入点的对应关系
AOP工作流程
1.Spring容器启动
2.读取所有切面配置中的切入点
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
// 设置切入点,要求配置在方法上方
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void ptx(){}
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
// 设置在切入点pt()的前面运行当前操作(前置通知)
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
3.初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,则创建对象
- 匹配成功,则创建原始对象(目标对象)的代理对象
注:只要有一个切入点匹配成功,就算匹配上了
4.获取bean执行方法
- 获取bean,调用方法并执行,完成操作
- 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
AOP切入点表达式
切入点:要进行增强的方法
切入点表达式:要进行增强的方法的描述方式
- 方式一:执行接口中的无参数方法
- 方式二:执行实现类中的无参数方法
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
execution(public User com.itheima.service.UserService.findById(int))
- 动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
- 访问修饰符:public、private等,可省略
- 返回值
- 包名
- 类/接口名
- 方法名
- 参数
- 异常名:方法定义中抛出指定异常,可以省略
可以使用通配符描述切入点,实现快速描述
- *****:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.itheima.*.UserService.find*(*))
匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
- …:多个连续地任意符号,可以对出现,常用于化简包名与参数的书写
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
- +:专用于匹配子类类型
execution(* *..*Service+.*(..))
书写技巧
- 描述切入点通常描述接口,而不描述实现类,可以降低耦合度
- 访问控制修饰符针对接口开发均采用public描述(可省略public)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
- 包名书写尽量不使用…匹配,效率过低,常用*做单个包描述匹配,或精准匹配
- 接口名/类名书写名称与模块相关的采用*****匹配,例如UserService书写成:*Service,绑定业务层接口名
- 方法名书写以动词进行精准匹配,名词采用*****匹配,例如getById书写成getBy*,selectAll书写成selectAll
- 通常不使用异常作为匹配规则
通知类型
-
AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
-
AOP通知共分为5种类型
-
前置通知
-
后置通知
-
环绕通知(常用)
- 名称:@Around
- 类型:方法注解
- 位置:通知方法定义上方
- 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
- 范例
@Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("around before advice ..."); Object ret = pjp.proceed(); // 表示对原始操作的调用 System.out.println("around after advice ..."); return ret; }
-
返回后通知(不常用)
-
抛出异常后通知(不常用)
-
@Around注意事项
- 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
- 通知中如果未使用ProceedingJoinPoint对原始方法进行调用,将跳过原始方法的执行
- 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,通知方法必须设定为Object类型
- 原始方法的返回值如果是void类型,通知方法的返回值可以设置成void,也可以设置成Object
- 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
AOP通知获取数据
-
获取切入点方法的参数
- JoinPoint:适用于前置、后置、返回后、抛出异常后通知
@Before("pt()") public void before(JoinPoint jp) { Object[] args = jp.getArgs(); System.out.println(Arrays.toString(args)); System.out.println("before advice ..." ); }
- ProceedingJointPoint:适用于环绕通知
@Around("pt()") public Object around(ProceedingJoinPoint pjp) { Object[] args = pjp.getArgs(); System.out.println(Arrays.toString(args)); args[0] = 666; Object ret = null; try { ret = pjp.proceed(args); } catch (Throwable t) { t.printStackTrace(); } return ret; }
注:ProceedingJointPoint是JointPoint的子类
-
获取切入点方法返回值
- 返回后通知
- 环绕通知
-
获取切入点方法运行异常信息
- 抛出异常后通知
- 环绕通知