Spring_IOC与AOP

上篇博文中模拟了IOC的底层实现,接下来对spring的IOC和AOP部分进行总结。

1、首先看看spring框架是怎么替我们创建对象的,spring_ioc入门。

导入相关的jar包,然后copy下面的程序,放入指定的位置。就可以体验到spring的ioc服务。

User.java:

public class User {

	public void test() {
		System.out.println("test...");
	}
	
}

log4j.properties:

#控制父类日志记录器的日志级别为info,默认所有模块下只输出info级别以上的日志
log4j.rootLogger=info,console
 
############# 日志输出到控制台 ############# 
#日志输出到控制台使用的api类  
log4j.appender.console=org.apache.log4j.ConsoleAppender  
#指定当前输出源的日志级别,有了前面的配置,就不需要配置该项了
#log4j.appender.console.Threshold = info
#指定日志输出的格式:灵活的格式
log4j.appender.console.layout=org.apache.log4j.PatternLayout  
#具体格式的内容
log4j.appender.console.layout.ConversionPattern=%d %-2p [%c.%M()] - %m%n

ApplicationContext.xml:(图中没有展开,该文件可以放在src目录下,也可以放在其他的路径下,同样名字可以是其他,如bean.xml,而笔者为了方便管理,把该文件放入springConfig文件夹中)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	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">
	
	<bean id="user" class="h.l.pojo.User"></bean>
	
</beans>

UserTest.java: 

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

class UserTest {

	ApplicationContext context;
	@BeforeEach
	void setUp() throws Exception {
		context = new ClassPathXmlApplicationContext("springConfig/ApplicationContext.xml");
	}

	@Test
	void test() {
		User user = (User) context.getBean("user");
		System.out.println(user);
		user.test();
	}

}

运行结果: 

2018-11-25 10:28:18,864 INFO [org.springframework.context.support.ClassPathXmlApplicationContext.prepareRefresh()] - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@29b5cd00: startup date [Sun Nov 25 10:28:18 CST 2018]; root of context hierarchy
2018-11-25 10:28:18,943 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions()] - Loading XML bean definitions from class path resource [springConfig/ApplicationContext.xml]
h.l.pojo.User@61dd025
test...

