SSM框架系列学习总结2之Spring AOP
先整理AOP之前, 我先把之前DI的内容整理完!
DI
Spring的依赖注入第二种方式:
通过构造方法注入属性
首先提供一个实体类, 不过这个类没有属性的get和set方法, 只有一个有参的构造方法和toString方法.
public class Boy {
private String id;
private String name;
private Integer age;
private Double salary;
public Boy(String id, String name, Integer age, Double salary) {
System.out.println("IOC容器创建对象");
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return "Boy{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
Spring配置
<!--
通过constructor实现依赖注入
constructor-arg: 通过构造方法传过去的值
type: 用来限制传递的参数的类型
-->
<bean id="boyId" class="com.wtu.spring.di.constructor.Boy">
<!--
<constructor-arg type="java.lang.String">
<value>001</value>
</constructor-arg>
<constructor-arg type="java.lang.String">
<value>小阳</value>
</constructor-arg>
<constructor-arg type="java.lang.Integer">
<value>16</value>
</constructor-arg>
<constructor-arg type="java.lang.Double">
<value>1000</value>
</constructor-arg>
-->
<!-- index: 参数在构造方法中的位置, 第一个参数从0开始 -->
<constructor-arg index="0">
<value>001</value>
</constructor-arg>
<constructor-arg index="1">
<value>小阳</value>
</constructor-arg>
<constructor-arg index="2">
<value>16</value>
</constructor-arg>
<constructor-arg index="3">
<value>1000</value>
</constructor-arg>
</bean>
测试类:
// 启动IOC容器
ApplicationContext ac = new ClassPathXmlApplicationContext(
new String[]{"com/wtu/spring/di/constructor/spring3.0.xml"}
);
Boy boy = (Boy) ac.getBean("boyId");
System.out.println(boy);
//Boy{id='001', name='小阳', age=16, salary=1000.0}
注入Date类型:
在依赖注入中 如何将一个字符串转换成Date对象, 注入到目标对象
public class Boy {
private String id;
private String name;
private Integer age;
private Date birthday;
private Double salary;
public Boy() {
System.out.println("Ioc容器创建对象");
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Boy{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", birthday=" + birthday +
", age=" + age +
", salary=" + salary +
'}';
}
}
Spring配置
<!-- 注册时间格式转换器
-->
<bean id="format" class="java.text.SimpleDateFormat">
<constructor-arg>
<!-- 时间的格式样式 -->
<value>yyyy-MM-dd</value>
</constructor-arg>
</bean>
<!-- 注册bean对象
new SimpleDateFormat("yyyy-MM-dd").parse("2017-12-12");
-->
<bean id="boy" class="com.wtu.spring.di.set.Boy">
<property name="birthday">
<bean factory-bean="format" factory-method="parse">
<constructor-arg>
<value>2017-12-12</value>
</constructor-arg>
</bean>
</property>
</bean>
测试类:
// 启动IOC容器
ApplicationContext ac = new ClassPathXmlApplicationContext(
new String[]{"com/wtu/spring/di/set/spring3.0.xml"}
);
Boy boy = (Boy) ac.getBean("boy");
System.out.println(new SimpleDateFormat("yyyy-MM-dd").parse("2017-12-12"));
//Tue Dec 12 00:00:00 CST 2017
System.out.println(boy);
//Boy{id='null', name='null', birthday=Tue Dec 12 00:00:00 CST 2017, age=null, salary=null}
DI注解形式
结构.pngUserDaoImpl.java:
public class UserDaoImpl implements UserDao {
public UserDaoImpl() {
System.out.println("IOC容器创建对象");
}
@Override
public void addUser() {
System.out.println("添加用户");
}
}
UserServiceImpl.java:
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserServiceImpl() {
System.out.println("IOC容器创建UserServiceImpl对象");
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser() {
System.out.println("service中addUser方法被调用了");
}
}
Spring配置:
<!--注册UserDao对象 -->
<bean id="userDao" class="com.wtu.spring.di.annotation.UserDaoImpl"/>
<!--注册UserService对象 -->
<bean id="userService" class="com.wtu.spring.di.annotation.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
测试类:
// 启动IOC容器
ApplicationContext ac = new ClassPathXmlApplicationContext(
new String[]{"com/wtu/spring/di/annotation/spring3.0.xml"}
);
UserService us = (UserService) ac.getBean("userService");
运行结果:
结果.png
注解改写:
后来的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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启Spring注解的功能 -->
<context:annotation-config/>
<!-- 让springIOC容器自动去扫描某一个包, 创建该包下的bean对象 -->
<context:component-scan base-package="com.wtu.spring.di.annotation2"/>
</beans>
UserDaoImpl.java:
/**
* @Component(value = "userDao")
* 告诉SpringIOC容器创建一个id为userDao的bean对象
* value可以省略, 但是此时生成的id为类名, 并且首字母小写
*/
//@Component(value = "userDao") // userDaoImpl
@Repository(value = "userDao")
public class UserDaoImpl implements UserDao {
public UserDaoImpl() {
System.out.println("IOC容器创建对象");
}
@Override
public void addUser() {
System.out.println("添加用户");
}
}
UserServiceImpl.java:
@Service(value = "userServiceImpl")
public class UserServiceImpl implements UserService {
@Resource(name = "userDao")
// @Autowired
private UserDao userDao;
public UserServiceImpl() {
System.out.println("IOC容器创建UserServiceImpl对象");
}
/*
spring会自动查找id为userDao的bean对象, 如果找到就通过下面的set方法注入进来
*/
// @Resource(name = "userDao")
// public void setUserDao(UserDao userDao) {
// this.userDao = userDao;
// }
@Override
public void addUser() {
System.out.println("service中addUser方法被调用了");
}
}
测试类:
// 启动IOC容器
ApplicationContext ac = new ClassPathXmlApplicationContext(
new String[]{"com/wtu/spring/di/annotation2/spring3.0.xml"}
);
UserService us = (UserService) ac.getBean("userServiceImpl");
us.addUser();
运行结果:
注意:
@Component
现在不提倡使用对于注册bean对象的注解现在提倡使用如下三个:
@Controller
一般作用在springMVC中@Service
作用在业务层@Repository
作用在持久层但是不是绝对的, 任何一个在任何一层都可以用
AOP
AOP.jpeg AOP.jpeg AOP.jpeg如图上述的解决方案不好:
在javaweb三层框架中,dao层是数据访问层,它的核心业务就是操作数据库,然而像输出时间,开启事务, 日志输出等等。这些事情都不是核心业务,我们可以称之为服务代码,我们不能将执行服务代码这样的事情交给dao层来做。但是我们又需要这样的服务代码。所以我们可以找该类的一个代理对象来实现。
动态代理图解:
动态代理图解.png
动态代理核心代码模拟实现
日志类, 切面:
public class MyLog {
@SuppressWarnings("deprecation")
public void writeConsole() {
System.out.println(new Date().toLocaleString());
}
}
目标对象所在类, 执行核心代码:
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
模拟JDK动态代理的Proxy类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 中间类, 相当于JDK动态代理中的Proxy
* @Author menglanyingfei
* @Created on 2018.01.17 16:51
*/
public class Middle {
private UserDao userDao = new UserDaoImpl();
// 定义切面
private MyLog myLog = new MyLog();
public Object getObject() {
/*
第一个参数: 当前类的类加载器
第二个参数: 代理对象实现的接口类型
第三个参数: 是个接口, 在这个接口中具体去处理如何调用切面以及目标对象
匿名内部类
*/
return Proxy.newProxyInstance(
this.getClass().getClassLoader(),
userDao.getClass().getInterfaces(),
new InvocationHandler() {
/**
*
* @param proxy 目标对象
* @param method 需要执行目标对象的方法对应的Method对象
* @param args 执行目标对象业务方法时需要的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 定义返回值, 目标对象方法执行以后的返回值
Object result = "";
// 获取目标对象的方法名
String methodName = method.getName();
if ("addUser".equals(methodName)) {
myLog.writeConsole();
result = method.invoke(userDao, args);
myLog.writeConsole();
} else {
result = method.invoke(userDao, args);
}
return result;
}
}
);
}
}
测试类:
// 通过一个中间类来获取代理对象
Middle middle = new Middle();
UserDao userDao = (UserDao) middle.getObject();
userDao.addUser();
System.out.println(userDao);
//com.wtu.spring.aop.base.UserDaoImpl@5305068a
// 红色标识显示
System.err.println(userDao.getClass());
// 使用JDK动态代理产生的代理对象
//class com.sun.proxy.$Proxy0
运行结果:
运行结果.png
导入aop的所需jar包:
jar.png
SpringAOP的五种通知方式
通知就是告诉代理在什么时候去执行服务代码。
1.前置通知: 执行目标对象业务方法之前执行服务代码(切面)
2.后置通知: 执行目标对象业务方法之后执行服务代码
3.方法正常返回通知:
4.方法抛出异常通知:
5.环绕通知:
由于晚上太想睡觉了, 所以只先总结到前置通知, 把剩下的内容留到下次总结吧!
先弄一下前置通知吧!
先定义一个切面: 输出日志时间类
package com.wtu.spring.aop.before;
import java.util.Date;
/**
* 切面
* @Author menglanyingfei
* @Created on 2018.01.17 17:06
*/
public class MyLog {
@SuppressWarnings("deprecation")
public void writeConsole() {
System.out.println(new Date().toLocaleString());
}
}
目标对象所在类的核心业务代码:
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
// if (true) {
// throw new RuntimeException("测试一下遇到异常执行情况");
// }
System.out.println("添加用户...");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
">
<!-- 注册UserDao对象 -->
<bean id="userDao" class="com.wtu.spring.aop.before.UserDaoImpl">
</bean>
<!-- 配置切面 -->
<bean id="myLog" class="com.wtu.spring.aop.before.MyLog"/>
<!--
aop:config: 配置代理对象
proxy-target-class: 指底层的动态代理使用的是JDK, 还是CGLIB代理;
true表示CGLIB代理, CGLIB代理不是说没有接口, spring会根据目标对象自动生成一个接口
aop:pointcut: 配置切入点
execution: 确定切入点的方法
aop:aspect: 配置切面如何去切切入点
pointcut-ref: 切面中的方法去切哪个目标对象中的切入点
-->
<aop:config proxy-target-class="true">
<aop:pointcut expression="execution(* com.wtu.spring.aop.before.UserDaoImpl.a*(..))" id="xxx"/>
<aop:aspect ref="myLog">
<aop:before method="writeConsole" pointcut-ref="xxx"/>
</aop:aspect>
</aop:config>
</beans>
测试类:
package com.wtu.spring.aop.before;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @Author menglanyingfei
* @Created on 2018.01.17 14:22
*/
public class TestSpringIoc {
public static void main(String[] args) {
// 启动Ioc容器
ApplicationContext ac = new ClassPathXmlApplicationContext(
new String[]{"com/wtu/spring/aop/before/spring3.0.xml"}
);
UserDao userDao = (UserDao) ac.getBean("userDao");
userDao.addUser();
System.out.println(userDao.getClass());
//class com.wtu.spring.aop.before.UserDaoImpl$$EnhancerByCGLIB$$aa48c241
}
}
运行结果:
运行结果.png
完整代码地址见Github
https://github.com/menglanyingfei/SSMLearning/tree/master/spring_day02