Spring 深入学习
文章目录
一、本章目标
- 掌握异常抛出增强、最终增强、环绕增强的使用方法
- 理解构造注入
- 掌握p命名空间注入
- 掌握不同数据类型的注入方法
- 掌握使用注解的方式实现IoC
- 掌握使用注解的方式实现AOP
二、Spring 增强
2.1.异常抛出增强
特点
- 在目标对象方法抛出异常时织入增强处理
- 可灵活拔插的异常处理方案
语法:
<aop:config> <aop:aspect ref="增强方法所在的Bean"> <aop:after-throwing method="增强处理方法" pointcut-ref="切入点id" throwing="e" /> </aop:aspect> </aop:config>
实现步骤:
- 在maven中添加依赖包
<!--支持切入点表达式等等--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
- 定义用于增强方法的JavaBean
/** * 定义包含增强方法的JavaBean */ public class ErrorLogger { private static final Logger log = Logger.getLogger(ErrorLogger.class); public void afterThrowing(JoinPoint jp, RuntimeException e) { log.error(jp.getSignature().getName() + " 方法发生异常:" + e); } }
- 配置切面
<!-- 声明增强方法所在的Bean --> <bean id="theErrorLogger" class="com.aiden.aop.ErrorLogger"></bean> <!-- 配置切面 --> <aop:config> <!-- 定义切入点 --> <aop:pointcut id="pointcut" expression="execution(* com.aiden.service.UserService.*(..))"/> <!-- 引用包含增强方法的Bean --> <aop:aspect ref="theErrorLogger"> <!-- 将afterThrowing()方法定义为异常抛出增强并引用pointcut切入点 --> <!-- 通过throwing属性指定为名为e的参数注入异常实例 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/> </aop:aspect> </aop:config>
- 在dao实现中模拟一个小错误
/** * 用户DAO类,实现UserDao接口,负责User类的持久化操作 */ public class UserDaoImpl implements UserDao { //模拟操作数据库 public void saveUser(User user) { System.out.println("保存用户信息到数据库"); throw new RuntimeException("为测试程序运行效果抛出的异常"); } }
- 测试
@org.junit.Test public void testAfterThrowing() { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService service = ctx.getBean("service", UserService.class); User user = new User(); user.setId(1); user.setUsername("aiden"); user.setPassword("666666"); user.setEmail("aiden@163.com"); service.save(user); }
- 运行结果
2.2.最终增强
特点
- 无论方法正常运行还是抛出异常,都会在目标方法最后织入增强处理,即:该增强都会得到执行
- 与Java中finally代码块的作用相似,通常用于释放资源
- 可灵活拔插
语法:
<aop:config> <aop:aspect ref="增强方法所在的Bean"> <aop:after method="增强处理方法" pointcut-ref="切入点id" /> </aop:aspect> </aop:config>
实现步骤
引入依赖包
定义增强的Javabean
/** * 定义包含增强方法的JavaBean */ public class AfterLogger { private static final Logger log = Logger.getLogger(AfterLogger.class); //最终增强方法 public void afterLogger(JoinPoint jp) { log.info(jp.getSignature().getName() + " 方法结束执行。"); } }
- 配置切面
<!-- 声明增强方法所在的Bean --> <bean id="theAfterLogger" class="com.aiden.aop.AfterLogger"></bean> <!-- 配置切面 --> <aop:config> <!-- 定义切入点 --> <aop:pointcut id="pointcut" expression="execution(* com.aiden.service.UserService.*(..))"/> <aop:aspect ref="theAfterLogger"> <!-- 将afterLogger()方法定义为最终增强并引用pointcut切入点 --> <aop:after method="afterLogger" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>
- 测试
@org.junit.Test public void testAfterLogger() { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService service = ctx.getBean("service", UserService.class); User user = new User(); user.setId(1); user.setUsername("aiden"); user.setPassword("666666"); user.setEmail("aiden@163.com"); service.save(user); }
- 运行结果
2.3.环绕增强
特点
- 目标方法前后都可织入增强处理,可获取或修改目标方法的参数、返回值
- 可对目标方法进行异常处理,甚至可以决定目标方法是否执行
<aop:config> <aop:aspect ref="增强方法所在的Bean"> <aop:around method="增强处理方法" pointcut-ref="切入点id" /> </aop:aspect> </aop:config>
实现步骤
引入依赖包,同上。
定义增强的Javabean
/** * 定义包含增强方法的JavaBean */ public class AroundLogger { private static final Logger log = Logger.getLogger(AroundLogger.class); //环绕增强方法 public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable { log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs())); try { Object result = jp.proceed(); log.info("调用 " + jp.getTarget() + "的"+jp.getSignature().getName() +"方法。方法返回值:" + result); return result; } catch (Throwable e) { log.error(jp.getSignature().getName() + " 方法发生异常:" + e); throw e; } finally { log.info(jp.getSignature().getName() + " 方法结束执行。"); } } }
3.配置切面
<!-- 声明增强方法所在的Bean --> <bean id="theAfterLogger" class="com.aiden.aop.AfterLogger"></bean> <!-- 配置切面 --> <aop:config> <!-- 定义切入点 --> <aop:pointcut id="pointcut" expression="execution(* com.aiden.service.UserService.*(..))"/> <aop:aspect ref="theAfterLogger"> <!-- 将afterLogger()方法定义为最终增强并引用pointcut切入点 --> <aop:after method="afterLogger" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>
4.测试
@org.junit.Test public void testAroundLogger() { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService service = ctx.getBean("service", UserService.class); User user = new User(); user.setId(1); user.setUsername("aiden"); user.setPassword("666666"); user.setEmail("aiden@163.com"); service.save(user); }
5.运行结果
三、依赖注入快速回顾
3.1.构造注入
问题:
- 除了使用set方法,还有什么方式实现注入?
分析:
- 可以使用带参构造
- Spring通过构造方法为属性赋值的一种注入方式
- 可以在对象初始化时对属性赋值,具有良好的时效性
语法:
<bean id="唯一标识" class="类的全路径"> <constructor-arg name="参数名称" ref="依赖对象" /> </bean>
注意:
- 编写带参构造方法后,Java虚拟机不再提供默认的无参构造方法,为了保证使用的灵活性,建议自行添加一个无参构造方法
3.2.Set 注入 (重点)
注意:被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is。
Address.java
package com.aiden.pojo; /** * Address类 * * @author Aiden */ public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
Student.java
package com.aiden.pojo; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; /** * 学生信息类 * * @author Aiden */ public class Student { private String name;//姓名 private Address address;//地址 private String[] books;//喜欢的书籍 private List<String> hobbys;//爱好 private Map<String, String> card;//银行卡 private Set<String> games;//喜欢的游戏 private String wife; private Properties info; public void setName(String name) { this.name = name; } public void setAddress(Address address) { this.address = address; } public void setBooks(String[] books) { this.books = books; } public void setHobbys(List<String> hobbys) { this.hobbys = hobbys; } public void setCard(Map<String, String> card) { this.card = card; } public void setGames(Set<String> games) { this.games = games; } public void setWife(String wife) { this.wife = wife; } public void setInfo(Properties info) { this.info = info; } public void show() { System.out.println("name=" + name + ",address=" + address.getAddress() + ",books=" ); for (String book : books) { System.out.print("<<" + book + ">>\t"); } System.out.println("\n爱好:" + hobbys); System.out.println("card:" + card); System.out.println("games:" + games); System.out.println("wife:" + wife); System.out.println("info:" + info); } }
3.3扩展的注入
3.3.1.常量注入
<bean id="student" class="com.aiden.pojo.Student"> <property name="name" value="小明"/> </bean>
@Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = context.getBean("student",Student.class); System.out.println(student.getName()); }
3.3.2Bean注入
<bean id="addressObj" class="com.aiden.pojo.Address"> <property name="address" value="长沙"/> </bean> <bean id="student" class="com.aiden.pojo.Student"> <property name="name" value="小明"/> <!--注意点:这里的值是一个引用,ref--> <property name="address" ref="addressObj"/> </bean>
3.3.3数组注入
<bean id="student" class="com.aiden.pojo.Student"> <property name="name" value="小明"/> <property name="address" ref="addressObj"/> <property name="books"> <array> <value>西游记</value> <value>红楼梦</value> <value>水浒传</value> </array> </property> </bean>
3.3.4List注入
<property name="hobbys"> <list> <value>阅读</value> <value>看电影</value> <value>听歌</value> </list> </property>
3.3.5Map注入
<property name="card"> <map> <entry key="中国农业" value="6228280128069313663"/> <entry key="中国建设" value="621700166000758262"/> </map> </property>
3.3.6set注入
<property name="games"> <set> <value>LOL</value> <value>王者荣耀</value> <value>第五人格</value> </set> </property>
3.3.7Null注入
<property name="wife"><null/></property>
3.3.8Properties 注入
<property name="info"> <props> <prop key="学号">2021001</prop> <prop key="性别">男</prop> <prop key="姓名">小明</prop> </props> </property>
3.3.9P命名和C命名注入
public class User { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age +'}'; } }
3.3.9.1、P命名空间注入 : 需要在头文件中加入约束文件
语法:
<bean id="唯一标识" class="类的全路径" p:"属性1"="注入的值" p:"属性2"="注入的值" /> <bean id="唯一标识" class="类的全路径" p:属性-ref="注入的Bean" />
<!--导入约束 : xmlns:p="http://www.springframework.org/schema/p"--> <!--P(属性: properties)命名空间 , 直接注入属性--> <bean id="user" class="com.aiden.pojo.User" p:name="张三" p:age="20"/>
3.3.9.2、C 命名空间注入 : 需要在头文件中加入约束文件
<!--导入约束 : xmlns:c="http://www.springframework.org/schema/c"--> <!--C(构造: Constructor)命名空间 , 使用构造器注入--> <bean id="user" class="com.aiden.pojo.User" c:name="张三" c:age="20"/>
四、使用注解实现Spring IoC
4.1.使用注解实现Spring IoC
注解方式将Bean的定义信息和Bean实现类结合在一起,Spring提供的注解有
@Component:实现Bean组件的定义
@Repository:用于标注DAO类
@Service:用于标注业务类
@Controller:用于标注控制器类
/** * 用户模块DAO业务层实现 */ //与在XML配置文件中编写<bean id="userDao" class="dao.impl.UserDaoImpl" />等效 @Repository("userDao") public class UserDaoImpl implements UserDao { … }
4.2.使用注解实现Spring IoC
使用 @Autowired 注解实现Bean的自动装配
默认按类型匹配,可使用 @Qualifier 指定Bean的名称
当dao接口有多个实现容易引发错误,可借助 @Qualifier 来区分实现
/** * 用户模块业务层实现 */ @Service("userService") public class UserServiceImpl implements UserService { @Autowired // 默认按类型匹配 @Qualifier("userDao") private UserDao dao; …… }
4.3.使用注解实现Spring IoC
修改配置文件使注解生效,完成Bean的定义和组装
<?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"> <!--扫描包中注解标注的类--> <context:component-scan base-package="com.aiden.service,com.aiden.dao" /> </beans>
五、使用Java标准注解完成装配
5.1.使用Java标准注解完成装配2-1
- 使用 @Resource 注解实现组件装配
- 默认按名称匹配
@Service("userService") public class UserServiceImpl implements UserService { @Resource// 查找名为dao的Bean,并注入给dao属性 private UserDao dao; …… }
@Service("userService") public class UserServiceImpl implements UserService { @Resource(name="userDao")// 为dao属性注入名为userDao的Bean private UserDao dao; …… }
5.2.使用Java标准注解完成装配2-2
示例:
@Service("userService") public class UserServiceImpl implements UserService { @Resource(type = UserDaoImpl.class)// 用于显示指定依赖的Bean的具体类型 private UserDao dao; …… }
- 对比 @Autowired 注解和 @Resource 注解
- @Autowired是Spring提供的注解,@Resource 是Java 提供的
- 在不指定任何参数且不配合其他注解时,@Autowired注解会优先按Bean的类型进行装配,@Resource注解则是优先按Bean的名称进行装配
六、使用注解实现Spring AOP
6.1.使用注解实现Spring AOP
AspectJ
- 面向切面的框架,它扩展了Java语言,定义了AOP语法,能够在编译期提供代码的织入
@AspectJ
- AspectJ 5新增的功能,使用JDK 5.0注解技术和正规的AspectJ切点表达式语言描述切面
Spring通过集成AspectJ框架实现了以注解的方式定义切面,使得配置文件的代码大大减少
- 利用轻量级的字节码处理框架asm处理@AspectJ中所描述的方法参数名
注意: 使用 @AspectJ ,首先要保证所用的JDK是5.0或以上版本
实现AOP的切面主要有以下几个要素
- 使用
@Aspect
注解将一个java类定义为切面- 使用
@Pointcut
定义一个切入点,可以是一个规则表达式,比如下例中某个package下的所有函数,也可以是一个注解等- 使用
@Before
在切入点开始处切入内- 使用
@After
在切入点结尾处切入内- 使用
@AfterReturning
在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)- 使用
@Around
在切入点前后切入内容,并自己控制何时执行切入点自身的内- 使用
@AfterThrowing
用来处理当切入内容部分抛出异常之后的处理逻辑
6.2.使用注解实现Spring AOP
需求说明
使用注解方式实现对业务方法前后增加日志
使用注解定义前置增强和后置增强实现日志功能
@Aspect
@Before
@AfterReturning编写Spring配置文件,完成切面织入
<aop:aspectj-autoproxy>:启用对于@AspectJ注解的支持
实现步骤
- 创建UserServiceLogger类使用注解定义切面
package com.aiden.aop; import java.util.Arrays; import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Pointcut; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; /** * 使用注解定义切面 */ @Aspect public class UserServiceLogger { private static final Logger log = Logger.getLogger(UserServiceLogger.class); @Pointcut("execution(* com.aiden.service.UserService.*(..))") public void pointcut() {} /** * 前置增强 * @param jp */ @Before("pointcut()") public void before(JoinPoint jp) { log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs())); } /** * 后置增强 * @param jp * @param returnValue */ @AfterReturning(pointcut = "pointcut()", returning = "returnValue") public void afterReturning(JoinPoint jp, Object returnValue) { log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法。方法返回值:" + returnValue); } }
- Dao实现
package com.aiden.dao.impl; import org.springframework.stereotype.Repository; import com.aiden.dao.UserDao; import com.aiden.entity.User; /** * 用户DAO类,实现UserDao接口,负责User类的持久化操作 */ @Repository public class UserDaoImpl implements UserDao { public int saveUser(User user) { // 这里并未实现完整的数据库操作,仅为说明问题 System.out.println("保存用户信息到数据库"); return 1; } }
- service实现类
package com.aiden.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.aiden.service.UserService; import com.aiden.dao.UserDao; import com.aiden.entity.User; /** * 用户业务类,实现对User功能的业务管理 */ @Service("userService") public class UserServiceImpl implements UserService { // 声明接口类型的引用,和具体实现类解耦合 @Autowired private UserDao dao; /** * 保存用户 * @param user */ public int save(User user) { // 调用用户DAO的方法保存用户信息 return dao.saveUser(user); } }
- 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: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-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"> <!-- 注解扫描 --> <context:component-scan base-package="service,dao" /> <bean class="aop.UserServiceLogger"/> <aop:aspectj-autoproxy /> </beans>
七、使用注解定义其他类型的增强
7.1.使用注解定义异常抛出增强
问题:
- 使用注解来定义异常抛出增强
分析:
- 使用
@AfterThrowing
注解定义异常抛出增强/** * 用户DAO类,实现UserDao接口,负责User类的持久化操作 */ @Repository public class UserDaoImpl implements UserDao { public void saveUser(User user) { System.out.println("保存用户信息到数据库"); throw new RuntimeException("为测试程序运行效果抛出的异常"); } }
/** * 用户业务类,实现对User功能的业务管理 */ @Service("userService") public class UserServiceImpl implements UserService { // 声明接口类型的引用,和具体实现类解耦合 @Autowired private UserDao dao; public void save(User user) { // 调用用户DAO的方法保存用户信息 dao.saveUser(user); } }
package com.aiden.aop; import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; /** * 通过注解实现异常抛出增强 */ @Aspect public class ErrorLogger { private static final Logger log = Logger.getLogger(ErrorLogger.class); @AfterThrowing(pointcut = "execution(* com.aiden.service.UserService.*(..))", throwing = "e") public void afterThrowing(JoinPoint jp, RuntimeException e) { log.error(jp.getSignature().getName() + " 方法发生异常:" + e); } }
<?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-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"> <context:component-scan base-package="com.aiden.service,com.aiden.dao" /> <bean class="com.aiden.aop.ErrorLogger"></bean> <aop:aspectj-autoproxy /> </beans>
7.2.使用注解定义最终增强
问题:
- 使用注解来定义最终增强
分析:
- 使用
@After
注解定义最终增强/** * 用户DAO类,实现UserDao接口,负责User类的持久化操作 */ @Repository public class UserDaoImpl implements UserDao { public void saveUser(User user) { System.out.println("保存用户信息到数据库"); } }
/** * 用户业务类,实现对User功能的业务管理 */ @Service("userService") public class UserServiceImpl implements UserService { // 声明接口类型的引用,和具体实现类解耦合 @Autowired private UserDao dao; public void save(User user) { // 调用用户DAO的方法保存用户信息 dao.saveUser(user); } }
package com.aiden.aop; import org.apache.log4j.Logger; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; /** * 通过注解实现最终增强 */ @Aspect public class AfterLogger { private static final Logger log = Logger.getLogger(AfterLogger.class); @After("execution(* com.aiden.service.UserService.*(..))") public void afterLogger(JoinPoint jp) { log.info(jp.getSignature().getName() + " 方法结束执行。"); } }
<context:component-scan base-package="com.aiden.service,com.aiden.dao" /> <bean class="com.aiden.aop.AfterLogger"></bean> <aop:aspectj-autoproxy />
7.3.使用注解定义环绕增强
问题:
- 如何使用注解来定义环绕增强?
分析:
- 使用
@Around
注解定义环绕增强package com.aiden.aop; import java.util.Arrays; import org.apache.log4j.Logger; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; /** * 通过注解实现环绕增强 */ @Aspect public class AroundLogger { private static final Logger log = Logger.getLogger(AroundLogger.class); @Around("execution(* com.aiden.service.UserService.*(..))") public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable { log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs())); try { Object result = jp.proceed(); log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法。方法返回值:" + result); return result; } catch (Throwable e) { log.error(jp.getSignature().getName() + " 方法发生异常:" + e); throw e; } finally { log.info(jp.getSignature().getName() + " 方法结束执行。"); } } }
<context:component-scan base-package="com.aiden.service,com.aiden.dao" /> <bean class="com.aiden.aop.AroundLogger"></bean> <aop:aspectj-autoproxy />
7.4.注解使用小结
Spring在同一个功能上提供了注解和配置文件两种实现方式以供选择
通常情况下,优先选择注解来简化配置工作量
常用的注解
@AfterThrowing
@Before
@AfterReturning
@Around
@After
@Aspect
@Pointcut