可以发现,User对象的创建不用我们自己动手,spring框架会自动线dom4j解析ApplicationContext.xml文件,然后根据id找到对应的bean,得到该bean中的class属性,通过反射,创建出指定对象,好处是什么呢?class属性所指定的类路径,无论该类放哪个路径或者文件夹下,都可以通过更改class属性中的值,让框架准确帮你创建对象,解耦!(可以参考Spring_IOC底层实现模拟

2、入门了spring_ioc后,下面总结spring中bean实例化的三种方式。

(1)使用类的无参构造创建:(下面的写法就是使用的是pojo类的无参方法实现bean实例化的,如果想测试的话,pojo类中带有参构造方法,不带无参构造方法即可发现,此时对象是无法创建的)

<bean id="user" class="h.l.pojo.User"></bean>

无法创建时的报错信息:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'user' defined in class path resource [springConfig/ApplicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [h.l.pojo.User]: No default constructor found; nested exception is java.lang.NoSuchMethodException: h.l.pojo.User.<init>()

 此时解决方式,就是在pojo类中添加无参构造方法就可以了。

(2)使用静态工厂进行创建:

配置如下:

<bean id="userFactory" class="h.l.pojo.UserFactory" factory-method="getBean"></bean>

静态工厂如下: 

public class UserFactory {

	public static User getBean() {
		return new User();
	}
}

 测试类如下:

	@Test
	void test() {
		User user = (User) context.getBean("user");
		System.out.println(user);
		user.test();
	}

(3)使用实例工厂进行创建:

配置如下:

<bean id="userFactory" class="h.l.pojo.UserFactory"></bean>
<bean id="user" factory-bean="userFactory" factory-method="getBean"></bean>

实例工厂如下:

public class UserFactory {

	public User getBean() {
		return new User();
	}
}

测试如下:

	@Test
	void test() {
		User user = (User) context.getBean("user");
		System.out.println(user);
		user.test();
	}

ps:实际上开发过程中,我们更多的是使用第一种方法,即使用类的无参构造创建对象,配置bean也来得更加方便。

3、bean标签中的属性:

id属性:bean的标识,根据id得到配置对象。注意的是id值不能包含特殊属性。

class属性:创建对象所在类的全路径。

name属性:功能类似id,在name属性中可以包含特殊字符。

scope属性:

  • singleton:默认值,单例的。
  • prototype:多例的。
  • request:web项目中,Spring创建一个Bean对象,将对象存入request域中。
  • session:web项目中,Spring创建一个Bean对象,将对象存入session域中。
  • globalSession:web项目中,Spring创建一个Bean对象,将对象存入globalSession中。

至于单例和多例的测试,仅仅是通过IOC容器创建两个对象,比较一下是否相等即可,单例肯定为true,多例为false。

<bean id="user" class="h.l.pojo.User" scope="prototype或者singleton,默认单例singleton"></bean>

4、属性的注入:

属性的注入方式有三种:

使用set方法注入、有参构造注入、使用接口注入

 在Spring框架中,支持前两种方式

(1)有参构造注入

<bean id="propertyDemo1" class="h.l.pojo2.PropertyDemo1">
	<constructor-arg name="username" value="hello"></constructor-arg>
</bean>
public class PropertyDemo1 {

	private String username;

	public PropertyDemo1(String username) {
		this.username = username;
	}

	@Override
	public String toString() {
		return "PropertyDemo1 [username=" + username + "]";
	}
	
}

(2)set方法的注入

<bean id="propertyDemo1" class="h.l.pojo2.PropertyDemo1">
	<property name="username" value="world"></property>
</bean>
public class PropertyDemo1 {

	private String username;

	public void setUsername(String username) {
		this.username = username;
	}

	@Override
	public String toString() {
		return "PropertyDemo1 [username=" + username + "]";
	}
}

(3)使用set方法注入对象类型的属性:

<bean id="userDao" class="h.l.pojo3.UserDao"></bean>
<bean id="userService" class="h.l.pojo3.UserService">
	<property name="userDao" ref="userDao"></property>
</bean>
public class UserService {

	private UserDao userDao;

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	public void test() {
		System.out.println("service...");
		userDao.test();
	}
}
public class UserDao {

	public void test() {
		System.out.println("Dao...");
	}
}
class UserServiceTest {

	ApplicationContext context;
	@BeforeEach
	void setUp() throws Exception {
		context = new ClassPathXmlApplicationContext(
				"springConfig/ApplicationContext.xml");
	}

	@Test
	void test() {
		UserService userService=(UserService)context.getBean("userService");
		userService.test();
	}

}

当然,也可以使用带参构造函数注入:

<bean id="userDao" class="h.l.pojo3.UserDao"></bean>
<bean id="userService" class="h.l.pojo3.UserService">
	<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
public class UserService {

	private UserDao userDao;

	public UserService(UserDao userDao) {
		this.userDao = userDao;
	}

	public void test() {
		System.out.println("service...");
		userDao.test();
	}
}

对应的地方作相应的修改即可,但是实际开发中使用set的方法注入会更多些,因为pojo类中有set方法,能有更多的灵活性。

(4)p名称空间注入:

public class Person {

	private String username;

	public void setUsername(String username) {
		this.username = username;
	}

	@Override
	public String toString() {
		return "Person [username=" + username + "]";
	}
	
}

通过上面的方式也能够实现属性的注入。如果属性中有对象类型属性,p名称空间注入即可按如下方式进行:

<bean id="user" class="h.l.pojo.User" p:username="I am ok"></bean>
<bean id="person" class="h.l.pojo4.Person" p:username="Is-Me-HL" p:user-ref="user"></bean>

除了p名称空间,还有c名称空间,用法类似,只是语法有些差异,请自行百度。

(5)复杂类型注入:

public class Person {
	private String[] arrs;
	private List<String>list;
	private Map<String,String>map;
	private Properties properties;

	public void setArrs(String[] arrs) {
		this.arrs = arrs;
	}

	public void setList(List<String> list) {
		this.list = list;
	}

	public void setMap(Map<String, String> map) {
		this.map = map;
	}

	public void setProperties(Properties properties) {
		this.properties = properties;
	}

	public void show() {
		System.out.println("arrs:"+"=========="+arrs);
		System.out.println("list:"+"=========="+list);
		System.out.println("map:"+"=========="+map);
		System.out.println("properties:"+"=========="+properties);
	}
	
}
<bean id="person" class="h.l.pojo4.Person">
		<property name="arrs">
			<list>
				<value>1</value>
				<value>2</value>
				<value>3</value>
			</list>
		</property>
		<property name="list">
			<list>
				<value>11</value>
				<value>22</value>
				<value>33</value>
			</list>
		</property>
		<property name="map">
			<map>
				<entry key="1" value="11"></entry>
				<entry key="2" value="22"></entry>
				<entry key="3" value="33"></entry>
			</map>
		</property>
		<property name="properties">
			<props>
				<prop key="jdbc.driver">com.mysql.jdbc.Driver</prop>
				<prop key="jdbc.url">jdbc:mysql://localhost:3306/XXX</prop>
				<prop key="jdbc.user">root</prop>
				<prop key="jdbc.password">root</prop>
			</props>
		</property>
	</bean>

(6)IOC和DI的区别:

IOC:控制反转,把对象创建交给spring进行配置。

DI:依赖注入,向类里面的属性中设置值。

关系:依赖注入不能单独存在,需要在ioc基础之上完成。

5、spring注解开发:在核心jar包的基础上添加spring-aop-4.3.14.RELEASE.jar。

(1)创建对象的四个注解:

import org.springframework.stereotype.Component;

@Component(value="user")
public class User {

	public void show() {
		System.out.println("User...");
	}
}

其余三个注解分别是@Controller(web层)、@Service(业务层)、@Repository(持久层),实际上这四个注解的功能都是一样的,目前来说。

(2)使用注解注入属性:

前提:配置注解扫描:

<?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"> <!-- bean definitions here -->

	<!-- 开启注解扫描 -->
	<context:component-scan base-package="h.l.pojo"></context:component-scan>
</beans>

第一个注解:@Autowired

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component(value = "userService")
public class UserService {

	@Autowired // 需要注意的是Autowired是根据类名找到对应类实现自动注入的,如找到下面的UserDao
	private UserDao userDao;

	public void test() {
		System.out.println("service...");
		userDao.test();
	}
}

第二个注解:@Resource(name="")

import javax.annotation.Resource;
import org.springframework.stereotype.Component;
@Component(value = "userService")
public class UserService {

	@Resource(name="userDao")
	private UserDao userDao;

	public void test() {
		System.out.println("service...");
		userDao.test();
	}
}

6、AOP:面向切面编程,扩展功能不通过修改源代码实现。采用横向抽取机制,取代传统纵向继承体系重复性代码。

关于动态代理:请移步:JDK动态代理

AOP的常见操作术语:

  • 连接点:类里面哪些方法可以被增强,这些方法称为增强点。
  • 切入点:在类里面可以有很多的方法被增强,但实际上我们很有可能挑其中的几个方法增强,因此,实际增强的方法称为切入点。
  • 通知/增强:增强的逻辑,称为增强,比如扩展日志功能,这个日志功能就称为增强,包括前置通知(在方法前执行),后置通知(在方法之后执行),异常通知(方法出现异常),最终通知(在后置之后执行),环绕通知(在方法之前和之后执行)。
  • 切面:把增强应用到具体方法上,过程称为切面(即把增强用到切入点过程)。

关于AOP的demo如下:

先导入相关jar包

Book.java:

package h.l.pojo6;

public class Book {

	public void add() {
		System.out.println("add....");
	}
}

MyBook.java:

package h.l.pojo6;

public class MyBook {

	public void before1() {
		System.out.println("前置增强...");
	}
}

相关配置文件:

<?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" xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->

	<!-- 配置对象 -->
	<bean id="book" class="h.l.pojo6.Book"></bean>
	<bean id="myBook" class="h.l.pojo6.MyBook"></bean>
	<!-- 配置aop操作 -->
	<aop:config>
		<!-- 配置切入点 -->
		<aop:pointcut expression="execution(* h.l.pojo6.Book.*(..))" id="pointcut1"/>
		<!-- 配置切面 -->
		<aop:aspect ref="myBook">
			<aop:before method="before1" pointcut-ref="pointcut1"/>
		</aop:aspect>
	</aop:config>
</beans>

BookTest.java测试类:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

class BookTest {

	ApplicationContext context;
	@BeforeEach
	void setUp() throws Exception {
		context = new ClassPathXmlApplicationContext(
				"springConfig/ApplicationContext.xml");
	}
	@Test
	void test() {
		Book book = (Book) context.getBean("book");
		book.add();
	}

}

测试结果:

前置增强...
add....

上述就是aop(面向切面编程)的一个实例。关于Aspects框架使用表达式配置切入点:下面再进行些总结:

模板:execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)

常用表达式:
    
    (1)execution(* h.l.pojo6.Book.add(..)):表示切入点是h.l.pojo6包下的Book类中的add方法

    (2)execution(* h.l.pojo6.Book.*(..)):表示切入点是h.l.pojo6包下的Book类中的所有方法

    (3)execution(* *.*(..)):表示切入点是所有包下的所有类中的所有方法

    (4)execution(* save*(..)):表示切入点是save开头的所有方法

上面是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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->

	<!-- 配置对象 -->
	<bean id="book" class="h.l.pojo6.Book"></bean>
	<bean id="myBook" class="h.l.pojo6.MyBook"></bean>
	
	<!-- 开启aop操作 -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

 Book.java:

package h.l.pojo6;

public class Book {

	public void add() {
		System.out.println("add....");
	}
}

MyBook.java:

package h.l.pojo6;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyBook {

	@Before(value="execution(* h.l.pojo6.Book.*(..))")
	public void before1() {
		System.out.println("before1...");
	}
}

测试结果:

before1...
add....

以上仅是部分Spring知识总结,其余部分将在下一篇文章进行总结。


注:以上文章仅是个人学习过程总结,若有不当之处,望不吝赐教

猜你喜欢

转载自blog.csdn.net/m0_37265215/article/details/84476786