Spring IOC与AOP的简单实现
IOC
XML方式
- 将Spring的核心依赖包全部通过maven导入
<!-- spring-core spring-beans spring-context spring-expression-->
<!-- 这几个包的版本必须一致-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
</dependencies>
- 在resources文件夹下创建一个Spring config文件
- 在java文件夹下面创建一个MVC的基本架构(domain、persistence、service、注意test是用来做单元检测的)
其中Account、AccountDAOImpl、AccountService的代码如下:
public class Account {
private String userName;
private String password;
private int age;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Account(String userName, String password, int age) {
this.userName = userName;
this.password = password;
this.age = age;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class AccountDAOImpl implements AccountDAO {
@Override
public void insert() {
System.out.println("新增用户");
}
@Override
public void deletd() {
System.out.println("删除用户");
}
@Override
public void update() {
System.out.println("更新用户");
}
@Override
public void select() {
System.out.println("查询用户");
}
}
public class AccountService {
private AccountDAO accountDAO;
public AccountService(AccountDAO accountDAO) {
this.accountDAO = accountDAO;
}
public void login(){
System.out.println("Service的login方法");
accountDAO.select();
}
public AccountDAO getAccountDAO() {
return accountDAO;
}
public void setAccountDAO(AccountDAO accountDAO) {
this.accountDAO = accountDAO;
}
}
- 根据Account、AccountDAOImpl、AccountService的调用关系(例如Service调用Dao层来实现Account的查询等操作),配置相应的Spring config文件,实现IOC的XML注入方式,注意这里有两种注入方式,一种是构造函数注入,一种是属性注入。
属性注入:
<!-- 传统的配置注入方式 -->
<bean id="accountDao" class="org.csu.spring.demo.ioc.persistence.AccountDAOImpl"/>
<bean id="account" class="org.csu.spring.demo.ioc.demain.Account">
<!-- <property name="userName" value="John"/>-->
<!-- <property name="password" value="123456"/>-->
<!-- <property name="age" value="18"/>-->
</bean>
<bean id="accountService" class="org.csu.spring.demo.ioc.service.AccountService">
<property name="accountDAO" ref="accountDao"/>
</bean>
构造函数注入:
<!-- 传统的配置注入方式 -->
<bean id="accountDao" class="org.csu.spring.demo.ioc.persistence.AccountDAOImpl"/>
<bean id="account" class="org.csu.spring.demo.ioc.demain.Account">
<constructor-arg name="userName" value="John"/>
<constructor-arg name="password" value="666666"/>
<constructor-arg name="age" value="18"/>
</bean>
<bean id="accountService" class="org.csu.spring.demo.ioc.service.AccountService">
<constructor-arg name="accountDAO" ref="accountDao"/>
</bean>
- 在test文件夹下创建一个demo单元测试单元,进行测试
public class demo {
private AccountService service;
@Test
public void test(){
//使用Spring Ioc容器来获取对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Account temp = (Account)context.getBean("account") ;
AccountService service = (AccountService)context.getBean("accountService");
service.login();
System.out.println(temp.getUserName() + "," + temp.getPassword() + "," + String.valueOf(temp.getAge()));
}
}
测试结果如下:
6. 总结
此方法为Spring IOC实现的基础方法-基于XML配置文件实现的。这样的方法虽然可以实现IOC控制反转,但是有明显的不足。如果有一百个类似的MVC结构的类关系,难道你需要在配置文件配置一百次吗?显然这个是不人性化的,所以接下来介绍注解方式实现IOC。
注解方式
注意:前面的1、2、3步和基于XML配置文件实现IOC的步骤一模一样,这里直接从第四步开始!
- 将之前的Spring conf配置文件的bean全部删掉,加上下面这一个语句,注释功能实现的基础就是这个包扫描语句
<!-- 开启注解的包扫描-->
<context:component-scan base-package="org.csu.spring.demo.ioc"/>
- 在MVC框架中的各个类里面加上相应的注释
@Component(“name”)用于实体类的声明
@Component("account")
public class Account {
@Value("Mike")
private String userName;
@Value("888888")
private String password;
@Value("22")
private int age;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
@Repository(“name”)用于Dao类的声明
@Repository("accountDao")
public class AccountDAOImpl implements AccountDAO {
@Override
public void insert() {
System.out.println("新增用户");
}
@Override
public void deletd() {
System.out.println("删除用户");
}
@Override
public void update() {
System.out.println("更新用户");
}
@Override
public void select() {
System.out.println("查询用户");
}
}
@Service(“name”)用于业务类的声明
@Service("accountService")
public class AccountService {
//这个注解它会扫描ioc包的内容,根据类型匹配来注入,如果找不到,会注入null值
@Autowired
//按名称去制定
@Resource(name = "accountDao")
private AccountDAO accountDAO;
public AccountService(AccountDAO accountDAO) {
this.accountDAO = accountDAO;
}
public void login(){
System.out.println("Service的login方法");
accountDAO.select();
}
public AccountDAO getAccountDAO() {
return accountDAO;
}
public void setAccountDAO(AccountDAO accountDAO) {
this.accountDAO = accountDAO;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(“classpath:applicationContext.xml”)
代表JUnit在相应的环境下进行测试
//用Junit创建环境
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class demo {
@Autowired
private AccountService service;
@Test
public void test(){
service.login();
}
}
- 运行demo1的单元测试,结果如下
- 总结
注解的方式只是减少了程序员编码的工作,其中的原理和之前介绍的用配置XML方式实现IOC注入的原理是一模一样的
AOP
传统Spring AOP
动态代理代理类实现
- 建立如下的目录结构(aspectj在后面会介绍,这里暂时不用)
- 相应类的代码
AccountDao与AccountImpl两个类和之前IOC里面的一模一样,直接拷贝即可。
public class proxyDemo implements InvocationHandler {
private Object object;
public proxyDemo(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("select"))
beforeAdvice();
Object proxyObject = method.invoke(object,args);
return proxyObject;
}
public void beforeAdvice(){
System.out.println("前置建议:日志记录。。。");
}
}
public class demo {
// @Autowired
// private AccountDAO accountDAO;
@Test
public void test(){
AccountDAO accountDao = new AccountDAOImpl();
Class class1 = accountDao.getClass();
accountDao = (AccountDAO)Proxy.newProxyInstance(class1.getClassLoader(),class1.getInterfaces(),new proxyDemo(accountDao));
accountDao.insert();
accountDao.select();
}
}
- 运行demo单元测试,结果如下
- 总结
传统AOP就是基于InvocationHandler这个接口实现的,其思想和JVM类似。实现AOP的原理如下:通过一个类实现InvocationHandler接口来编写自己的代理类,代理类通过invoke方法实现,对所有被代理类的方法的切入。这个方法有许多不足:所有被代理的类必须是实现接口的类,而且代理类只能实现对于被代理类的方法的切入。
XML配置实现
- 在Resources下面添加新的Spring config文件,并且进行相应的配置(因为需要用到aop的xml命名空间,这里需要在Spring官网里面进行相应的搜索)
- 新增一个Proxy类,为了和之前的代理类区分
public class SpringAopProxyDemo implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("后置意见。。。");
}
}
- 配置Spring config文件
<?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.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="accountDAO" class="org.csu.spring.demo.aop.persistence.AccountDAOImpl"/>
<bean id="demoProxy" class="org.csu.spring.demo.aop.proxy.SpringAopProxyDemo"/>
<bean id="demoAfterProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="accountDAO"/>
<property name="interceptorNames" value="demoProxy"/>
<property name="proxyInterfaces" value="org.csu.spring.demo.aop.persistence.AccountDAO"/>
</bean>
</beans>
- 修改demo测试单元的内容,并且运行得出结果
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-aop.xml")
public class demo {
@Resource(name="demoAfterProxy")
private AccountDAO accountDAO;
@Test
public void test(){
accountDAO.insert();
}
}
- 结论
这个方式和之前的基于动态代理的实现原理一模一样,只不过把硬编码的方式编程类配置文件的方式。
基于AspectJ的Spring AOP
- 修改Spring config的内容
<?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.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<context:component-scan base-package="org.csu.spring.demo.aop"/>
<bean id="demoAspect" class="org.csu.spring.demo.aop.persistence.demoAspect"/>
</beans>
- 在aspectj文件夹下面创建一个切面类
@Component
@Aspect
public class demoAspect {
//execution(访问修饰符,返回类型,类,方法,参数)
@Before(value = "execution(* org.csu.spring.demo.aop.persistence.*.*(..))")
public void before(){
System.out.println("这是一个前置建议:安全验证。。。");
}
}
- 修改demo测试单元代码,执行得出结果
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-aop.xml")
public class demo {
@Autowired
private AccountDAO accountDAO;
@Test
public void test(){
accountDAO.select();
}
}
- 结论
aspectj的思想和之前传统的思想不太一样。动态代理以一个代理类为核心,所以每一个不同的被代理类都需要对应一个代理类,导致过程很繁琐;但是aspectj的核心为切面,每一个切面可以有对应多个被代理类的多个方法,这样的思想可以简化很多代码量!